Skip to content

Commit

Permalink
add bluetooth
Browse files Browse the repository at this point in the history
  • Loading branch information
b-lack committed Oct 15, 2024
1 parent 4d61bd0 commit 617c1fa
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 11 deletions.
28 changes: 28 additions & 0 deletions android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,32 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- Needed only if your app looks for Bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!-- Needed only if your app makes the device discoverable to Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- Needed only if your app communicates with already-paired Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />

<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
</manifest>
32 changes: 29 additions & 3 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- Needed only if your app looks for Bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!-- Needed only if your app makes the device discoverable to Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- Needed only if your app communicates with already-paired Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />

<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>


<application
android:label="terrestrial_forest_monitor"
android:name="${applicationName}"
Expand Down
12 changes: 12 additions & 0 deletions lib/screens/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:terrestrial_forest_monitor/providers/language.dart';
import 'package:terrestrial_forest_monitor/providers/theme-mode.dart';
import 'package:provider/provider.dart';
import 'package:terrestrial_forest_monitor/widgets/gnss-bluetooth.dart';
import 'package:terrestrial_forest_monitor/widgets/gnss-settings.dart';

class Settings extends StatefulWidget {
Expand Down Expand Up @@ -52,6 +53,17 @@ class _SettingsState extends State<Settings> {
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(
Expand Down
167 changes: 167 additions & 0 deletions lib/widgets/gnss-bluetooth.dart
Original file line number Diff line number Diff line change
@@ -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<GNSSBluetooth> createState() => _GNSSBluetoothState();
}

class _GNSSBluetoothState extends State<GNSSBluetooth> {
//List baudeRates = [9600, 19200, 38400, 57600, 115200];
List baudeRates = [9600, 19200, 38400, 57600, 115200];
int? baudeRate;
List<String> 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<dynamic> _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<String>(
value: selectedPort,
onChanged: (String? value) {
if (value == null) return;
setState(() {
selectedPort = value;
});
},
items: availablePorts.map<DropdownMenuItem<String>>((dynamic value) {
return DropdownMenuItem<String>(
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<int>(
value: baudeRate,
onChanged: (int? value) {
setState(() {
baudeRate = value;
});
},
items: baudeRates.map<DropdownMenuItem<int>>((dynamic value) {
return DropdownMenuItem<int>(
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'),
),
],
)
],
);
}
}
57 changes: 52 additions & 5 deletions lib/widgets/gnss-serial-selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});

Expand All @@ -14,7 +31,7 @@ class _GnssSerialSelectionState extends State<GnssSerialSelection> {
List baudeRates = [9600, 19200, 38400, 57600, 115200];
int? baudeRate;
List availablePorts = [];
late String selectedPort;
String selectedPort = '';
bool tested = false;
bool testing = false;
var serialPort;
Expand All @@ -39,8 +56,19 @@ class _GnssSerialSelectionState extends State<GnssSerialSelection> {
}

_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();
}
Expand All @@ -60,8 +88,24 @@ class _GnssSerialSelectionState extends State<GnssSerialSelection> {
});
}

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<void> _testConnection() async {
_closePort();
if (selectedPort.isEmpty) {
return;
}

setState(() {
testing = true;
Expand All @@ -76,6 +120,7 @@ class _GnssSerialSelectionState extends State<GnssSerialSelection> {

try {
var config = serialPort!.config;
print(baudeRate);
config.baudRate = baudeRate!;
serialPort!.config = config;

Expand All @@ -86,9 +131,10 @@ class _GnssSerialSelectionState extends State<GnssSerialSelection> {
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(() {
Expand All @@ -97,6 +143,7 @@ class _GnssSerialSelectionState extends State<GnssSerialSelection> {
}, onError: (e) {
print('Error: $e');
});
print('read start');
} catch (e) {
print(e);
}
Expand Down
Loading

0 comments on commit 617c1fa

Please sign in to comment.