From 617c1faac1034f2d3dbc44d9a6576a5c357f7d1a Mon Sep 17 00:00:00 2001 From: Gerrit Balindt Date: Tue, 15 Oct 2024 15:02:21 +0200 Subject: [PATCH] add bluetooth --- android/app/src/debug/AndroidManifest.xml | 28 ++++ android/app/src/main/AndroidManifest.xml | 32 ++++- lib/screens/settings.dart | 12 ++ lib/widgets/gnss-bluetooth.dart | 167 ++++++++++++++++++++++ lib/widgets/gnss-serial-selection.dart | 57 +++++++- macos/Podfile.lock | 6 + pubspec.lock | 4 +- pubspec.yaml | 2 +- 8 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 lib/widgets/gnss-bluetooth.dart diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 399f698..105029c 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -4,4 +4,32 @@ to allow setting breakpoints, to provide hot reload, etc. --> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 69acf5f..317d423 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,9 +1,35 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + { margin: EdgeInsets.all(10.0), child: GnssSettings(), ), + Container( + margin: EdgeInsets.only(top: 20.0, left: 30.0, right: 30.0), + child: Text( + 'Bluetooth', + style: TextStyle(fontSize: 15), + ), + ), + Card( + margin: EdgeInsets.all(10.0), + child: GNSSBluetooth(), + ), Container( margin: EdgeInsets.only(top: 20.0, left: 30.0, right: 30.0), child: Text( diff --git a/lib/widgets/gnss-bluetooth.dart b/lib/widgets/gnss-bluetooth.dart new file mode 100644 index 0000000..923208b --- /dev/null +++ b/lib/widgets/gnss-bluetooth.dart @@ -0,0 +1,167 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class GNSSBluetooth extends StatefulWidget { + const GNSSBluetooth({super.key}); + + @override + State createState() => _GNSSBluetoothState(); +} + +class _GNSSBluetoothState extends State { + //List baudeRates = [9600, 19200, 38400, 57600, 115200]; + List baudeRates = [9600, 19200, 38400, 57600, 115200]; + int? baudeRate; + List availablePorts = []; + String selectedPort = ''; + bool scanning = false; + bool tested = false; + bool testing = false; + var serialPort; + + _getAvailablePorts() { + return FlutterBluePlus.onScanResults.listen( + (results) { + print(results); + if (results.isNotEmpty) { + ScanResult r = results.last; // the most recently found device + print('${r.device.remoteId}: "${r.advertisementData.advName}" found!'); + availablePorts.add(r.advertisementData.advName); + setState(() { + availablePorts = availablePorts.toSet().toList(); + }); + } + }, + onError: (e) => print(e), + ); + } + + _refreshAvailable() async { + scanning = true; + await FlutterBluePlus.startScan(timeout: Duration(seconds: 4), androidUsesFineLocation: true); + scanning = false; + } + + Future _init() async { + if (await FlutterBluePlus.isSupported == false) { + print("Bluetooth not supported by this device"); + return; + } + + var status = await Permission.bluetoothScan.status; + if (status.isDenied) { + print('denied'); + } else { + print('Permitted'); + } + + // handle bluetooth on & off + // note: for iOS the initial state is typically BluetoothAdapterState.unknown + // note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized + var subscription = FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) async { + print('BluetoothAdapterState'); + print(state); + if (state == BluetoothAdapterState.on) { + // usually start scanning, connecting, etc + _getAvailablePorts(); + + print('start Scanning'); + } else { + // show an error to the user, etc + print('ERROR'); + } + }); + print('START _INiT'); + return; + + // turn on bluetooth ourself if we can + // for iOS, the user controls bluetooth enable/disable + if (Platform.isAndroid) { + await FlutterBluePlus.turnOn(); + } + + // cancel to prevent duplicate listeners + subscription.cancel(); + } + + @override + void initState() { + // TODO: implement initState + super.initState(); + _init(); + } + + _testConnection() {} + _cancelTesting() {} + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ListTile( + title: Text('Serial Ports:'), + subtitle: Text('Get Information from Device...'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + DropdownButton( + value: selectedPort, + onChanged: (String? value) { + if (value == null) return; + setState(() { + selectedPort = value; + }); + }, + items: availablePorts.map>((dynamic value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), // Explicitly specify the type + ), + IconButton( + icon: Icon(Icons.refresh), + onPressed: _refreshAvailable, + ), + ], + )), + ListTile( + title: Text('Baud Rate: '), + subtitle: Text('Get Information from Device...'), + trailing: DropdownButton( + value: baudeRate, + onChanged: (int? value) { + setState(() { + baudeRate = value; + }); + }, + items: baudeRates.map>((dynamic value) { + return DropdownMenuItem( + value: value, + child: Text(value.toString()), + ); + }).toList(), // Explicitly specify the type + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (testing) + ElevatedButton( + onPressed: _cancelTesting, + child: Text('Cancel testing'), + ) + else + ElevatedButton( + onPressed: baudeRate != null ? _testConnection : null, + child: Text('TEST connection'), + ), + ], + ) + ], + ); + } +} diff --git a/lib/widgets/gnss-serial-selection.dart b/lib/widgets/gnss-serial-selection.dart index bce9a1c..181f4e3 100644 --- a/lib/widgets/gnss-serial-selection.dart +++ b/lib/widgets/gnss-serial-selection.dart @@ -2,6 +2,23 @@ import 'package:flutter/material.dart'; import 'package:terrestrial_forest_monitor/polyfill/libserial.dart' if (dart.library.html) 'package:terrestrial_forest_monitor/polyfill/libserial.dart' if (dart.library.io) 'package:flutter_libserialport/flutter_libserialport.dart'; //import 'package:flutter_libserialport/flutter_libserialport.dart'; +extension IntToString on int { + String toHex() => '0x${toRadixString(16)}'; + String toPadded([int width = 3]) => toString().padLeft(width, '0'); + String toTransport() { + switch (this) { + case SerialPortTransport.usb: + return 'USB'; + case SerialPortTransport.bluetooth: + return 'Bluetooth'; + case SerialPortTransport.native: + return 'Native'; + default: + return 'Unknown'; + } + } +} + class GnssSerialSelection extends StatefulWidget { const GnssSerialSelection({super.key}); @@ -14,7 +31,7 @@ class _GnssSerialSelectionState extends State { List baudeRates = [9600, 19200, 38400, 57600, 115200]; int? baudeRate; List availablePorts = []; - late String selectedPort; + String selectedPort = ''; bool tested = false; bool testing = false; var serialPort; @@ -39,8 +56,19 @@ class _GnssSerialSelectionState extends State { } _getAvailablePorts() { - setState(() { + try { availablePorts = SerialPort.availablePorts; + } catch (e) { + if (e is SerialPortError) { + // TODO: Show error + print('${e}'); + // Handle the error, e.g., show a dialog or a snackbar + } else { + print('Error: $e'); + } + } + setState(() { + print(availablePorts); if (availablePorts.isNotEmpty) { selectedPort = availablePorts[0].toString(); } @@ -60,8 +88,24 @@ class _GnssSerialSelectionState extends State { }); } + void parseReceiveData(String nmeaData) { + double latitude; + double longitude; + + var nmeaDataArray = nmeaData.split(","); + + if (nmeaDataArray[0].startsWith('\$GNRMC') && nmeaDataArray[3].isNotEmpty && nmeaDataArray[5].isNotEmpty) { + print('parse'); + //latitude = parseToDecimal(nmeaDataArray[3], nmeaDataArray[4]); + //longitude = parseToDecimal(nmeaDataArray[5], nmeaDataArray[6]); + } + } + Future _testConnection() async { _closePort(); + if (selectedPort.isEmpty) { + return; + } setState(() { testing = true; @@ -76,6 +120,7 @@ class _GnssSerialSelectionState extends State { try { var config = serialPort!.config; + print(baudeRate); config.baudRate = baudeRate!; serialPort!.config = config; @@ -86,9 +131,10 @@ class _GnssSerialSelectionState extends State { print('Port not opened'); } var reader = SerialPortReader(serialPort!); - - reader.stream.listen((data) { - print('Data: $data'); + reader!.stream.listen((data) { + //print('Data: $data'); + String utfDAta = String.fromCharCodes(data); + if (utfDAta.startsWith('\$GNRMC')) parseReceiveData(utfDAta); }, onDone: () { print('Done'); setState(() { @@ -97,6 +143,7 @@ class _GnssSerialSelectionState extends State { }, onError: (e) { print('Error: $e'); }); + print('read start'); } catch (e) { print(e); } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index a3445d5..7b0d8ac 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -11,6 +11,8 @@ PODS: - geolocator_apple (1.2.0): - FlutterMacOS - libserialport (0.1.1) + - package_info_plus (0.0.1): + - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -21,6 +23,7 @@ DEPENDENCIES: - flutter_libserialport (from `Flutter/ephemeral/.symlinks/plugins/flutter_libserialport/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) SPEC REPOS: @@ -38,6 +41,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral geolocator_apple: :path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin @@ -48,6 +53,7 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 geolocator_apple: 72a78ae3f3e4ec0db62117bd93e34523f5011d58 libserialport: 1cb25e66ef3c92a8e59c2ea3820302c3fa2268cd + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 diff --git a/pubspec.lock b/pubspec.lock index 326e811..7f11659 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -330,10 +330,10 @@ packages: dependency: "direct main" description: name: flutter_blue_plus - sha256: "55d37e9339765fef9439f3759a2bddaf9c8db1f90d46ba5a5d834fb28e0ab809" + sha256: ddbed8d86199ab4342152b2f5ce9a7ea8b348219f6880da3e7899f0a73d2dae3 url: "https://pub.dev" source: hosted - version: "1.32.12" + version: "1.33.4" flutter_compass: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0de2881..2de60c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: flutter_map: ^7.0.2 latlong2: ^0.9.1 flutter_map_cancellable_tile_provider: ^3.0.0 - flutter_blue_plus: ^1.32.12 + flutter_blue_plus: ^1.33.4 url_strategy: ^0.3.0 provider: ^6.1.2 flutter_svg: ^2.0.10+1