semesterprojekt-bluetooth-p.../lib/screens/bluetooth_screen.dart

582 lines
19 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:flutter/material.dart';
import 'package:flutter_provisioning_for_iot/objects/cloud_service_api.dart';
import 'package:flutter_provisioning_for_iot/screens/bluetooth_device_list_entry.dart';
import 'package:app_settings/app_settings.dart';
import 'package:permission_handler/permission_handler.dart';
class BluetoothScreen extends StatefulWidget {
final bool start = true;
const BluetoothScreen({super.key});
@override
State<BluetoothScreen> createState() => _BluetoothScreen();
}
// debugPrint("r: ${r.device.name} ${r.device.address} ${r.rssi}");
class _BluetoothScreen extends State<BluetoothScreen> {
CloudServiceAPI cloudServiceAPI = CloudServiceAPI();
//BluetoothManager bluetoothManager = BluetoothManager();
late Stream<BluetoothDiscoveryResult> _stream;
late StreamSubscription<BluetoothDiscoveryResult> _streamSubscription;
late Stream<Uint8List> _connectionStream;
late StreamSubscription<Uint8List> _connectionStreamSubscription;
BluetoothDevice? _bluetoothDevice;
BluetoothConnection? _bluetoothConnection;
final List<BluetoothDiscoveryResult> _discoveryResults =
List<BluetoothDiscoveryResult>.empty(growable: true);
String _messageBuffer = "";
bool isDiscovering = false;
bool isConnecting = false;
bool isDisconnecting = false;
bool isConnected = false;
String textInput = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String textOuput = "";
ButtonStyle buttonStyle = ElevatedButton.styleFrom(
foregroundColor: Colors.black,
backgroundColor: const Color(0xFFFDE100), // Text Color (Foreground color)
);
@override
void initState() {
super.initState();
isDiscovering = widget.start;
if (isDiscovering) {
_startDiscovery();
}
}
@override
void dispose() {
// Avoid memory leak (`setState` after dispose) and cancel discovery
_streamSubscription.cancel();
if (isConnected) {
isDisconnecting = true;
_bluetoothConnection?.dispose();
_bluetoothConnection = null;
}
super.dispose();
}
Future<void> _disconnectDevice(BluetoothDevice device) async {
if (_bluetoothConnection != null) {
await _bluetoothConnection!.finish();
_bluetoothConnection = null;
}
}
Future<void> _connectDevice(BluetoothDevice device) async {
String address = device.address;
if (_bluetoothConnection != null) {
await _bluetoothConnection!.finish();
_bluetoothConnection = null;
}
_bluetoothConnection = await BluetoothConnection.toAddress(address);
debugPrint("Connected to the device");
setState(() {
isConnecting = false;
isDisconnecting = false;
});
_connectionStream = _bluetoothConnection!.input!;
_connectionStreamSubscription = _connectionStream.listen(_connectionOnListen);
_connectionStreamSubscription.onDone(_connectionOnDone);
}
void _connectionOnDone() {
/*
// Example: Detect which side closed the connection
// There should be `isDisconnecting` flag to show are we are (locally)
// in middle of disconnecting process, should be set before calling
// `dispose`, `finish` or `close`, which all causes to disconnect.
// If we except the disconnection, `onDone` should be fired as result.
// If we didn't except this (no flag set), it means closing by remote.
*/
if (isDisconnecting) {
debugPrint("Disconnecting locally!");
} else {
debugPrint("Disconnected remotely!");
}
if (mounted) {
setState(() {});
}
}
void _connectionOnListen(Uint8List data) {
debugPrint("received: $data");
debugPrint("received decoded: ${const Utf8Decoder().convert(data)}");
setState(() {
textOuput += const Utf8Decoder().convert(data);
});
// Allocate buffer for parsed data
int backspacesCounter = 0;
for (var byte in data) {
if (byte == 8 || byte == 127) {
backspacesCounter++;
}
}
Uint8List buffer = Uint8List(data.length - backspacesCounter);
int bufferIndex = buffer.length;
// Apply backspace control character
backspacesCounter = 0;
for (int i = data.length - 1; i >= 0; i--) {
if (data[i] == 8 || data[i] == 127) {
backspacesCounter++;
} else {
if (backspacesCounter > 0) {
backspacesCounter--;
} else {
buffer[--bufferIndex] = data[i];
}
}
}
// Create message if there is new line character
String dataString = String.fromCharCodes(buffer);
int index = buffer.indexOf(13);
if (~index != 0) {
setState(() {
_messageBuffer = dataString.substring(index);
});
} else {
_messageBuffer = (backspacesCounter > 0
? _messageBuffer.substring(
0, _messageBuffer.length - backspacesCounter)
: _messageBuffer + dataString);
}
}
Future<void> _startDiscovery() async {
_stream = FlutterBluetoothSerial.instance.startDiscovery();
_streamSubscription = _stream.listen(_discoveryOnListen);
_streamSubscription.onDone(_discoveryOnDone);
}
void _discoveryOnDone() {
setState(() {
isDiscovering = false;
//debugPrint(isDiscovering as String?);
});
}
void _discoveryOnListen(BluetoothDiscoveryResult event) {
setState(() {
final int existingIndex = _discoveryResults.indexWhere(
(element) => element.device.address == event.device.address);
if (existingIndex >= 0) {
_discoveryResults[existingIndex] = event;
} else {
_discoveryResults.add(event);
}
debugPrint("event: ${event.device.address} ${event.device.name}");
String deviceAddress = "64:BC:58:61:56:B0";
if (event.device.address == deviceAddress) {
_bluetoothDevice = event.device;
}
});
}
Future<void> _restartDiscovery() async {
setState(() {
_discoveryResults.clear();
isDiscovering = true;
});
_startDiscovery();
}
Future<void> _sendData() async {
//String output = "0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ";// !§%&()=?#!?";
String output = textInput;
_bluetoothConnection!.output.add(Uint8List.fromList(const AsciiEncoder().convert("$output \r\n")));
await _bluetoothConnection!.output.allSent;
debugPrint("sent: $output");
}
@override
Widget build(BuildContext context) {
//bool isDark = true;
return Theme(
data: ThemeData.dark(), //isDark ? ThemeData.dark() : ThemeData.light(),
child: Scaffold(
appBar: AppBar(
title: isDiscovering
? const Text('Bluetooth Devices (searching...)')
: const Text('Bluetooth Devices'),
actions: <Widget>[
isDiscovering
? FittedBox(
child: Container(
margin: const EdgeInsets.all(16.0),
child: const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
)
: IconButton(
icon: const Icon(Icons.replay),
onPressed: _restartDiscovery,
)
],
),
body: // Center(
/*child:*/ SingleChildScrollView(
physics: const ScrollPhysics(),
child: Column(
children: <Widget>[
_SingleSection(
title: "Setup",
children: [
const _CustomListTile(
title: "Please Enable Bluetooth",
icon: Icons.info_outline_rounded,
),
_CustomListTile(
title: "Bluetooth Settings",
icon: Icons.bluetooth_connected,
onTap: () async {
await AppSettings.openBluetoothSettings();
},
),
],
),
const Divider(),
Text(textOuput),
TextFormField(
decoration: const InputDecoration(
labelText: "BluetoothText"
),
initialValue: textInput,
keyboardType: TextInputType.text,
onChanged: (String newValue) {
textInput = newValue;
},
),
ElevatedButton(
onPressed: () async {
await Permission.bluetoothConnect.request();
await Permission.bluetoothScan.request();
if (await Permission.bluetoothScan.request().isGranted) {
// Either the permission was already granted before or the user just granted it.
debugPrint("Location Permission is granted");
} else {
debugPrint("Location Permission is denied.");
}
},
style: buttonStyle,
child: const Text("Request Permissions"),
),
ElevatedButton(
onPressed: () async {
setState(() {
textOuput = "";
});
},
style: buttonStyle,
child: const Text("Clear Output"),
),
ElevatedButton(
onPressed: () async {
_restartDiscovery();
},
style: buttonStyle,
child: const Text("Restart Scan"),
),
ElevatedButton(
onPressed: () async {
_sendData();
},
style: buttonStyle,
child: const Text("Send Data"),
),
const Divider(),
const _SingleSection(
title: "Bluetooth Devices",
children: [],
),
const Divider(),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _discoveryResults.length,
itemBuilder: (BuildContext context, index) {
BluetoothDiscoveryResult result = _discoveryResults[index];
final device = result.device;
final address = device.address;
return BluetoothDeviceListEntry(
device: device,
rssi: result.rssi,
onTap: () {
debugPrint(_bluetoothConnection?.isConnected.toString());
if (_bluetoothConnection?.isConnected ?? false) {
_disconnectDevice(device);
} else {
_connectDevice(device);
}
//Navigator.of(context).pop(result.device);
},
onLongPress: () async {
try {
bool bonded = false;
if (device.isBonded) {
debugPrint('Unbonding from ${device.address}...');
await FlutterBluetoothSerial.instance
.removeDeviceBondWithAddress(address);
debugPrint(
'Unbonding from ${device.address} has succed');
} else {
debugPrint('Bonding with ${device.address}...');
bonded = (await FlutterBluetoothSerial.instance
.bondDeviceAtAddress(address))!;
debugPrint(
'Bonding with ${device.address} has ${bonded ? 'succed' : 'failed'}.');
}
setState(() {
_discoveryResults[_discoveryResults.indexOf(result)] =
BluetoothDiscoveryResult(
device: BluetoothDevice(
name: device.name ?? '',
address: address,
type: device.type,
bondState: bonded
? BluetoothBondState.bonded
: BluetoothBondState.none,
),
rssi: result.rssi);
});
} catch (ex) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Error occured while bonding'),
content: Text(ex.toString()),
actions: <Widget>[
TextButton(
child: const Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
);
},
),
],
),
),
//),
),
);
}
/*
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Bluetooth Devices"),
),
body: RefreshIndicator(
onRefresh: () {
debugPrint("refreshed");
return Future(() => null);
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
//child: Padding(
//padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color: Colors.grey),
),
),
child: Column(
children: [
const Text(
"General",
textAlign: TextAlign.start,
),
Row(
children: [
_CustomListTile(
title: "Dark Mode",
icon: CupertinoIcons.moon,
trailing:
CupertinoSwitch(value: false, onChanged: (value) {})),
]
),
TextField(
controller: textFieldValueHolder,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter the name of your new device',
),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
var textFieldValue = textFieldValueHolder.value.text;
checkNameAvailability(textFieldValue);
setState(() {
inputName = textFieldValue;
});
},
style: buttonStyle,
child: const Text("check name"),
),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
//_startDiscovery();
initScan();
},
style: buttonStyle,
child: const Text("discover devices"),
),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
//_startDiscovery();
_sendData();
},
style: buttonStyle,
child: const Text("send data"),
),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
//_startDiscovery();
dispose();
},
style: buttonStyle,
child: const Text("dispose"),
),
),
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color: Colors.grey),
),
),
child: Row(children: [
const Text(
"Toggle Scan",
style: TextStyle(fontWeight: FontWeight.bold),
),
const Expanded(child: Text("")),
Switch(
value: widgetScanState,
onChanged: (bool toggleState) {
scanState = toggleState;
log("widget state: $scanState");
setState(() {
widgetScanState = toggleState;
});
})
]),
),
// BluetoothDiscovery(start: initScan, deviceID: inputName),
],
),
//),
),
),
);
}*/
}
class _CustomListTile extends StatelessWidget {
final String title;
final IconData icon;
final VoidCallback? onTap;
//final Widget? trailing;
const _CustomListTile({
Key? key,
required this.title,
required this.icon,
this.onTap,
//this.trailing,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
leading: Icon(icon),
onTap: onTap,
//trailing: trailing,
);
}
}
class _SingleSection extends StatelessWidget {
final String? title;
final List<Widget> children;
const _SingleSection({
Key? key,
this.title,
required this.children,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
title!,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
Column(
children: children,
),
],
);
}
}