connector.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. import 'dart:io';
  2. import 'dart:typed_data';
  3. import 'package:buffer/buffer.dart';
  4. import 'package:file_picker/file_picker.dart';
  5. import 'package:flutter/cupertino.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/services.dart';
  8. import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
  9. import 'package:nrf/app_subscription_state.dart';
  10. import 'package:nrf/dfu_list.dart';
  11. import 'package:nrf/scanner.dart';
  12. import 'package:nrf/test.dart';
  13. class Connector extends StatefulWidget {
  14. final DiscoveredDevice device;
  15. const Connector({Key? key, required this.device}) : super(key: key);
  16. @override
  17. State<StatefulWidget> createState() => _State();
  18. }
  19. class _State extends State<Connector> with SubscriptionState {
  20. final flutterReactiveBle = FlutterReactiveBle();
  21. ConnectionStateUpdate? _connectionStateUpdate;
  22. Object? _connectError;
  23. QualifiedCharacteristic? characteristicWrite, characteristicNotify;
  24. final Map<String, String> _writeData = {};
  25. final Map<String, String> _readData = {};
  26. late TextEditingController _textEditingController;
  27. final ValueNotifier<Map> _infoNotifier = ValueNotifier({});
  28. final ValueNotifier<Map> _electricityNotifier = ValueNotifier({});
  29. @override
  30. void initState() {
  31. super.initState();
  32. _textEditingController = TextEditingController();
  33. _connect();
  34. }
  35. _connect() {
  36. cancel();
  37. setState(() {
  38. _writeData.clear();
  39. _readData.clear();
  40. _infoNotifier.value = {};
  41. _electricityNotifier.value = {};
  42. characteristicWrite = characteristicNotify = null;
  43. _connectionStateUpdate = null;
  44. });
  45. addSubscription(flutterReactiveBle
  46. .connectToDevice(
  47. id: widget.device.id,
  48. connectionTimeout: const Duration(seconds: 5),
  49. )
  50. .listen((connectionState) {
  51. if (connectionState.connectionState == DeviceConnectionState.connected) {
  52. _discoverService();
  53. }
  54. setState(() {
  55. _connectionStateUpdate = connectionState;
  56. });
  57. }, onError: (Object error) {
  58. setState(() {
  59. _connectError = error;
  60. });
  61. }));
  62. }
  63. _discoverService() {
  64. characteristicWrite = characteristicNotify = null;
  65. final deviceId = widget.device.id;
  66. flutterReactiveBle.discoverServices(widget.device.id).then((services) {
  67. for (DiscoveredService service in services) {
  68. for (var characteristic in service.characteristics) {
  69. if (characteristic.isWritableWithoutResponse) {
  70. characteristicWrite = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId);
  71. }
  72. if (characteristic.isNotifiable) {
  73. characteristicNotify = QualifiedCharacteristic(serviceId: service.serviceId, characteristicId: characteristic.characteristicId, deviceId: deviceId);
  74. }
  75. }
  76. }
  77. if (characteristicNotify != null) {
  78. addSubscription(flutterReactiveBle.subscribeToCharacteristic(characteristicNotify!).listen((event) {
  79. _parse(event);
  80. })..onError((error){setState(() {
  81. _connectError = error;
  82. });}));
  83. }
  84. setState(() {});
  85. }).then((value) {
  86. if (characteristicWrite != null) {
  87. Stream.periodic(const Duration(seconds: 1)).take(5).forEach((element) {
  88. _write(Uint8List.fromList([0xB0, 0x01]));
  89. });
  90. _write(Uint8List.fromList([0xA1, 0x00]));
  91. }
  92. });
  93. }
  94. void _parse(List<int> event) {
  95. if (!mounted) return;
  96. if (event.isEmpty == true) return;
  97. print("parse data $event");
  98. List<int> header = event.sublist(0, 4);
  99. List<int> content = event.sublist(4, event.length - 1);
  100. String headerStr = header.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase();
  101. String contentStr = content.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase();
  102. setState(() {
  103. _readData[headerStr] = headerStr + " " + contentStr;
  104. });
  105. switch (event[3]) {
  106. case 0xA1:
  107. ByteDataReader reader = ByteDataReader();
  108. reader.add(content);
  109. int cmd = reader.readUint8();
  110. switch (cmd) {
  111. case 0: // 设备基本信息
  112. Map<String, dynamic> info = {};
  113. List<int> name = [];
  114. for (var i = 0; i < 64; i++) {
  115. name.add(reader.readUint8());
  116. }
  117. info['name'] = String.fromCharCodes(name).replaceAll("\u0000", "");
  118. List<int> softwareVer = [];
  119. List<int> hardwareVer = [];
  120. List<String> device = [];
  121. for (var i = 0; i < 2; i++) {
  122. List<String> macList = [];
  123. for (var j = 0; j < 6; j++) {
  124. macList.add(reader.readUint8().toRadixString(16).padLeft(2, "0"));
  125. }
  126. var mac = macList.join(":").toUpperCase();
  127. print("mac $i = $mac");
  128. for (var k = 0; k < 4; k++) {
  129. hardwareVer.add(reader.readUint8());
  130. }
  131. softwareVer.add(reader.readUint16());
  132. device.add(mac);
  133. }
  134. print("bluetooth dfu info $hardwareVer $softwareVer");
  135. if (hardwareVer.length != 8 || softwareVer.length != 2) return;
  136. String leftHardware = hardwareVer.sublist(1, 4).join(".");
  137. String rightHardware = hardwareVer.sublist(4 + 1, hardwareVer.length).join(".");
  138. int compareHardware = versionCompare(leftHardware, rightHardware);
  139. String minHardwareVersion = compareHardware < 0 ? leftHardware : rightHardware;
  140. print("bluetooth hardwareVer $leftHardware $rightHardware $compareHardware $minHardwareVersion");
  141. String leftSoftware = hardwareVer.sublist(1, 3).join(".") + ".${softwareVer[0]}";
  142. String rightSoftware = hardwareVer.sublist(4 + 1, 4 + 1 + 2).join(".") + ".${softwareVer[1]}";
  143. int compareSoftware = versionCompare(leftSoftware, rightSoftware);
  144. String minSoftwareVersion = compareSoftware < 0 ? leftSoftware : rightSoftware;
  145. print("bluetooth softwareVer $leftSoftware $rightSoftware $compareSoftware $minSoftwareVersion");
  146. info['softwareVer'] = minSoftwareVersion;
  147. info['hardwareVer'] = minHardwareVersion;
  148. info['mac'] = device.length > 1 ? device.first : "";
  149. info['device'] = device;
  150. _infoNotifier.value = info;
  151. break;
  152. case 1:
  153. Map<String, dynamic> info = {};
  154. for (var i = 0; i < 2; i++) {
  155. info['${i}_electricity'] = reader.readUint8();
  156. info['${i}_temperature'] = reader.readUint8();
  157. info['${i}_pressure'] = reader.readUint(4);
  158. reader.readUint(4);
  159. }
  160. if (reader.remainingLength >= 4) {
  161. info['0_adc'] = reader.readUint16();
  162. info['1_adc'] = reader.readUint16();
  163. }
  164. _electricityNotifier.value = info;
  165. break;
  166. default:
  167. break;
  168. }
  169. break;
  170. default:
  171. break;
  172. }
  173. }
  174. static int toInt(dynamic v) {
  175. if (v == null) return 0;
  176. if (v is String) {
  177. try {
  178. return int.parse(v);
  179. } catch (e) {
  180. return 0;
  181. }
  182. } else if (v is double) {
  183. try {
  184. return v.toInt();
  185. } catch (e) {
  186. return 0;
  187. }
  188. } else if (v is int) {
  189. return v;
  190. } else if (v is num) {
  191. return v.toInt();
  192. }
  193. return 0;
  194. }
  195. List<int> version(String versionName) {
  196. if (versionName.isEmpty == true) return [0, 0, 0];
  197. var arr = versionName.split(".");
  198. while (arr.length < 3) arr.add("0");
  199. return arr.map((e) => toInt(e)).toList();
  200. }
  201. int versionCompare(String v1, String v2) {
  202. if (v1 == v2) return 0;
  203. var clientVersion = version(v1);
  204. var baseVersion = version(v2);
  205. int client = (clientVersion[0] << 20) | (clientVersion[1] << 10) | clientVersion[2];
  206. int base = (baseVersion[0] << 20) | (baseVersion[1] << 10) | baseVersion[2];
  207. int result = 0;
  208. if (client > base) {
  209. result = 1;
  210. } else if (client == base) {
  211. result = 0;
  212. } else {
  213. result = -1;
  214. }
  215. print("versionCompare $clientVersion $baseVersion == $result");
  216. return result;
  217. }
  218. Future _write(Uint8List data) async {
  219. if (!mounted) return;
  220. if (characteristicWrite == null) return;
  221. if (_connectionStateUpdate?.connectionState != DeviceConnectionState.connected) return;
  222. int length = data.length + 4;
  223. ByteDataWriter writer = ByteDataWriter();
  224. writer.writeUint8(0xAA);
  225. // writer.writeUint8(0xBB);
  226. // writer.writeUint8(0xCC);
  227. writer.writeUint8(length);
  228. writer.writeUint8(0xFF - length);
  229. // writer.writeUint8(0x00);
  230. if (data.isNotEmpty) writer.write(data);
  231. int ver = writer.toBytes().reduce((value, element) => value + element);
  232. writer.writeUint8(ver);
  233. Uint8List out = writer.toBytes();
  234. await flutterReactiveBle.writeCharacteristicWithoutResponse(characteristicWrite!, value: out);
  235. setState(() {
  236. _writeData["${data[0]}"] = out.map((e) => e.toRadixString(16).padLeft(2, "0")).join(" ").toUpperCase();
  237. });
  238. }
  239. @override
  240. Widget build(BuildContext context) {
  241. List<String> readData = _readData.values.toList();
  242. List<String> writeData = _writeData.values.toList();
  243. return Scaffold(
  244. appBar: AppBar(title: Text("${widget.device.name}"), actions: [
  245. if (_connectionStateUpdate?.connectionState == DeviceConnectionState.disconnected)
  246. IconButton(
  247. onPressed: () {
  248. _connect();
  249. },
  250. icon: const Icon(Icons.link))
  251. ]),
  252. body: CustomScrollView(
  253. slivers: [
  254. SliverToBoxAdapter(
  255. child: Column(
  256. children: [
  257. Container(
  258. padding: const EdgeInsets.all(8.0),
  259. child: Text("连接状态: $_connectionStateUpdate, $_connectError"),
  260. color: _connectionStateUpdate?.connectionState == DeviceConnectionState.connected ? Colors.green : Colors.grey,
  261. ),
  262. ValueListenableBuilder<Map>(
  263. valueListenable: _infoNotifier,
  264. builder: (_, value, __) => Padding(
  265. padding: const EdgeInsets.all(8.0),
  266. child: Text("鞋子信息: $value"),
  267. ),
  268. ),
  269. if (_connectionStateUpdate?.connectionState == DeviceConnectionState.connected)
  270. Padding(
  271. padding: const EdgeInsets.all(8.0),
  272. child: GridView(
  273. physics: const NeverScrollableScrollPhysics(),
  274. shrinkWrap: true,
  275. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 2, mainAxisSpacing: 4, childAspectRatio: 1.78),
  276. children: [
  277. ElevatedButton(
  278. onPressed: () {
  279. _discoverService();
  280. },
  281. child: const Text("发现服务"),
  282. ),
  283. if (characteristicWrite != null)
  284. ElevatedButton(
  285. onPressed: () async {
  286. clearCache();
  287. FilePickerResult? result = await FilePicker.platform.pickFiles();
  288. String? path = result?.files.single.path ?? "";
  289. if (path.isNotEmpty == true) {
  290. File file = File(path);
  291. Set<DiscoveredDevice> selectedResults = {};
  292. List<String> device = _infoNotifier.value["device"];
  293. for (var element in device) {
  294. selectedResults.add(DiscoveredDevice(id: element, name: element, manufacturerData: Uint8List.fromList([]), rssi: 0, serviceUuids: [], serviceData: {}));
  295. }
  296. Navigator.of(context).push(MaterialPageRoute(builder: (context) {
  297. return DFUList(
  298. selectedResults: selectedResults,
  299. file: file,
  300. );
  301. })).then((value) => _connect());
  302. } else {
  303. // User canceled the picker
  304. }
  305. },
  306. child: const Text("DFU"),
  307. ),
  308. if (characteristicWrite != null)
  309. ElevatedButton(
  310. onPressed: () {
  311. ByteDataWriter writer = ByteDataWriter();
  312. writer.writeUint8(0xAC);
  313. writer.writeUint16(10000);
  314. _write(writer.toBytes());
  315. },
  316. child: const Text("亮灯-(10s)"),
  317. ),
  318. if (characteristicWrite != null)
  319. ElevatedButton(
  320. onPressed: () {
  321. ByteDataWriter writer = ByteDataWriter();
  322. writer.writeUint8(0xA4);
  323. writer.writeUint16(500);
  324. writer.writeUint8(1);
  325. _write(writer.toBytes());
  326. },
  327. child: const Text("震动(左500ms)"),
  328. ),
  329. if (characteristicWrite != null)
  330. ElevatedButton(
  331. onPressed: () {
  332. ByteDataWriter writer = ByteDataWriter();
  333. writer.writeUint8(0xA4);
  334. writer.writeUint16(500);
  335. writer.writeUint8(2);
  336. _write(writer.toBytes());
  337. },
  338. child: const Text("震动(右500ms)"),
  339. ),
  340. if (characteristicWrite != null)
  341. ElevatedButton(
  342. onPressed: () {
  343. ByteDataWriter writer = ByteDataWriter();
  344. writer.writeUint8(0xA4);
  345. writer.writeUint16(500);
  346. writer.writeUint8(0);
  347. _write(writer.toBytes());
  348. },
  349. child: const Text("震动(500ms)"),
  350. ),
  351. if (characteristicWrite != null)
  352. ElevatedButton(
  353. onPressed: () {
  354. _write(Uint8List.fromList([0xA2, 0x01]));
  355. },
  356. child: const Text("游戏模式 - 开"),
  357. ),
  358. if (characteristicWrite != null)
  359. ElevatedButton(
  360. onPressed: () {
  361. _write(Uint8List.fromList([0xA2, 0x00]));
  362. },
  363. child: const Text("游戏模式 - 关"),
  364. ),
  365. if (characteristicWrite != null)
  366. ElevatedButton(
  367. onPressed: () async {
  368. if (characteristicNotify != null) {
  369. _write(Uint8List.fromList([0xA2, 0x02]));
  370. await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
  371. await SystemChrome.setPreferredOrientations(Platform.isIOS ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]);
  372. await Navigator.of(context).push(MaterialPageRoute(builder: (context) {
  373. return Test(
  374. device: widget.device,
  375. characteristicNotify: characteristicNotify!,
  376. );
  377. }));
  378. _write(Uint8List.fromList([0xA2, 0x00]));
  379. await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
  380. await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  381. }
  382. },
  383. child: const Text("硬件数据"),
  384. ),
  385. if (characteristicWrite != null)
  386. ElevatedButton(
  387. onPressed: () async {
  388. while (characteristicNotify != null) {
  389. ByteDataWriter writer = ByteDataWriter();
  390. writer.writeUint8(0xAC);
  391. writer.writeUint16(10000);
  392. _write(writer.toBytes());
  393. await Future.delayed(const Duration(seconds: 1));
  394. writer = ByteDataWriter();
  395. writer.writeUint8(0xA4);
  396. writer.writeUint16(500);
  397. writer.writeUint8(0);
  398. _write(writer.toBytes());
  399. await Future.delayed(const Duration(seconds: 1));
  400. }
  401. },
  402. child: const Text("放电"),
  403. ),
  404. if (characteristicWrite != null)
  405. ElevatedButton(
  406. onPressed: () async {
  407. _write(Uint8List.fromList([0xA1, 0x01]));
  408. },
  409. child: const Text("电量"),
  410. ),
  411. ElevatedButton(
  412. onPressed: () async {
  413. while (mounted) {
  414. cancel();
  415. await Future.delayed(const Duration(seconds: 3));
  416. _connect();
  417. await Future.delayed(const Duration(seconds: 5));
  418. }
  419. },
  420. child: const Text("测试10秒重新连接循化"),
  421. ),ElevatedButton(
  422. onPressed: () async {
  423. cancel();
  424. await Future.delayed(const Duration(seconds: 3));
  425. _connect();
  426. },
  427. child: const Text("重新连接"),
  428. ),
  429. ],
  430. ),
  431. ),
  432. if (characteristicWrite != null)
  433. Container(
  434. padding: const EdgeInsets.all(12.0),
  435. child: Column(
  436. mainAxisSize: MainAxisSize.min,
  437. children: [
  438. Row(
  439. children: [
  440. Expanded(
  441. child: CupertinoTextField(
  442. cursorColor: const Color(0xffFFC400),
  443. controller: _textEditingController,
  444. )),
  445. ElevatedButton(
  446. onPressed: () {
  447. var data = _textEditingController.value.text;
  448. if (data.isEmpty) return;
  449. List<int> list = data.split(" ").where((element) => element.isNotEmpty).map((e) => int.parse(e, radix: 16)).toList();
  450. _write(Uint8List.fromList(list));
  451. },
  452. child: const Text("发送"),
  453. ),
  454. ],
  455. ),
  456. ValueListenableBuilder(
  457. valueListenable: _electricityNotifier,
  458. builder: (BuildContext context, value, Widget? child) => Text(
  459. "电量: $value",
  460. ),
  461. ),
  462. ],
  463. ),
  464. ),
  465. ],
  466. ),
  467. ),
  468. if (characteristicWrite != null)
  469. const SliverToBoxAdapter(
  470. child: Padding(
  471. padding: EdgeInsets.all(12.0),
  472. child: Text("发送历史"),
  473. ),
  474. ),
  475. if (characteristicWrite != null)
  476. SliverList(
  477. delegate: SliverChildBuilderDelegate((content, index) {
  478. return Padding(
  479. padding: const EdgeInsets.symmetric(vertical: 4.0),
  480. child: Text("--> ${writeData[index]}"),
  481. );
  482. }, childCount: writeData.length),
  483. ),
  484. if (characteristicNotify != null)
  485. const SliverToBoxAdapter(
  486. child: Padding(
  487. padding: EdgeInsets.all(12.0),
  488. child: Text("接收历史"),
  489. ),
  490. ),
  491. if (characteristicWrite != null)
  492. SliverList(
  493. delegate: SliverChildBuilderDelegate((content, index) {
  494. return Padding(
  495. padding: const EdgeInsets.symmetric(vertical: 4.0),
  496. child: Text("<-- ${readData[index]}"),
  497. );
  498. }, childCount: readData.length),
  499. ),
  500. ],
  501. ),
  502. );
  503. }
  504. }