Flutter蓝牙通信插件ble_sdk的使用

Flutter蓝牙通信插件ble_sdk的使用

ble_sdk

<img src="https://img.shields.io/pub/v/ble_sdk?label=ble_sdk" alt="ble_sdk version">
<img src="https://img.shields.io/github/repo-size/ho-doan/ble_sdk" alt="ble_sdk size">
<img src="https://img.shields.io/github/issues/ho-doan/ble_sdk" alt="ble_sdk issues">
<img src="https://img.shields.io/pub/likes/ble_sdk" alt="ble_sdk issues">
  • Bluetooth Low Energy (BLE) 插件,可以与单个设备通信。

Futures

  • BLE 设备发现
  • 连接 BLE
  • BLE 状态
  • 连接 BLE 状态
  • 发现服务
  • 启用通知一个特征值
  • 启用指示一个特征值
  • 读取一个特征值
  • 写入一个特征值

Getting Started

android

Android ProGuard 规则

-keep class com.hodoan.ble_sdk.** { *; }

ios

<key>NSBluetoothAlwaysUsageDescription</key>
<string>使用 BLE</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>使用 BLE</string>

Usage

扫描设备

BleSdk.instance.startScan(services: ['1808']);

停止扫描设备

BleSdk.instance.stopScan();

连接设备

BleSdk.instance.connect(deviceId: '...');

设备发现

BleSdk.instance.discoverServices();

设置通知一个特征值

BleSdk.instance.setNotification(Characteristic(
    characteristicId: '...',
    serviceId: '...',
    properties: [],
));

设置指示一个特征值

BleSdk.instance.setIndication(Characteristic(
    characteristicId: '...',
    serviceId: '...',
    properties: [],
));

读取一个特征值

BleSdk.instance.readCharacteristic(Characteristic(
    characteristicId: '...',
    serviceId: '...',
    properties: [],
));

写入一个特征值

BleSdk.instance.readCharacteristic(CharacteristicValue(
    characteristic: ...,
    data: [],
));

断开设备连接

BleSdk.instance.disconnect();

日志特征值

BleSdk.instance.logResult.listen((_){});

监听读写所有特征值

BleSdk.instance.characteristicResult.listen((_){});

监听 BLE 开关状态

BleSdk.instance.stateBluetoothResult.listen((_){});

连接状态

BleSdk.instance.stateConnectResult.listen((_){});

示例代码

import 'dart:async';
import 'dart:developer';

import 'package:ble_sdk/ble_sdk.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: ScanPage()));
}

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

  [@override](/user/override)
  State<ScanPage> createState() => _ScanPageState();
}

class _ScanPageState extends State<ScanPage> {
  late StreamSubscription deviceStream;

  bool isScan = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    BleSdk.instance
        .isBluetoothAvailable()
        .then((value) => log(value.toString()));
  }

  [@override](/user/override)
  void dispose() {
    if (isScan) {
      deviceStream.cancel();
    }
    super.dispose();
  }

  final ctlServices = TextEditingController(text: '1808');
  List<BluetoothBLEModel> devices = [];

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            title: Text('Ble Sdk'),
          ),
          SliverToBoxAdapter(
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    decoration: const InputDecoration(
                      hintText: 'servicesUUID ex: 1808,1810',
                    ),
                    validator: (s) =>
                        s == null || !s.split(',').any((e) => e.length != 4)
                            ? null
                            : 'server is not validator',
                    autovalidateMode: AutovalidateMode.always,
                    style: const TextStyle(fontSize: 12),
                    controller: ctlServices,
                  ),
                ),
                GestureDetector(
                  onTap: () {
                    if (!ctlServices.text
                        .split(',')
                        .any((e) => e.length != 4)) {
                      setState(() => isScan = true);
                      BleSdk.instance.startScan(
                        services: ctlServices.text
                            .split(',')
                            .where((s) => s.length == 4)
                            .toList(),
                      );
                      setState(() => devices = []);
                      deviceStream =
                          BleSdk.instance.deviceResult.listen((event) {
                        setState(
                          () => devices = List.from(devices)..add(event),
                        );
                      });
                    }
                  },
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      vertical: 4,
                      horizontal: 6,
                    ),
                    color: Colors.blue,
                    child: const Text(
                      'Scan',
                    ),
                  ),
                ),
                const SizedBox(width: 2),
                GestureDetector(
                  onTap: () {
                    BleSdk.instance.stopScan();
                    if (isScan) {
                      deviceStream.cancel();
                      setState(() => isScan = false);
                    }
                  },
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      vertical: 4,
                      horizontal: 6,
                    ),
                    color: Colors.blue,
                    child: const Text(
                      'Stop',
                    ),
                  ),
                ),
                const SizedBox(width: 2),
              ],
            ),
          ),
          SliverList(
            delegate: SliverChildListDelegate(
              [
                for (final device in devices)
                  Container(
                    color: Colors.grey,
                    padding: const EdgeInsets.symmetric(
                      vertical: 10,
                      horizontal: 16,
                    ),
                    child: Row(
                      children: [
                        Expanded(
                          child: Text('${device.name}(${device.id})'),
                        ),
                        GestureDetector(
                          onTap: () async {
                            BleSdk.instance
                                .connect(deviceId: device.id)
                                .then((value) {
                              log('message $value');
                              if (value) {
                                Navigator.of(context).push(
                                  MaterialPageRoute(
                                    builder: (ctx) => DevicePage(device: device),
                                  ),
                                );
                              }
                            });
                          },
                          child: Container(
                            padding: const EdgeInsets.symmetric(
                              vertical: 4,
                              horizontal: 6,
                            ),
                            color: Colors.blue,
                            child: const Text(
                              'Connect',
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

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

  [@override](/user/override)
  State<DevicePage> createState() => _DevicePageState();
}

class _DevicePageState extends State<DevicePage> {
  List<Service> _services = [];
  List<String> _logs = [];
  late StreamSubscription logStream;
  late StreamSubscription subscription;
  [@override](/user/override)
  void initState() {
    logStream = BleSdk.instance.logResult.listen((event) {
      setState(() => _logs = List.from(_logs)
        ..add(
            '${event.characteristic.characteristicId.replaceAll('0000', '').split('-').first}: ${event.message}'));
    });
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      subscription = BleSdk.instance.stateConnectResult.listen((event) {
        if (event == StateConnect.disconnected) {
          Navigator.of(context).pop();
        }
      });
    });
  }

  [@override](/user/override)
  void dispose() {
    subscription.cancel();
    logStream.cancel();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            title: Text(
              'Device ${widget.device.name}\n(${widget.device.id}) -- connected',
            ),
          ),
          SliverToBoxAdapter(
            child: Row(
              children: [
                GestureDetector(
                  onTap: () async {
                    final services = await BleSdk.instance.discoverServices();
                    setState(
                      () => _services = services,
                    );
                  },
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      vertical: 4,
                      horizontal: 6,
                    ),
                    color: Colors.blue,
                    child: const Text(
                      'Discover',
                    ),
                  ),
                ),
                const SizedBox(width: 2),
                GestureDetector(
                  onTap: () => BleSdk.instance.disconnect().then(
                        (value) => Navigator.of(context).pop(),
                      ),
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      vertical: 4,
                      horizontal: 6,
                    ),
                    color: Colors.blue,
                    child: const Text(
                      'Disconnect',
                    ),
                  ),
                ),
                const Spacer(),
                GestureDetector(
                  onTap: () => showDialog(
                    context: context,
                    builder: (ctx) => Material(
                      type: MaterialType.transparency,
                      child: Padding(
                        padding: const EdgeInsets.all(10),
                        child: Container(
                          color: Colors.white,
                          child: ListView(
                            children: [
                              for (final log in _logs) Text(log),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      vertical: 4,
                      horizontal: 6,
                    ),
                    color: Colors.blue,
                    child: const Text(
                      'Log',
                    ),
                  ),
                ),
              ],
            ),
          ),
          SliverList(
            delegate: SliverChildListDelegate(
              [
                for (final service in _services)
                  ServiceWidget(service: service),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class ServiceWidget extends StatelessWidget {
  const ServiceWidget({
    super.key,
    required this.service,
  });

  final Service service;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Container(
        color: Colors.grey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              child: Text(
                  'S: ${service.serviceId.replaceFirst('0000', '').split('-').first}'),
            ),
            for (final char in service.characteristics)
              CharacteristicWidget(char: char),
          ],
        ),
      ),
    );
  }
}

class CharacteristicWidget extends StatefulWidget {
  const CharacteristicWidget({
    super.key,
    required this.char,
  });

  final Characteristic char;

  [@override](/user/override)
  State<CharacteristicWidget> createState() => _CharacteristicWidgetState();
}

class _CharacteristicWidgetState extends State<CharacteristicWidget> {
  [@override](/user/override)
  void initState() {
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(4).copyWith(left: 10),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.black),
      ),
      child: Row(
        children: [
          Expanded(
            child: Text(
              '- C: ${widget.char.characteristicId.replaceAll('0000', '').split('-').first}(${widget.char.properties.map((e) => e.name).join(',')})',
            ),
          ),
          SizedBox(
            width: 120,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                if (widget.char.properties
                    .contains(CharacteristicProperties.READ))
                  GestureDetector(
                    onTap: () => BleSdk.instance.readCharacteristic(widget.char),
                    child: const Icon(
                      Icons.read_more,
                      size: 28,
                      color: Colors.white,
                    ),
                  ),
                if (widget.char.properties
                    .contains(CharacteristicProperties.WRITE))
                  GestureDetector(
                    onTap: () => _write(widget.char),
                    child: const Icon(
                      Icons.edit_note,
                      size: 28,
                      color: Colors.green,
                    ),
                  ),
                if (widget.char.properties
                    .contains(CharacteristicProperties.NOTIFY))
                  GestureDetector(
                    onTap: () => BleSdk.instance.setNotification(widget.char),
                    child: const Icon(
                      Icons.notifications_on,
                      size: 28,
                      color: Colors.redAccent,
                    ),
                  ),
                if (widget.char.properties
                    .contains(CharacteristicProperties.INDICATE))
                  GestureDetector(
                    onTap: () => BleSdk.instance.setNotification(widget.char),
                    child: const Icon(
                      Icons.drag_indicator_rounded,
                      size: 28,
                      color: Colors.redAccent,
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  _write(Characteristic char) {
    showDialog(
      context: context,
      builder: (context) => Material(
        type: MaterialType.transparency,
        child: Center(
          child: WriteCharacteristicWidget(char: widget.char),
        ),
      ),
    );
  }
}

class WriteCharacteristicWidget extends StatefulWidget {
  const WriteCharacteristicWidget({
    super.key,
    required this.char,
  });
  final Characteristic char;

  [@override](/user/override)
  State<WriteCharacteristicWidget> createState() => _WriteCharacteristicWidgetState();
}

class _WriteCharacteristicWidgetState extends State<WriteCharacteristicWidget> {
  final ctlValue = TextEditingController(text: '4');
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Container(
      width: MediaQuery.of(context).size.width - 50,
      padding: const EdgeInsets.all(10),
      height: 300,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            'Write value characteristic: ${widget.char.characteristicId.replaceAll('0000', '').split('-').first}',
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w600,
            ),
          ),
          const Spacer(),
          TextFormField(
            controller: ctlValue,
            decoration: const InputDecoration(
              hintText: '0 or 0,1,2,3,4 ...',
              border: OutlineInputBorder(),
            ),
          ),
          const Spacer(),
          GestureDetector(
            onTap: () => BleSdk.instance.writeCharacteristic(
              CharacteristicValue(
                characteristic: widget.char,
                data: ctlValue.text.split(',').map(
                      (e) => int.parse(e),
                    ),
              ),
            ),
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.green,
                ),
                borderRadius: BorderRadius.circular(8),
                color: Colors.blue,
              ),
              padding: const EdgeInsets.symmetric(
                vertical: 6,
                horizontal: 20,
              ),
              child: const Text(
                'Write',
              ),
            ),
          ),
        ],
      ),
    );
  }
}

更多关于Flutter蓝牙通信插件ble_sdk的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter蓝牙通信插件ble_sdk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


ble_sdk 是一个用于在 Flutter 应用中实现低功耗蓝牙(BLE)通信的插件。使用该插件,开发者可以轻松地在 Flutter 应用中扫描、连接、读写 BLE 设备。以下是使用 ble_sdk 插件进行蓝牙通信的基本步骤和代码示例。

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 ble_sdk 依赖:

dependencies:
  flutter:
    sdk: flutter
  ble_sdk: ^1.0.0  # 使用最新版本

然后运行 flutter pub get 来安装依赖。

2. 初始化蓝牙

在使用蓝牙功能之前,需要先初始化蓝牙适配器:

import 'package:ble_sdk/ble_sdk.dart';

void initBluetooth() async {
  bool isAvailable = await BleSdk.instance.isAvailable();
  if (!isAvailable) {
    print("Bluetooth is not available on this device.");
    return;
  }

  bool isEnabled = await BleSdk.instance.isEnabled();
  if (!isEnabled) {
    print("Bluetooth is not enabled.");
    return;
  }

  print("Bluetooth is available and enabled.");
}

3. 扫描设备

可以使用 startScan 方法来扫描附近的 BLE 设备:

void scanDevices() async {
  BleSdk.instance.startScan(
    onDeviceFound: (device) {
      print("Found device: ${device.name} - ${device.id}");
    },
    onScanFinished: (devices) {
      print("Scan finished. Total devices found: ${devices.length}");
    },
    onError: (error) {
      print("Scan error: $error");
    },
  );
}

4. 连接设备

扫描到设备后,可以使用 connect 方法来连接设备:

void connectDevice(String deviceId) async {
  await BleSdk.instance.connect(
    deviceId,
    onConnected: (device) {
      print("Connected to device: ${device.name}");
    },
    onDisconnected: (device) {
      print("Disconnected from device: ${device.name}");
    },
    onError: (error) {
      print("Connection error: $error");
    },
  );
}

5. 读取和写入数据

连接设备后,可以读取和写入数据。首先需要发现服务和特征:

void discoverServices(String deviceId) async {
  List<BleService> services = await BleSdk.instance.discoverServices(deviceId);
  for (var service in services) {
    print("Service: ${service.uuid}");
    for (var characteristic in service.characteristics) {
      print("  Characteristic: ${characteristic.uuid}");
    }
  }
}

读取数据:

void readCharacteristic(String deviceId, String serviceUuid, String characteristicUuid) async {
  List<int> value = await BleSdk.instance.readCharacteristic(deviceId, serviceUuid, characteristicUuid);
  print("Read value: $value");
}

写入数据:

void writeCharacteristic(String deviceId, String serviceUuid, String characteristicUuid, List<int> value) async {
  await BleSdk.instance.writeCharacteristic(deviceId, serviceUuid, characteristicUuid, value);
  print("Write value: $value");
}

6. 断开连接

使用 disconnect 方法断开与设备的连接:

void disconnectDevice(String deviceId) async {
  await BleSdk.instance.disconnect(deviceId);
  print("Disconnected from device: $deviceId");
}

7. 停止扫描

扫描完成后,可以使用 stopScan 方法停止扫描:

void stopScanning() {
  BleSdk.instance.stopScan();
  print("Scan stopped.");
}

8. 权限处理

在 Android 和 iOS 上,使用蓝牙功能需要请求相应的权限。确保在 AndroidManifest.xmlInfo.plist 中添加必要的权限配置。

9. 处理蓝牙状态变化

蓝牙状态可能会发生变化(例如用户关闭蓝牙),可以监听这些变化:

void listenToBluetoothState() {
  BleSdk.instance.onStateChanged.listen((state) {
    print("Bluetooth state changed to: $state");
  });
}
回到顶部