Flutter:BLE蓝牙开发

说明:

使用flutter_blue_plus插件实现低功耗蓝牙开发。

一、添加蓝牙权限:

1.Android网络权限(工程/android/app/src/main/AndroidManifest.xml):

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

2.iOS蓝牙权限(工程/ios/Runner/Info.plist):

<dict>
    ...
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>Need BLE permission</string>
    <key>NSBluetoothPeripheralUsageDescription</key>
    <string>Need BLE permission</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>Need Location permission</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Need Location permission</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Need Location permission</string>
</dict>

二、实现扫描/连接/接收BLE设备数据:

1.添加flutter_blue_plus插件依赖,在pubspec.yaml中:

dependencies:
  flutter_blue_plus: ^1.3.1     #蓝牙插件

2.实现BLE蓝牙设备扫描:

(1)蓝牙扫描类:

class ScanDevice {
  static const int SCAN_TIMEOUT = 10000;
  final String NAME_PREFIX = "T";
  final String NAME_PREFIX_2 = "HLW";
  final FlutterBluePlus _flutterBlue = FlutterBluePlus.instance; //蓝牙API
  final ScanCallback _callback; //回调接口
  var _isScanning = false;
  late Timer? _timer = null;
  ScanDevice(this._callback);
  //开始扫描
  void startScan({int timeout = SCAN_TIMEOUT}) {
    log("1.开始扫描设备 >>>>>>");
    if (_isScanning) return;
    _isScanning = true;
    _flutterBlue.scanResults.listen((results) {
      for (ScanResult item in results) {
        _handlerScanResult(item);
      }
    });
    _flutterBlue?.startScan(timeout: Duration(seconds: timeout));
    startTimer();
  }
  //N秒后停止扫描, 并回调通知外部
  void startTimer(){
    cancelTimer();
    _timer = Timer(Duration(milliseconds: SCAN_TIMEOUT), () {
      stopScan(); //停止扫描
      _callback.onStop(); //回调通知外部
    });
  }
  void cancelTimer(){
    _timer?.cancel();
    _timer = null;
  }
  //是否扫描中
  bool isScan() {
    return _isScanning;
  }
  //停止扫描
  void stopScan() {
    log("停止扫描设备 >>>>>>");
    cancelTimer();
    if (!_isScanning) return;
    _isScanning = false;
    _flutterBlue.stopScan();
  }
  //处理扫描结果
  void _handlerScanResult(ScanResult result) {
    if (!result.device.name.startsWith(NAME_PREFIX) && !result.device.name.startsWith(NAME_PREFIX_2)) return; //过滤掉非本公司的蓝牙设备
    log('扫到设备, name: ${result.device.name}');
    if (result.device.name.startsWith(NAME_PREFIX_2)) { //针对定制过的蓝牙设备,需要从广播中获取真正的设备名称
      var realName = getRealName(result);  //从广播中获取真实的设备名称
      if (realName != null && realName.startsWith("T")){
        //...
      }
      _callback.onFind(DeviceBean(result.device)); //回调到外部
      return;
    }
    _callback.onFind(DeviceBean(result.device)); //回调到外部
  }
}

(2)蓝牙扫描回调接口:

typedef OnFind = void Function(DeviceBean device);
typedef OnStop = void Function();
class ScanCallback {
  ScanCallback ({required this.onFind, required this.onStop});
  OnFind onFind;
  OnStop onStop;
}

(3)自定义实体类:

class DeviceBean {
  BluetoothDevice device; //设备
  DeviceBean(this.device);
}

3.实现连接设备/接收数据:

(1)连接设备/接收数据管理类:

class ConnectManager {
  final Guid SET_MODE_SERVICE_UUID = Guid("0000180f-0000-1000-8000-00805f9b34fb");           //设置模式-服务UUID
  final Guid SET_MODE_CHARACTERISTIC_UUID = Guid("00002a19-0000-1000-8000-00805f9b34fb");    //设置模式-特征值UUID
  final Guid SET_MODE_DESCRIPTOR_UUID = Guid("00002902-0000-1000-8000-00805f9b34fb");        //设置模式-特征值描述UUID(固定不变)
  final Guid WRITE_DATA_SERVICE_UUID = Guid("01ff0100-ba5e-f4ee-5ca1-eb1e5e4b1ce1");         //写数据-服务UUID
  final Guid WRITE_DATA_CHARACTERISTIC_UUID = Guid("01ff0101-ba5e-f4ee-5ca1-eb1e5e4b1ce1");  //写数据-特征值UUID
  final List<int> ENABLE_NOTIFICATION_VALUE = [0x01, 0x00];  //启用Notification模式
  final List<int> DISABLE_NOTIFICATION_VALUE = [0x00, 0x00]; //停用Notification模式
  final List<int> ENABLE_INDICATION_VALUE = [0x02, 0x00];    //启用Indication模式
  static const int CON_TIMEOUT = 10000;
  final GattCallback _gattCallback;
  late BluetoothCharacteristic? _writeCharacteristic = null;
  late ScanDevice? _scanDevice = null;
  late DeviceBean? _device = null;
  bool isConnecting = false;
  ConnectManager(this._gattCallback);
  //1.扫描
  Future<void> start(String deviceName, {int timeout = ScanDevice.SCAN_TIMEOUT}) async {
    if (_scanDevice == null) {
      _scanDevice = ScanDevice(ScanCallback(
          onFind: (DeviceBean device) { //扫描到设备
            if(isConnecting) {
              _scanDevice?.stopScan();  //停止扫描
              return;
            }
            if(device.device.name == deviceName){
              this._device = device;
              _scanDevice?.stopScan();  //停止扫描
              connect(device.device); //转 - 2.连接
            }
          },
          onStop: () { //停止扫描
            if(this._device == null){
              log("没找到设备 >>>>>>");
              _gattCallback.onDeviceNotFind();  //没扫描到设备时, 回调外部
            }
          }
      ));
    }
    _scanDevice?.startScan(timeout: timeout);
    isConnecting = false;
  }
  //2.连接
  Future<void> connect(BluetoothDevice device) async {
    isConnecting = true;
    log("2.开始连接 >>>>>>name: ${device.name}");
    await device.connect(timeout: Duration(milliseconds: CON_TIMEOUT), autoConnect: false);
    log("连接成功 >>>>>>name: ${device.name}");
    _discoverServices(device);
  }
  //3.发现服务
  Future<void> _discoverServices(BluetoothDevice device) async {
    log("3.开始发现服务 >>>>>>name: ${device.name}");
    List<BluetoothService> services = await device.discoverServices();
    log("发现服务成功 >>>>>>name: ${device.name}");
    _handlerServices(device, services);  //遍历服务列表,找出指定服务
    isConnecting = false;
  }
  //3.1遍历服务列表,找出指定服务
  void _handlerServices(BluetoothDevice device, List<BluetoothService> services){
    services.forEach((sItem) {
      String sUuid = sItem.uuid.toString();
      if(sUuid == SET_MODE_SERVICE_UUID.toString()){ //找到设置模式的服务
        log("4.找到设置模式的服务 >>>>>>name: ${device.name}  serviceGuid: ${SET_MODE_SERVICE_UUID.toString()}");
        _readCharacteristics(device, sItem); //读取特征值
      } else if(sUuid == WRITE_DATA_SERVICE_UUID.toString()){ //找到写数据的服务
        log("4.找到写数据的服务 >>>>>>name: ${device.name}  serviceGuid: ${WRITE_DATA_SERVICE_UUID.toString()}");
        _readCharacteristics(device, sItem); //读取特征值
      }
    });
  }
  //4.读取特征值(读出设置模式与写数据的特征值)
  Future<void> _readCharacteristics(BluetoothDevice device, BluetoothService service) async {
    var characteristics = service.characteristics;
    for(BluetoothCharacteristic cItem in characteristics) {
      String cUuid = cItem.uuid.toString();
      if(cUuid == SET_MODE_CHARACTERISTIC_UUID.toString()){ //找到设置模式的特征值
        log("4.0.找到设置模式的特征值 >>>>>>name: ${device.name}  characteristicUUID: ${SET_MODE_CHARACTERISTIC_UUID.toString()}");
        _requestMtu(device); //设置MTU
        _setNotificationMode(device, cItem); //设置为Notification模式(设备主动给手机发数据)
      } else if(cUuid == WRITE_DATA_CHARACTERISTIC_UUID.toString()){ //找到写数据的特征值
        log("4.0.找到写数据的特征值 >>>>>>name: ${device.name}  characteristicUUID: ${WRITE_DATA_CHARACTERISTIC_UUID.toString()}");
        _writeCharacteristic = cItem; //保存写数据的征值
      }
    }
  }
  //4.1.设置MTU
  Future<void> _requestMtu(BluetoothDevice device) async {
    final mtu = await device.mtu.first;
    log("4.1.当前mtu: $mtu 请求设置mtu为512 >>>>>>name: ${device.name}");
    await device.requestMtu(512);
  }
  //4.2.设置为Notification模式(设备主动给手机发数据),Indication模式需要手机读设备的数据
  Future<void> _setNotificationMode(BluetoothDevice device, BluetoothCharacteristic cItem) async {
    log("4.2.设置为通知模式 >>>>>>name: ${device.name}");
    await cItem.setNotifyValue(true); //为指定特征的值设置通知
    cItem.value.listen((value) {
      if (value == null || value.isEmpty) return;
      log("接收数据 >>>>>>name: ${device.name}  value: $value");
      MessageData data = MessageData();
      //...省略解析设备数据的逻辑
      _gattCallback.onRead(data);  //回调外部,返回设备发送的数据
    });
    var descriptors = cItem.descriptors;
    for (BluetoothDescriptor dItem in descriptors) {
      if (dItem.uuid.toString() == SET_MODE_DESCRIPTOR_UUID.toString()) {//找到设置模式的descriptor
        log("发送Notification模式给设备 >>>>>>name: ${device.name}");
        dItem.write(ENABLE_NOTIFICATION_VALUE); //发送Notification模式给设备
        return;
      }
    }
  }
  //发送指令到设备
  Future<void> writeCommand(List<int> data) async {
    log("发送指令给设备 >>>>>>name: ${device.name}  data: $data");
    await _writeCharacteristic?.write(data);
  }
  //断开连接
  void disconnect(BluetoothDevice device) {
    log("断开连接 >>>>>>name: ${device.name}");
    device.disconnect();  //关闭连接
    _gattCallback.onDisconnect();  //连接失败回调
  }
}

(2)蓝牙连接回调接口:

typedef OnDeviceNotFind = void Function();
typedef OnConnected = void Function();
typedef OnDisconnect = void Function();
typedef OnRead = void Function(MessageData data);
class GattCallback {
  GattCallback ({required this.onDeviceNotFind, required this.onConnected, required this.onDisconnect, required this.onRead});
  OnDeviceNotFind onDeviceNotFind;
  OnConnected onConnected;
  OnDisconnect onDisconnect;
  OnRead onRead;
}

4.调用例子:

...//省略其他
ConnectManager connectManager = ConnectManager(GattCallback( //1.实例化连接管理类,并监听连接状态
    onDeviceNotFind: () { //没找到设备

    },
    onConnected: () { //连接成功回调

    },
    onDisconnect: () { //连接关闭回调

    },
    onRead: (MessageData data) {   //设备发过来的数据

    }
));
...//省略其他
if(!connectManager.isConnecting){
  connectManager.start("T11302002020169"); //2.开始连接蓝牙设备,T11302002020169为蓝牙设备名称
}

猜你喜欢

转载自blog.csdn.net/a526001650a/article/details/127845077