美文网首页
Flutter开发之蓝牙链接传输数据

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

作者: Tomous | 来源:发表于2024-01-18 11:16 被阅读0次

本文使用的是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代码

CSDN地址

相关文章

  • 打印机

    iOS开发之蓝牙/Socket链接小票打印机(一)iOS开发之蓝牙/Socket链接小票打印机(二) iOS so...

  • flutter_boost

    混合开发要点 flutter engine复用 flutter路由和原生导航同步 flutter和原生数据传输->...

  • 蓝牙4.0/4.1/4.2 BLE协议监控分析仪

    典型应用: - 抓取BLE蓝牙传输数据,分析数据传输协议; - 协助开发调试BLE相关软件,固件; - 实时捕获、...

  • Flutter 开发之蓝牙通信

    本文包含: 蓝牙简介; Flutter 中蓝牙开发步骤; Flutter 插件 flutter_blue 介绍; ...

  • 经典蓝牙数据收发和格式处理

    开发过程中会遇到蓝牙开发,目前蓝牙分两种,一种3.0经典蓝牙,可以大数据传输的,可以传输图片,歌曲和视频的;一种是...

  • 蓝牙开发的细节(数据传输)

    蓝牙数据传输问题 对于蓝牙来说google已经封装好了很多api所以使用起来并不会很难,但是实际开发中蓝牙开发最头...

  • Android 蓝牙开发实践笔记

    本文基于传统蓝牙开发。 先来梳理一下蓝牙开发的逻辑,基本分为以下几步:搜索设备,配对设备,连接设备,传输数据。前三...

  • Flutter 完整 蓝牙通讯 含:搜索,连接,匹配特征值,发送

    Flutter 中蓝牙开发步骤; Flutter 插件 flutter_blue 介绍; Flutter 插件 f...

  • Android 蓝牙设备通讯

    Android 蓝牙设备通讯的开发(配对/连接/传输数据)http://blog.csdn.net/qq_3055...

  • 谷歌官方BLEdemo详解

    ** 安卓开发中或多或少会接触到蓝牙部分,像一些智能家居,蓝牙手环,还有一些串口数据传输的设备都和蓝牙相关,面...

网友评论

      本文标题:Flutter开发之蓝牙链接传输数据

      本文链接:https://www.haomeiwen.com/subject/dnqcodtx.html