123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- import 'dart:io';
- import 'dart:typed_data';
- import 'package:buffer/buffer.dart';
- import 'package:file_picker/file_picker.dart';
- import 'package:flutter/cupertino.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
- import 'package:nrf/app_subscription_state.dart';
- import 'package:nrf/dfu_list.dart';
- import 'package:nrf/scanner.dart';
- import 'package:nrf/test.dart';
- class Connector extends StatefulWidget {
- final DiscoveredDevice device;
- const Connector({Key? key, required this.device}) : super(key: key);
- @override
- State<StatefulWidget> createState() => _State();
- }
- class _State extends State<Connector> with SubscriptionState {
- final flutterReactiveBle = FlutterReactiveBle();
- ConnectionStateUpdate? _connectionStateUpdate;
- Object? _connectError;
- QualifiedCharacteristic? characteristicWrite, characteristicNotify;
- final Map<String, String> _writeData = {};
- final Map<String, String> _readData = {};
- late TextEditingController _textEditingController;
- final ValueNotifier<Map> _infoNotifier = ValueNotifier({});
- final ValueNotifier<Map> _electricityNotifier = ValueNotifier({});
- @override
- void initState() {
- super.initState();
- _textEditingController = TextEditingController();
- _connect();
- }
- _connect() {
- cancel();
- setState(() {
- _writeData.clear();
- _readData.clear();
- _infoNotifier.value = {};
- _electricityNotifier.value = {};
- characteristicWrite = characteristicNotify = null;
- _connectionStateUpdate = null;
- });
- addSubscription(flutterReactiveBle
- .connectToDevice(
- id: widget.device.id,
- connectionTimeout: const Duration(seconds: 5),
- )
- .listen((connectionState) {
- if (connectionState.connectionState == DeviceConnectionState.connected) {
- _discoverService();
- }
- setState(() {
- _connectionStateUpdate = connectionState;
- });
- }, onError: (Object error) {
- setState(() {
- _connectError = error;
- });
- }));
- }
- _discoverService() {
- characteristicWrite = characteristicNotify = null;
- final deviceId = widget.device.id;
- flutterReactiveBle.discoverServices(widget.device.id).then((services) {
- for (DiscoveredService service in services) {
- for (var characteristic in service.characteristics) {
- if (characteristic.isWritableWithoutResponse) {
- characteristicWrite = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId);
- }
- if (characteristic.isNotifiable) {
- characteristicNotify = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId);
- }
- }
- }
- if (characteristicNotify != null) {
- addSubscription(flutterReactiveBle.subscribeToCharacteristic(characteristicNotify!).listen((event) {
- _parse(event);
- })..onError((error){setState(() {
- _connectError = error;
- });}));
- }
- setState(() {});
- }).then((value) {
- if (characteristicWrite != null) {
- Stream.periodic(const Duration(seconds: 1)).take(5).forEach((element) {
- _write(Uint8List.fromList([0xB0, 0x01]));
- });
- _write(Uint8List.fromList([0xA1, 0x00]));
- }
- });
- }
- void _parse(List<int> event) {
- if (!mounted) return;
- if (event.isEmpty == true) return;
- print("parse data $event");
- List<int> header = event.sublist(0, 4);
- List<int> content = event.sublist(4, event.length - 1);
- String headerStr = header.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase();
- String contentStr = content.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase();
- setState(() {
- _readData[headerStr] = headerStr + " " + contentStr;
- });
- switch (event[3]) {
- case 0xA1:
- ByteDataReader reader = ByteDataReader();
- reader.add(content);
- int cmd = reader.readUint8();
- switch (cmd) {
- case 0: // 设备基本信息
- Map<String, dynamic> info = {};
- List<int> name = [];
- for (var i = 0; i < 64; i++) {
- name.add(reader.readUint8());
- }
- info['name'] = String.fromCharCodes(name).replaceAll("\u0000", "");
- List<int> softwareVer = [];
- List<int> hardwareVer = [];
- List<String> device = [];
- for (var i = 0; i < 2; i++) {
- List<String> macList = [];
- for (var j = 0; j < 6; j++) {
- macList.add(reader.readUint8().toRadixString(16).padLeft(2, "0"));
- }
- var mac = macList.join(":").toUpperCase();
- print("mac $i = $mac");
- for (var k = 0; k < 4; k++) {
- hardwareVer.add(reader.readUint8());
- }
- softwareVer.add(reader.readUint16());
- device.add(mac);
- }
- print("bluetooth dfu info $hardwareVer $softwareVer");
- if (hardwareVer.length != 8 || softwareVer.length != 2) return;
- String leftHardware = hardwareVer.sublist(1, 4).join(".");
- String rightHardware = hardwareVer.sublist(4 + 1, hardwareVer.length).join(".");
- int compareHardware = versionCompare(leftHardware, rightHardware);
- String minHardwareVersion = compareHardware < 0 ? leftHardware : rightHardware;
- print("bluetooth hardwareVer $leftHardware $rightHardware $compareHardware $minHardwareVersion");
- String leftSoftware = hardwareVer.sublist(1, 3).join(".") + ".${softwareVer[0]}";
- String rightSoftware = hardwareVer.sublist(4 + 1, 4 + 1 + 2).join(".") + ".${softwareVer[1]}";
- int compareSoftware = versionCompare(leftSoftware, rightSoftware);
- String minSoftwareVersion = compareSoftware < 0 ? leftSoftware : rightSoftware;
- print("bluetooth softwareVer $leftSoftware $rightSoftware $compareSoftware $minSoftwareVersion");
- info['softwareVer'] = minSoftwareVersion;
- info['hardwareVer'] = minHardwareVersion;
- info['mac'] = device.length > 1 ? device.first : "";
- info['device'] = device;
- _infoNotifier.value = info;
- break;
- case 1:
- Map<String, dynamic> info = {};
- for (var i = 0; i < 2; i++) {
- info['${i}_electricity'] = reader.readUint8();
- info['${i}_temperature'] = reader.readUint8();
- info['${i}_pressure'] = reader.readUint(4);
- reader.readUint(4);
- }
- if (reader.remainingLength >= 4) {
- info['0_adc'] = reader.readUint16();
- info['1_adc'] = reader.readUint16();
- }
- _electricityNotifier.value = info;
- break;
- default:
- break;
- }
- break;
- default:
- break;
- }
- }
- static int toInt(dynamic v) {
- if (v == null) return 0;
- if (v is String) {
- try {
- return int.parse(v);
- } catch (e) {
- return 0;
- }
- } else if (v is double) {
- try {
- return v.toInt();
- } catch (e) {
- return 0;
- }
- } else if (v is int) {
- return v;
- } else if (v is num) {
- return v.toInt();
- }
- return 0;
- }
- List<int> version(String versionName) {
- if (versionName.isEmpty == true) return [0, 0, 0];
- var arr = versionName.split(".");
- while (arr.length < 3) arr.add("0");
- return arr.map((e) => toInt(e)).toList();
- }
- int versionCompare(String v1, String v2) {
- if (v1 == v2) return 0;
- var clientVersion = version(v1);
- var baseVersion = version(v2);
- int client = (clientVersion[0] << 20) | (clientVersion[1] << 10) | clientVersion[2];
- int base = (baseVersion[0] << 20) | (baseVersion[1] << 10) | baseVersion[2];
- int result = 0;
- if (client > base) {
- result = 1;
- } else if (client == base) {
- result = 0;
- } else {
- result = -1;
- }
- print("versionCompare $clientVersion $baseVersion == $result");
- return result;
- }
- Future _write(Uint8List data) async {
- if (!mounted) return;
- if (characteristicWrite == null) return;
- if (_connectionStateUpdate?.connectionState != DeviceConnectionState.connected) return;
- int length = data.length + 4;
- ByteDataWriter writer = ByteDataWriter();
- writer.writeUint8(0xAA);
- // writer.writeUint8(0xBB);
- // writer.writeUint8(0xCC);
- writer.writeUint8(length);
- writer.writeUint8(0xFF - length);
- // writer.writeUint8(0x00);
- if (data.isNotEmpty) writer.write(data);
- int ver = writer.toBytes().reduce((value, element) => value + element);
- writer.writeUint8(ver);
- Uint8List out = writer.toBytes();
- await flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite!, value: out);
- setState(() {
- _writeData["${data[0]}"] = out.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase();
- });
- }
- @override
- Widget build(BuildContext context) {
- List<String> readData = _readData.values.toList();
- List<String> writeData = _writeData.values.toList();
- return Scaffold(
- appBar: AppBar(title: Text("${widget.device.name}"), actions: [
- if (_connectionStateUpdate?.connectionState == DeviceConnectionState.disconnected)
- IconButton(
- onPressed: () {
- _connect();
- },
- icon: const Icon(Icons.link))
- ]),
- body: CustomScrollView(
- slivers: [
- SliverToBoxAdapter(
- child: Column(
- children: [
- Container(
- padding: const EdgeInsets.all(8.0),
- child: Text("连接状态: $_connectionStateUpdate, $_connectError"),
- color: _connectionStateUpdate?.connectionState == DeviceConnectionState.connected ? Colors.green : Colors.grey,
- ),
- ValueListenableBuilder<Map>(
- valueListenable: _infoNotifier,
- builder: (_, value, __) => Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text("鞋子信息: $value"),
- ),
- ),
- if (_connectionStateUpdate?.connectionState == DeviceConnectionState.connected)
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: GridView(
- physics: const NeverScrollableScrollPhysics(),
- shrinkWrap: true,
- gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 2, mainAxisSpacing: 4, childAspectRatio: 1.78),
- children: [
- ElevatedButton(
- onPressed: () {
- _discoverService();
- },
- child: const Text("发现服务"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () async {
- clearCache();
- FilePickerResult? result = await FilePicker.platform.pickFiles();
- String? path = result?.files.single.path ?? "";
- if (path.isNotEmpty == true) {
- File file = File(path);
- Set<DiscoveredDevice> selectedResults = {};
- List<String> device = _infoNotifier.value["device"];
- for (var element in device) {
- selectedResults.add(DiscoveredDevice(id: element, name: element, manufacturerData: Uint8List.fromList([]), rssi: 0, serviceUuids: [], serviceData: {}));
- }
- Navigator.of(context).push(MaterialPageRoute(builder: (context) {
- return DFUList(
- selectedResults: selectedResults,
- file: file,
- );
- })).then((value) => _connect());
- } else {
- // User canceled the picker
- }
- },
- child: const Text("DFU"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () {
- ByteDataWriter writer = ByteDataWriter();
- writer.writeUint8(0xAC);
- writer.writeUint16(10000);
- _write(writer.toBytes());
- },
- child: const Text("亮灯-(10s)"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () {
- ByteDataWriter writer = ByteDataWriter();
- writer.writeUint8(0xA4);
- writer.writeUint16(500);
- writer.writeUint8(1);
- _write(writer.toBytes());
- },
- child: const Text("震动(左500ms)"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () {
- ByteDataWriter writer = ByteDataWriter();
- writer.writeUint8(0xA4);
- writer.writeUint16(500);
- writer.writeUint8(2);
- _write(writer.toBytes());
- },
- child: const Text("震动(右500ms)"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () {
- ByteDataWriter writer = ByteDataWriter();
- writer.writeUint8(0xA4);
- writer.writeUint16(500);
- writer.writeUint8(0);
- _write(writer.toBytes());
- },
- child: const Text("震动(500ms)"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () {
- _write(Uint8List.fromList([0xA2, 0x01]));
- },
- child: const Text("游戏模式 - 开"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () {
- _write(Uint8List.fromList([0xA2, 0x00]));
- },
- child: const Text("游戏模式 - 关"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () async {
- if (characteristicNotify != null) {
- _write(Uint8List.fromList([0xA2, 0x02]));
- await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
- await SystemChrome.setPreferredOrientations(Platform.isIOS ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]);
- await Navigator.of(context).push(MaterialPageRoute(builder: (context) {
- return Test(
- device: widget.device,
- characteristicNotify: characteristicNotify!,
- );
- }));
- _write(Uint8List.fromList([0xA2, 0x00]));
- await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
- await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
- }
- },
- child: const Text("硬件数据"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () async {
- while (characteristicNotify != null) {
- ByteDataWriter writer = ByteDataWriter();
- writer.writeUint8(0xAC);
- writer.writeUint16(10000);
- _write(writer.toBytes());
- await Future.delayed(const Duration(seconds: 1));
- writer = ByteDataWriter();
- writer.writeUint8(0xA4);
- writer.writeUint16(500);
- writer.writeUint8(0);
- _write(writer.toBytes());
- await Future.delayed(const Duration(seconds: 1));
- }
- },
- child: const Text("放电"),
- ),
- if (characteristicWrite != null)
- ElevatedButton(
- onPressed: () async {
- _write(Uint8List.fromList([0xA1, 0x01]));
- },
- child: const Text("电量"),
- ),
- ElevatedButton(
- onPressed: () async {
- while (mounted) {
- cancel();
- await Future.delayed(const Duration(seconds: 3));
- _connect();
- await Future.delayed(const Duration(seconds: 5));
- }
- },
- child: const Text("测试10秒重新连接循化"),
- ),ElevatedButton(
- onPressed: () async {
- cancel();
- await Future.delayed(const Duration(seconds: 3));
- _connect();
- },
- child: const Text("重新连接"),
- ),
- ],
- ),
- ),
- if (characteristicWrite != null)
- Container(
- padding: const EdgeInsets.all(12.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Row(
- children: [
- Expanded(
- child: CupertinoTextField(
- cursorColor: const Color(0xffFFC400),
- controller: _textEditingController,
- )),
- ElevatedButton(
- onPressed: () {
- var data = _textEditingController.value.text;
- if (data.isEmpty) return;
- List<int> list = data.split(" ").where((element) => element.isNotEmpty).map((e) => int.parse(e, radix: 16)).toList();
- _write(Uint8List.fromList(list));
- },
- child: const Text("发送"),
- ),
- ],
- ),
- ValueListenableBuilder(
- valueListenable: _electricityNotifier,
- builder: (BuildContext context, value, Widget? child) => Text(
- "电量: $value",
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- if (characteristicWrite != null)
- const SliverToBoxAdapter(
- child: Padding(
- padding: EdgeInsets.all(12.0),
- child: Text("发送历史"),
- ),
- ),
- if (characteristicWrite != null)
- SliverList(
- delegate: SliverChildBuilderDelegate((content, index) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 4.0),
- child: Text("--> ${writeData[index]}"),
- );
- }, childCount: writeData.length),
- ),
- if (characteristicNotify != null)
- const SliverToBoxAdapter(
- child: Padding(
- padding: EdgeInsets.all(12.0),
- child: Text("接收历史"),
- ),
- ),
- if (characteristicWrite != null)
- SliverList(
- delegate: SliverChildBuilderDelegate((content, index) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 4.0),
- child: Text("<-- ${readData[index]}"),
- );
- }, childCount: readData.length),
- ),
- ],
- ),
- );
- }
- }
|