Flutter开发之蓝牙链接传输数据

发布时间:2024年01月19日

本文使用的是flutter_blue_plus插件来实现链接蓝牙之后,和设备直接实现数据互相传输的功能。

1、配置蓝牙权限

iOS权限设置

	<key>NSBluetoothAlwaysUsageDescription</key>
	<string>App需要您的同意,才能访问蓝牙,进行设备连接,数据通讯服务</string>
	<key>NSBluetoothPeripheralUsageDescription</key>
	<string>App需要您的同意,才能访问蓝牙,进行设备连接,数据通讯服务</string>


Android权限设置

    <!-- 蓝牙-->
    <!-- google play store需要-->
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="false" />

    <!--    Android 12-->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

    <!--    Android 11 及以下-->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION " />

2、添加flutter_blue_plus插件

flutter_blue_plus: ^1.31.8

3、搜索蓝牙设备列表页面,如图:

image.png

代码如下:

import 'dart:async';
import 'dart:io';

import 'package:demo/view/device_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';

class BluetoothPage extends StatefulWidget {
  const BluetoothPage({super.key});

  @override
  State<BluetoothPage> createState() => _BluetoothPageState();
}

class _BluetoothPageState extends State<BluetoothPage> {
  ///当前已经连接的蓝牙设备
  List<BluetoothDevice> _systemDevices = [];

  ///扫描到的蓝牙设备
  List<ScanResult> _scanResults = [];

  late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
  late StreamSubscription<bool> _isScanningSubscription;

  @override
  void initState() {
    super.initState();
    _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
      _scanResults = results;
      if (mounted) {
        setState(() {});
      }
    }, onError: (error) {
      print('Scan Error:$error');
    });
    _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    _scanResultsSubscription.cancel();
    _isScanningSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("蓝牙"),
      ),
      body: ListView(
        children: [
          ..._buildSystemDeviceTiles(),
          ..._buildScanResultTiles(),
        ],
      ),
      floatingActionButton: FlutterBluePlus.isScanningNow
          ? FloatingActionButton(
              onPressed: () {
                FlutterBluePlus.stopScan();
              },
              backgroundColor: Colors.red,
              child: const Text("Stop"),
            )
          : FloatingActionButton(
              onPressed: () async {
                try {
                  _systemDevices = await FlutterBluePlus.systemDevices;
                  print('dc-----_systemDevices$_systemDevices');
                } catch (e) {
                  print("Stop Scan Error:$e");
                }
                try {
                  // android is slow when asking for all advertisements,
                  // so instead we only ask for 1/8 of them
                  int divisor = Platform.isAndroid ? 8 : 1;
                  await FlutterBluePlus.startScan(
                      timeout: const Duration(seconds: 15),
                      continuousUpdates: true,
                      continuousDivisor: divisor);
                } catch (e) {
                  print("Stop Scan Error:$e");
                }
                if (mounted) {
                  setState(() {});
                }
              },
              child: const Text("SCAN"),
            ),
    );
  }

  List<Widget> _buildSystemDeviceTiles() {
    return _systemDevices.map((device) {
      return ListTile(
        title: Text(device.platformName),
        subtitle: Text(device.remoteId.toString()),
        trailing: ElevatedButton(
          onPressed: () {
            Get.to(DeviceScreen(device: device));
          },
          child: const Text('CONNECT'),
        ),
      );
    }).toList();
  }

  List<Widget> _buildScanResultTiles() {
    return _scanResults
        .map(
          (scanResult) => ListTile(
            title: Text(
              scanResult.device.platformName,
              overflow: TextOverflow.ellipsis,
            ),
            subtitle: Text(
              scanResult.device.remoteId.toString(),
            ),
            trailing: ElevatedButton(
              onPressed: () {
                Get.to(DeviceScreen(device: scanResult.device));
              },
              child: const Text('CONNECT'),
            ),
          ),
        )
        .toList();
  }
}

4、点击CONNECT进入当前设备详情页面,如图:

image.png

代码如下

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/route_manager.dart';

class DeviceScreen extends StatefulWidget {
  final BluetoothDevice device;
  const DeviceScreen({
    super.key,
    required this.device,
  });

  @override
  State<DeviceScreen> createState() => _DeviceScreenState();
}

class _DeviceScreenState extends State<DeviceScreen> {
  List<BluetoothService> _services = [];
  BluetoothConnectionState _connectionState =
      BluetoothConnectionState.disconnected;
  late StreamSubscription<BluetoothConnectionState>
      _connectionStateSubscription;

  bool get isConnected {
    return _connectionState == BluetoothConnectionState.connected;
  }

  @override
  void initState() {
    super.initState();

    _connectionStateSubscription =
        widget.device.connectionState.listen((state) async {
      _connectionState = state;
      if (state == BluetoothConnectionState.connected) {
        _services = []; // must rediscover services
      }
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    _connectionStateSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          widget.device.platformName,
        ),
        actions: [
          TextButton(
            onPressed: isConnected
                ? () async {
                    await widget.device.disconnect();
                  }
                : () async {
                    await widget.device.connect();
                    print("Connect: Success");
                  },
            child: Text(
              isConnected ? "DISCONNECT" : "CONNECT",
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          ListTile(
            title: Text(
              'Device is ${_connectionState.toString().split('.')[1]}.',
            ),
            trailing: TextButton(
              onPressed: () async {
                if (!isConnected) {
                  Get.snackbar('title', '请先连接蓝牙设备');
                  return;
                }
                _services = await widget.device.discoverServices();
                setState(() {});
              },
              child: const Text("Get Services"),
            ),
          ),
          ..._buildServiceTiles(),
        ],
      ),
    );
  }

  List<Widget> _buildServiceTiles() {
    return _services.map(
      (service) {
        return ExpansionTile(
          title: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              const Text('Service', style: TextStyle(color: Colors.blue)),
              Text(
                '0x${service.uuid.str.toUpperCase()}',
              ),
            ],
          ),
          children: service.characteristics.map((c) {
            return CharacteristicTile(
              characteristic: c,
              descriptorTiles: c.descriptors
                  .map((d) => DescriptorTile(descriptor: d))
                  .toList(),
            );
          }).toList(),
        );
      },
    ).toList();
  }
}

5、点击右上角的CONNECT链接上设备之后,获取service,如图:

image.png

代码如下:


class CharacteristicTile extends StatefulWidget {
  final BluetoothCharacteristic characteristic;
  final List<DescriptorTile> descriptorTiles;

  const CharacteristicTile(
      {Key? key, required this.characteristic, required this.descriptorTiles})
      : super(key: key);

  @override
  State<CharacteristicTile> createState() => _CharacteristicTileState();
}

class _CharacteristicTileState extends State<CharacteristicTile> {
  List<int> _value = [];

  late StreamSubscription<List<int>> _lastValueSubscription;

  @override
  void initState() {
    super.initState();
    _lastValueSubscription =
        widget.characteristic.lastValueStream.listen((value) {
      _value = value;
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    _lastValueSubscription.cancel();
    super.dispose();
  }

  BluetoothCharacteristic get c => widget.characteristic;

  List<int> _getRandomBytes() {
    // 将字符串转换为字节数组
    String data = 'Hello, Bluetooth!';
    List<int> bytes = utf8.encode(data);
    return bytes;
  }

  Future onReadPressed() async {
    try {
      await c.read();
      print("Read: Success");
    } catch (e) {
      print("Read Error:");
    }
  }

  Future onWritePressed() async {
    try {
      await c.write(_getRandomBytes(),
          withoutResponse: c.properties.writeWithoutResponse);
      print("Write: Success");
      if (c.properties.read) {
        await c.read();
      }
    } catch (e) {
      print("Write Error:");
    }
  }

  Future onSubscribePressed() async {
    try {
      String op = c.isNotifying == false ? "Subscribe" : "Unubscribe";
      await c.setNotifyValue(c.isNotifying == false);
      print("$op : Success");
      if (c.properties.read) {
        await c.read();
      }
      if (mounted) {
        setState(() {});
      }
    } catch (e) {
      print("Subscribe Error:");
    }
  }

  Widget buildUuid(BuildContext context) {
    String uuid = '0x${widget.characteristic.uuid.str.toUpperCase()}';
    return Text(uuid);
  }

  Widget buildValue(BuildContext context) {
    String data = _value.toString();
    return Text(
      data,
      style: const TextStyle(fontSize: 13),
    );
  }

  Widget buildReadButton(BuildContext context) {
    return TextButton(
        child: const Text(
          "Read",
          style: TextStyle(fontSize: 13),
        ),
        onPressed: () async {
          await onReadPressed();
          if (mounted) {
            setState(() {});
          }
        });
  }

  Widget buildWriteButton(BuildContext context) {
    bool withoutResp = widget.characteristic.properties.writeWithoutResponse;
    return TextButton(
        child: Text(withoutResp ? "WriteNoResp" : "Write",
            style: const TextStyle(fontSize: 13, color: Colors.grey)),
        onPressed: () async {
          await onWritePressed();
          if (mounted) {
            setState(() {});
          }
        });
  }

  Widget buildSubscribeButton(BuildContext context) {
    bool isNotifying = widget.characteristic.isNotifying;
    return TextButton(
        child: Text(isNotifying ? "Unsubscribe" : "Subscribe"),
        onPressed: () async {
          await onSubscribePressed();
          if (mounted) {
            setState(() {});
          }
        });
  }

  Widget buildButtonRow(BuildContext context) {
    bool read = widget.characteristic.properties.read;
    bool write = widget.characteristic.properties.write;
    bool notify = widget.characteristic.properties.notify;
    bool indicate = widget.characteristic.properties.indicate;
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (read) buildReadButton(context),
        if (write) buildWriteButton(context),
        if (notify || indicate) buildSubscribeButton(context),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return ExpansionTile(
      title: ListTile(
        title: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            const Text(
              'Characteristic',
              style: TextStyle(fontSize: 13),
            ),
            buildUuid(context),
            buildValue(context),
          ],
        ),
        subtitle: buildButtonRow(context),
        contentPadding: const EdgeInsets.all(0.0),
      ),
      children: widget.descriptorTiles,
    );
  }
}

class DescriptorTile extends StatefulWidget {
  final BluetoothDescriptor descriptor;

  const DescriptorTile({Key? key, required this.descriptor}) : super(key: key);

  @override
  State<DescriptorTile> createState() => _DescriptorTileState();
}

class _DescriptorTileState extends State<DescriptorTile> {
  List<int> _value = [];

  late StreamSubscription<List<int>> _lastValueSubscription;

  @override
  void initState() {
    super.initState();
    _lastValueSubscription = widget.descriptor.lastValueStream.listen((value) {
      _value = value;
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    _lastValueSubscription.cancel();
    super.dispose();
  }

  BluetoothDescriptor get d => widget.descriptor;

  List<int> _getRandomBytes() {
    // 将字符串转换为字节数组
    String data = 'Hello, Bluetooth!';
    return utf8.encode(data);
  }

  Future onReadPressed() async {
    try {
      await d.read();
      print("Descriptor Read : Success");
    } catch (e) {
      print("Descriptor Read Error:");
    }
  }

  Future onWritePressed() async {
    try {
      await d.write(_getRandomBytes());
      print("Descriptor Write : Success");
    } catch (e) {
      print("Descriptor Write Error:");
    }
  }

  Widget buildUuid(BuildContext context) {
    String uuid = '0x${widget.descriptor.uuid.str.toUpperCase()}';
    return Text(
      uuid,
      style: TextStyle(fontSize: 13),
    );
  }

  Widget buildValue(BuildContext context) {
    String data = _value.toString();
    return Text(
      data,
      style: TextStyle(fontSize: 13),
    );
  }

  Widget buildReadButton(BuildContext context) {
    return TextButton(
      child: Text("Read"),
      onPressed: onReadPressed,
    );
  }

  Widget buildWriteButton(BuildContext context) {
    return TextButton(
      child: Text("Write"),
      onPressed: onWritePressed,
    );
  }

  Widget buildButtonRow(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        buildReadButton(context),
        buildWriteButton(context),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          const Text(
            'Descriptor',
            style: TextStyle(fontSize: 13),
          ),
          buildUuid(context),
          buildValue(context),
        ],
      ),
      subtitle: buildButtonRow(context),
    );
  }
}

6、展开service,Write就是给设备发送数据,read就是读取设备传递过来的数据,如图:

image.png

发送数据的代码:

Future onWritePressed() async {
    try {
      await c.write(_getRandomBytes(),
          withoutResponse: c.properties.writeWithoutResponse);
      print("Write: Success");
      if (c.properties.read) {
        await c.read();
      }
    } catch (e) {
      print("Write Error:");
    }
  }

  List<int> _getRandomBytes() {
    // 将字符串转换为字节数组
    String data = 'Hello, Bluetooth!';
    List<int> bytes = utf8.encode(data);
    return bytes;
  }

更多具体的功能可以自行查看flutter_blue_plus给出的官方demo代码

简书地址

文章来源:https://blog.csdn.net/weixin_43031192/article/details/135693190
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。