Add high level API (#160)
* Add high-level api extensions for BleDevice and Characteristic * Add doc comments and move few files to utils * Add advance documentation * Make the high level APIs the new default in the readme * More readme cleanups * Add pair api, improve notifications apis and deprecate writeValue * Use highLevel apis in Example * Rename HighLevel isPaired api to hasPairing api * Update Readme * Rename low level readValue api to read and update Readme * Update UniversalBle to use new apis instead of deprecated internally * Split queueCommand and queueCommandWithoutTimeout * Split setNotifiable into subscribeNotifications, subscribeIndications and unsubscribe * Fix web imports * Update changelog * Switch back to isPaired Co-authored-by: Rohit Sangwan <rohitsangwan647@gmail.com> * Fix BleDevice toString method * Switch back to isPaired * Change peripheral to device * Rename cached to preferCached * Remove preferCached from discoverServices() * Update changelog * Use cached services * Add /// * Reverse withoutResponse to withResponse to avoid negation * Link to low level API * Move connectionState to public readme * Rename disableSubscriptions to unsubscribe * Improve documentation * Fix getService cache * Add NotFoundException --------- Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
+9
-3
@@ -1,6 +1,12 @@
|
||||
## 0.20
|
||||
* Fix `getBluetoothAvailabilityState` reporting wrong Bluetooth status on browsers where Web Bluetooth can be globally disabled
|
||||
* `startScan` API on Web platforms now throws a `WebBluetoothGloballyDisabled` exception if scanning cannot proceed due to Web Bluetooth being globally disabled
|
||||
## 0.20.0
|
||||
* Add new high level API. Services are auto-discovered. the BleDevice class offers convenient member methods and properties.
|
||||
* BREAKING CHANGE: `bleDevice.isPaired` is now `bleDevice.paired`
|
||||
* Deprecate `readValue` in favor of `read`
|
||||
* Deprecate `writeValue` in favor of `write`
|
||||
* Deprecate `setNotifiable` in favor of `subscribeNotifications`, `subscribeIndications`, `unsubscribe`
|
||||
* Fix `getBluetoothAvailabilityState` reporting wrong Bluetooth status on browsers where Web Bluetooth can globally be disabled
|
||||
* `startScan` on web now throws a `WebBluetoothGloballyDisabled` exception if scanning cannot proceed due to Web Bluetooth being globally disabled
|
||||
* EXAMPLE APP: Migrate to the new high level API
|
||||
|
||||
## 0.19.0
|
||||
* Get and prefer advertised name of scanned devices on Apple
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# Low Level API
|
||||
|
||||
### Connecting
|
||||
|
||||
```dart
|
||||
// Connect to a device using the `deviceId` of the BleDevice received from `UniversalBle.onScanResult`
|
||||
String deviceId = bleDevice.deviceId;
|
||||
UniversalBle.connect(deviceId);
|
||||
|
||||
// Disconnect from a device
|
||||
UniversalBle.disconnect(deviceId);
|
||||
|
||||
// Get connection/disconnection updates using stream
|
||||
UniversalBle.connectionStream(deviceId).listen((bool isConnected) {
|
||||
debugPrint('Is device $deviceId connected?: $isConnected');
|
||||
});
|
||||
|
||||
// Or set a handler to get updates of all devices
|
||||
UniversalBle.onConnectionChange = (String deviceId, bool isConnected, String? error) {
|
||||
debugPrint('Is device $deviceId connected?: $isConnected. Error: $error');
|
||||
}
|
||||
```
|
||||
|
||||
### Discovering Services
|
||||
|
||||
After establishing a connection, you need to discover services. This method will discover all services and their characteristics.
|
||||
|
||||
```dart
|
||||
// Discover services of a specific device
|
||||
UniversalBle.discoverServices(deviceId);
|
||||
```
|
||||
|
||||
### Reading & Writing data
|
||||
|
||||
You need to first [discover services](#discovering-services) before you are able to read and write to characteristics.
|
||||
|
||||
```dart
|
||||
// Read data from a characteristic
|
||||
UniversalBle.read(deviceId, serviceId, characteristicId);
|
||||
|
||||
// Write data to a characteristic
|
||||
UniversalBle.write(deviceId, serviceId, characteristicId, value);
|
||||
|
||||
// Subscribe to a characteristic notifications
|
||||
UniversalBle.subscribeNotifications(deviceId, serviceId, characteristicId);
|
||||
|
||||
// Subscribe to a characteristic indications
|
||||
UniversalBle.subscribeIndications(deviceId, serviceId, characteristicId);
|
||||
|
||||
// Get characteristic notifications/indications updates using stream
|
||||
UniversalBle.characteristicValueStream(deviceId, characteristicId).listen((Uint8List value) {
|
||||
debugPrint('OnValueChange $deviceId, $characteristicId, ${hex.encode(value)}');
|
||||
});
|
||||
|
||||
// Or set a handler to get updates of all characteristics
|
||||
UniversalBle.onValueChange = (String deviceId, String characteristicId, Uint8List value) {
|
||||
debugPrint('onValueChange $deviceId, $characteristicId, ${hex.encode(value)}');
|
||||
}
|
||||
|
||||
// Unsubscribe from notifications/indications
|
||||
UniversalBle.unsubscribe(deviceId, serviceId, characteristicId);
|
||||
```
|
||||
|
||||
### Pairing
|
||||
|
||||
#### Trigger pairing
|
||||
|
||||
##### Pair on Android, Windows, Linux
|
||||
|
||||
```dart
|
||||
await UniversalBle.pair(deviceId);
|
||||
```
|
||||
|
||||
##### Pair on Apple and web
|
||||
|
||||
For Apple and Web, pairing support depends on the device. Pairing is triggered automatically by the OS when you try to read/write from/to an encrypted characteristic.
|
||||
|
||||
Calling `UniversalBle.pair(deviceId)` will only trigger pairing if the device has an _encrypted read characteristic_.
|
||||
|
||||
If your device only has encrypted write characteristics or you happen to know which encrypted read characteristic you want to use, you can pass it with a `pairingCommand`.
|
||||
|
||||
```dart
|
||||
UniversalBle.pair(deviceId, pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
|
||||
```
|
||||
|
||||
After pairing you can check the pairing status.
|
||||
|
||||
#### Pairing status
|
||||
|
||||
##### Pair on Android, Windows, Linux
|
||||
|
||||
```dart
|
||||
// Check current pairing state
|
||||
bool? isPaired = UniversalBle.isPaired(deviceId);
|
||||
```
|
||||
|
||||
##### Pair on Apple and web
|
||||
|
||||
For `Apple` and `Web`, you have to pass a "pairingCommand" with an encrypted read or write characteristic. If you don't pass it then it will return `null`.
|
||||
|
||||
```dart
|
||||
bool? isPaired = await UniversalBle.isPaired(deviceId, pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
|
||||
```
|
||||
|
||||
##### Discovering encrypted characteristic
|
||||
|
||||
To discover encrypted characteristics, make sure your device is not paired and use the example app to read/write to all discovered characteristics one by one. If one of them triggers pairing, that means it is encrypted and you can use it to construct `BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC")`.
|
||||
|
||||
#### Pairing state changes
|
||||
|
||||
```dart
|
||||
// Get pairing state updates using stream
|
||||
UniversalBle.pairingStateStream(deviceId).listen((bool paired) {
|
||||
// Handle pairing state change
|
||||
});
|
||||
|
||||
// Or set a handler to get pairing state updates of all devices
|
||||
UniversalBle.onPairingStateChange = (String deviceId, bool paired) {}
|
||||
```
|
||||
|
||||
#### Unpair
|
||||
|
||||
```dart
|
||||
UniversalBle.unpair(deviceId);
|
||||
```
|
||||
|
||||
### Request MTU
|
||||
|
||||
This method will **attempt** to set the MTU (Maximum Transmission Unit) but it is not guaranteed to succeed due to platform limitations. It will always return the current MTU.
|
||||
|
||||
```dart
|
||||
int mtu = await UniversalBle.requestMtu(widget.deviceId, 247);
|
||||
```
|
||||
@@ -14,6 +14,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
|
||||
- [Reading & Writing data](#reading--writing-data)
|
||||
- [Pairing](#pairing)
|
||||
- [Bluetooth Availability](#bluetooth-availability)
|
||||
- [Requesting MTU](#requesting-mtu)
|
||||
- [Command Queue](#command-queue)
|
||||
- [Timeout](#timeout)
|
||||
- [UUID Format Agnostic](#uuid-format-agnostic)
|
||||
@@ -26,12 +27,12 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
|
||||
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| getSystemDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
|
||||
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| read | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| write | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| subscriptions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| pair | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ⏺ |
|
||||
| unpair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
|
||||
| isPaired | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| isPaired | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| onPairingStateChange | ✔️ | ⏺ | ⏺ | ✔️ | ✔️ | ⏺ |
|
||||
| getBluetoothAvailabilityState | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
|
||||
| enable/disable Bluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
|
||||
@@ -57,7 +58,7 @@ import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
```dart
|
||||
// Get scan updates from stream
|
||||
UniversalBle.scanStream.listen((bleDevice) {
|
||||
UniversalBle.scanStream.listen((BleDevice bleDevice) {
|
||||
// e.g. Use BleDevice ID to connect
|
||||
});
|
||||
|
||||
@@ -155,64 +156,139 @@ List<String> withNamePrefix;
|
||||
|
||||
### Connecting
|
||||
|
||||
#### Connect
|
||||
|
||||
Connects to the BLE device. This method initiates a connection to the Bluetooth device.
|
||||
|
||||
```dart
|
||||
// Connect to a device using the `deviceId` of the BleDevice received from `UniversalBle.onScanResult`
|
||||
String deviceId = bleDevice.deviceId;
|
||||
UniversalBle.connect(deviceId);
|
||||
await bleDevice.connect();
|
||||
```
|
||||
|
||||
// Disconnect from a device
|
||||
UniversalBle.disconnect(deviceId);
|
||||
#### Disconnect
|
||||
|
||||
// Get connection/disconnection updates using stream
|
||||
UniversalBle.connectionStream(deviceId).listen((bool isConnected) {
|
||||
debugPrint('OnConnectionChange $deviceId, $isConnected');
|
||||
Disconnects from the BLE device. This method terminates the connection to the Bluetooth device.
|
||||
|
||||
```dart
|
||||
await bleDevice.disconnect();
|
||||
```
|
||||
|
||||
#### Connection Stream
|
||||
|
||||
```dart
|
||||
bleDevice.connectionStream.listen((isConnected) {
|
||||
debugPrint('Is device connected?: $isConnected');
|
||||
});
|
||||
```
|
||||
|
||||
// Or set a handler to get updates of all devices
|
||||
UniversalBle.onConnectionChange = (String deviceId, bool isConnected, String? error) {
|
||||
debugPrint('OnConnectionChange $deviceId, $isConnected Error: $error');
|
||||
}
|
||||
#### IsConnected
|
||||
|
||||
// Get current connection state
|
||||
```dart
|
||||
bool isConnected = await bleDevice.isConnected;
|
||||
```
|
||||
|
||||
#### Connection state
|
||||
|
||||
```dart
|
||||
// Can be connected, disconnected, connecting or disconnecting
|
||||
BleConnectionState connectionState = await bleDevice.connectionState;
|
||||
```
|
||||
|
||||
### Discovering Services
|
||||
|
||||
After establishing a connection, you need to discover services. This method will discover all services and their characteristics.
|
||||
After establishing a connection, services need to be discovered. This method will discover all services and their characteristics.
|
||||
|
||||
If you don't call this method then it will be automatically called when you try to get any service or characteristic.
|
||||
|
||||
#### DiscoverServices
|
||||
|
||||
Discovers the services offered by the device. Returns a `Future<List<BleService>>`. After discovery services are cached and each call of this method updates the cache.
|
||||
|
||||
```dart
|
||||
// Discover services of a specific device
|
||||
UniversalBle.discoverServices(deviceId);
|
||||
List<BleService> services = await bleDevice.discoverServices();
|
||||
for (var service in services) {
|
||||
debugPrint('Service UUID: ${service.uuid}');
|
||||
}
|
||||
```
|
||||
|
||||
### Reading & Writing data
|
||||
#### GetService
|
||||
|
||||
Retrieves a specific service. Returns a `Future<BleService>`.
|
||||
|
||||
- `service`: The UUID of the service.
|
||||
- `preferCached`: If `true` (default), cached services are used. If cache is empty, `discoverServices()` will be called.
|
||||
|
||||
```dart
|
||||
BleService service = await bleDevice.getService('180a');
|
||||
```
|
||||
|
||||
#### GetCharacteristic
|
||||
|
||||
Retrieves a specific characteristic from a service. Returns a `Future<BleCharacteristic>`.
|
||||
|
||||
- `service`: The UUID of the service.
|
||||
- `characteristic`: The UUID of the characteristic.
|
||||
- `preferCached`: If `true` (default), cached services are used. If cache is empty, `discoverServices()` will be called
|
||||
|
||||
```dart
|
||||
BleCharacteristic characteristic = await bleDevice.getCharacteristic('180a','2a56');
|
||||
```
|
||||
|
||||
Or retrieve from `BleService`
|
||||
|
||||
```dart
|
||||
BleCharacteristic characteristic = await service.getCharacteristic('2a56');
|
||||
```
|
||||
|
||||
## Reading & Writing data
|
||||
|
||||
You need to first [discover services](#discovering-services) before you are able to read and write to characteristics.
|
||||
|
||||
```dart
|
||||
// Read data from a characteristic
|
||||
UniversalBle.readValue(deviceId, serviceId, characteristicId);
|
||||
Uint8List value = await characteristic.read();
|
||||
```
|
||||
|
||||
// Write data to a characteristic
|
||||
UniversalBle.writeValue(deviceId, serviceId, characteristicId, value);
|
||||
```dart
|
||||
await characteristic.write([0x01, 0x02, 0x03]);
|
||||
|
||||
// Subscribe to a characteristic
|
||||
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.notification);
|
||||
await characteristic.write([0x01, 0x02, 0x03], withResponse: false);
|
||||
```
|
||||
|
||||
// Get characteristic updates using stream
|
||||
UniversalBle.characteristicValueStream(deviceId, characteristicId).listen((Uint8List value) {
|
||||
debugPrint('OnValueChange $deviceId, $characteristicId, ${hex.encode(value)}');
|
||||
## Subscriptions
|
||||
|
||||
Get `BleCharacteristic` using `bleDevice.getCharacteristic`
|
||||
|
||||
### OnValueReceived
|
||||
|
||||
A stream of `Uint8List` that emits values received from the characteristic. Listen to this stream to receive updates whenever the characteristic's value changes.
|
||||
|
||||
```dart
|
||||
characteristic.onValueReceived.listen((value) {
|
||||
debugPrint('Received value: ${value.toString()}');
|
||||
});
|
||||
```
|
||||
|
||||
// Or set a handler to get updates of all characteristics
|
||||
UniversalBle.onValueChange = (String deviceId, String characteristicId, Uint8List value) {
|
||||
debugPrint('onValueChange $deviceId, $characteristicId, ${hex.encode(value)}');
|
||||
}
|
||||
### Notifications
|
||||
|
||||
// Unsubscribe from a characteristic
|
||||
UniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProperty.disabled);
|
||||
Subscribe to notifications for this characteristic. Throws an exception if the characteristic does not support notifications.
|
||||
|
||||
```dart
|
||||
await characteristic.notifications.subscribe();
|
||||
```
|
||||
|
||||
### Indications
|
||||
|
||||
Subscribe to indications for this characteristic. Throws an exception if the characteristic does not support indications.
|
||||
|
||||
```dart
|
||||
await characteristic.indications.subscribe();
|
||||
```
|
||||
|
||||
### Unsubscribe
|
||||
|
||||
Unsubscribe from notifications and indications of this characteristic.
|
||||
|
||||
```dart
|
||||
await characteristic.unsubscribe();
|
||||
```
|
||||
|
||||
### Pairing
|
||||
@@ -222,18 +298,18 @@ UniversalBle.setNotifiable(deviceId, serviceId, characteristicId, BleInputProper
|
||||
##### Pair on Android, Windows, Linux
|
||||
|
||||
```dart
|
||||
await UniversalBle.pair(deviceId);
|
||||
await bleDevice.pair();
|
||||
```
|
||||
|
||||
##### Pair on Apple and web
|
||||
For Apple and Web, pairing support depends on the device. Pairing is triggered automatically by the OS when you try to read/write from/to an encrypted characteristic.
|
||||
|
||||
Calling `UniversalBle.pair(deviceId)` will only trigger pairing if the device has an *encrypted read characteristic*.
|
||||
Calling `bleDevice.pair()` will only trigger pairing if the device has an *encrypted read characteristic*.
|
||||
|
||||
If your device only has encrypted write characteristics or you happen to know which encrypted read characteristic you want to use, you can pass it with a `pairingCommand`.
|
||||
|
||||
```dart
|
||||
UniversalBle.pair(deviceId, pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
|
||||
await bleDevice.pair(pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
|
||||
```
|
||||
After pairing you can check the pairing status.
|
||||
|
||||
@@ -243,7 +319,7 @@ After pairing you can check the pairing status.
|
||||
|
||||
```dart
|
||||
// Check current pairing state
|
||||
bool? isPaired = UniversalBle.isPaired(deviceId);
|
||||
bool? isPaired = bleDevice.isPaired();
|
||||
```
|
||||
|
||||
##### Pair on Apple and web
|
||||
@@ -251,7 +327,7 @@ bool? isPaired = UniversalBle.isPaired(deviceId);
|
||||
For `Apple` and `Web`, you have to pass a "pairingCommand" with an encrypted read or write characteristic. If you don't pass it then it will return `null`.
|
||||
|
||||
```dart
|
||||
bool? isPaired = await UniversalBle.isPaired(deviceId, pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
|
||||
bool? isPaired = await bleDevice.isPaired(pairingCommand: BleCommand(service:"SERVICE", characteristic:"ENCRYPTED_CHARACTERISTIC"));
|
||||
```
|
||||
|
||||
##### Discovering encrypted characteristic
|
||||
@@ -261,17 +337,14 @@ To discover encrypted characteristics, make sure your device is not paired and u
|
||||
|
||||
```dart
|
||||
// Get pairing state updates using stream
|
||||
UniversalBle.pairingStateStream(deviceId).listen((bool isPaired) {
|
||||
bleDevice.pairingStateStream.listen((bool paired) {
|
||||
// Handle pairing state change
|
||||
});
|
||||
|
||||
// Or set a handler to get pairing state updates of all devices
|
||||
UniversalBle.onPairingStateChange = (String deviceId, bool isPaired) {}
|
||||
```
|
||||
|
||||
#### Unpair
|
||||
```dart
|
||||
UniversalBle.unpair(deviceId);
|
||||
bleDevice.unpair();
|
||||
```
|
||||
|
||||
### Bluetooth Availability
|
||||
@@ -292,12 +365,10 @@ UniversalBle.enableBluetooth();
|
||||
UniversalBle.disableBluetooth();
|
||||
```
|
||||
|
||||
### Request MTU
|
||||
|
||||
This method will **attempt** to set the MTU (Maximum Transmission Unit) but it is not guaranteed to succeed due to platform limitations. It will always return the current MTU.
|
||||
### Requesting MTU
|
||||
|
||||
```dart
|
||||
int mtu = await UniversalBle.requestMtu(widget.deviceId, 247);
|
||||
int mtu = await bleDevice.requestMtu(256);
|
||||
```
|
||||
|
||||
#### Platform Limitations
|
||||
@@ -469,6 +540,9 @@ class UniversalBleMock extends UniversalBlePlatform {
|
||||
UniversalBle.setInstance(UniversalBleMock());
|
||||
```
|
||||
|
||||
## Low level API
|
||||
|
||||
For more granular control, you can use the [Low-Level API](README.low_level.md). This API is "Device ID"-based, offering greater flexibility by enabling direct calls without the need for object instances.
|
||||
|
||||
## 🧩 Apps using Universal BLE
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ class _MyAppState extends State<MyApp> {
|
||||
UniversalBle.queueType = _queueType;
|
||||
UniversalBle.timeout = const Duration(seconds: 10);
|
||||
|
||||
UniversalBle.onAvailabilityChange = (state) {
|
||||
UniversalBle.availabilityStream.listen((state) {
|
||||
setState(() {
|
||||
bleAvailabilityState = state;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
UniversalBle.onScanResult = (result) {
|
||||
UniversalBle.scanStream.listen((result) {
|
||||
// log(result.toString());
|
||||
int index = _bleDevices.indexWhere((e) => e.deviceId == result.deviceId);
|
||||
if (index == -1) {
|
||||
@@ -59,7 +59,7 @@ class _MyAppState extends State<MyApp> {
|
||||
_bleDevices[index] = result;
|
||||
}
|
||||
setState(() {});
|
||||
};
|
||||
});
|
||||
|
||||
// UniversalBle.onQueueUpdate = (String id, int remainingItems) {
|
||||
// debugPrint("Queue: $id RemainingItems: $remainingItems");
|
||||
@@ -259,13 +259,11 @@ class _MyAppState extends State<MyApp> {
|
||||
bleDevice: device,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PeripheralDetailPage(
|
||||
device.deviceId,
|
||||
device.name ?? "Unknown Peripheral",
|
||||
),
|
||||
));
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PeripheralDetailPage(device),
|
||||
),
|
||||
);
|
||||
UniversalBle.stopScan();
|
||||
setState(() {
|
||||
_isScanning = false;
|
||||
|
||||
@@ -30,7 +30,7 @@ class ScannedItemWidget extends StatelessWidget {
|
||||
visible: manufacturerData != null,
|
||||
child: Text(manufacturerData.toString()),
|
||||
),
|
||||
bleDevice.isPaired == true
|
||||
bleDevice.paired == true
|
||||
? const Text(
|
||||
"Paired",
|
||||
style: TextStyle(color: Colors.green),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: avoid_print, depend_on_referenced_packages
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
@@ -13,9 +11,8 @@ import 'package:universal_ble_example/widgets/responsive_buttons_grid.dart';
|
||||
import 'package:universal_ble_example/widgets/responsive_view.dart';
|
||||
|
||||
class PeripheralDetailPage extends StatefulWidget {
|
||||
final String deviceId;
|
||||
final String deviceName;
|
||||
const PeripheralDetailPage(this.deviceId, this.deviceName, {super.key});
|
||||
final BleDevice bleDevice;
|
||||
const PeripheralDetailPage(this.bleDevice, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -24,30 +21,32 @@ class PeripheralDetailPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
late final bleDevice = widget.bleDevice;
|
||||
bool isConnected = false;
|
||||
GlobalKey<FormState> valueFormKey = GlobalKey<FormState>();
|
||||
List<BleService> discoveredServices = [];
|
||||
final List<String> _logs = [];
|
||||
final binaryCode = TextEditingController();
|
||||
|
||||
({
|
||||
BleService service,
|
||||
BleCharacteristic characteristic
|
||||
})? selectedCharacteristic;
|
||||
StreamSubscription? connectionStreamSubscription;
|
||||
StreamSubscription? pairingStateSubscription;
|
||||
BleService? selectedService;
|
||||
BleCharacteristic? selectedCharacteristic;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
UniversalBle.onConnectionChange = _handleConnectionChange;
|
||||
|
||||
connectionStreamSubscription =
|
||||
bleDevice.connectionStream.listen(_handleConnectionChange);
|
||||
pairingStateSubscription =
|
||||
bleDevice.pairingStateStream.listen(_handlePairingStateChange);
|
||||
UniversalBle.onValueChange = _handleValueChange;
|
||||
UniversalBle.onPairingStateChange = _handlePairingStateChange;
|
||||
_asyncInits();
|
||||
}
|
||||
|
||||
void _asyncInits() {
|
||||
UniversalBle.getConnectionState(
|
||||
widget.deviceId,
|
||||
).then((state) {
|
||||
bleDevice.connectionState.then((state) {
|
||||
if (state == BleConnectionState.connected) {
|
||||
setState(() {
|
||||
isConnected = true;
|
||||
@@ -59,7 +58,8 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
UniversalBle.onConnectionChange = null;
|
||||
connectionStreamSubscription?.cancel();
|
||||
pairingStateSubscription?.cancel();
|
||||
UniversalBle.onValueChange = null;
|
||||
}
|
||||
|
||||
@@ -69,18 +69,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void _handleConnectionChange(
|
||||
String deviceId,
|
||||
bool isConnected,
|
||||
String? error,
|
||||
) {
|
||||
print(
|
||||
'_handleConnectionChange $deviceId, $isConnected ${error != null ? 'Error: $error' : ''}',
|
||||
);
|
||||
void _handleConnectionChange(bool isConnected) {
|
||||
debugPrint('_handleConnectionChange $isConnected');
|
||||
setState(() {
|
||||
if (deviceId == widget.deviceId) {
|
||||
this.isConnected = isConnected;
|
||||
}
|
||||
this.isConnected = isConnected;
|
||||
});
|
||||
_addLog('Connection', isConnected ? "Connected" : "Disconnected");
|
||||
// Auto Discover Services
|
||||
@@ -93,12 +85,12 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
String deviceId, String characteristicId, Uint8List value) {
|
||||
String s = String.fromCharCodes(value);
|
||||
String data = '$s\nraw : ${value.toString()}';
|
||||
print('_handleValueChange $deviceId, $characteristicId, $s');
|
||||
debugPrint('_handleValueChange $characteristicId, $s');
|
||||
_addLog("Value", data);
|
||||
}
|
||||
|
||||
void _handlePairingStateChange(String deviceId, bool isPaired) {
|
||||
print('isPaired $deviceId, $isPaired');
|
||||
void _handlePairingStateChange(bool isPaired) {
|
||||
debugPrint('isPaired $isPaired');
|
||||
_addLog("PairingStateChange - isPaired", isPaired);
|
||||
}
|
||||
|
||||
@@ -106,10 +98,9 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
const webWarning =
|
||||
"Note: Only services added in ScanFilter or WebOptions will be discovered";
|
||||
try {
|
||||
var services = await UniversalBle.discoverServices(widget.deviceId);
|
||||
print('${services.length} services discovered');
|
||||
print(services);
|
||||
discoveredServices.clear();
|
||||
var services = await bleDevice.discoverServices();
|
||||
debugPrint('${services.length} services discovered');
|
||||
debugPrint(services.toString());
|
||||
setState(() {
|
||||
discoveredServices = services;
|
||||
});
|
||||
@@ -126,13 +117,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
Future<void> _readValue() async {
|
||||
BleCharacteristic? selectedCharacteristic = this.selectedCharacteristic;
|
||||
if (selectedCharacteristic == null) return;
|
||||
try {
|
||||
Uint8List value = await UniversalBle.readValue(
|
||||
widget.deviceId,
|
||||
selectedCharacteristic!.service.uuid,
|
||||
selectedCharacteristic!.characteristic.uuid,
|
||||
);
|
||||
Uint8List value = await selectedCharacteristic.read();
|
||||
String s = String.fromCharCodes(value);
|
||||
String data = '$s\nraw : ${value.toString()}';
|
||||
_addLog('Read', data);
|
||||
@@ -142,6 +130,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
Future<void> _writeValue() async {
|
||||
BleCharacteristic? selectedCharacteristic = this.selectedCharacteristic;
|
||||
if (selectedCharacteristic == null ||
|
||||
!valueFormKey.currentState!.validate() ||
|
||||
binaryCode.text.isEmpty) {
|
||||
@@ -157,54 +146,59 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
try {
|
||||
await UniversalBle.writeValue(
|
||||
widget.deviceId,
|
||||
selectedCharacteristic!.service.uuid,
|
||||
selectedCharacteristic!.characteristic.uuid,
|
||||
await selectedCharacteristic.write(
|
||||
value,
|
||||
_hasSelectedCharacteristicProperty(
|
||||
[CharacteristicProperty.writeWithoutResponse])
|
||||
? BleOutputProperty.withoutResponse
|
||||
: BleOutputProperty.withResponse,
|
||||
withResponse: _hasSelectedCharacteristicProperty(
|
||||
[CharacteristicProperty.writeWithoutResponse],
|
||||
),
|
||||
);
|
||||
_addLog('Write', value);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
debugPrint(e.toString());
|
||||
_addLog('WriteError', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setBleInputProperty(BleInputProperty inputProperty) async {
|
||||
Future<void> _subscribeChar() async {
|
||||
BleCharacteristic? selectedCharacteristic = this.selectedCharacteristic;
|
||||
if (selectedCharacteristic == null) return;
|
||||
try {
|
||||
if (inputProperty != BleInputProperty.disabled) {
|
||||
List<CharacteristicProperty> properties =
|
||||
selectedCharacteristic!.characteristic.properties;
|
||||
if (properties.contains(CharacteristicProperty.notify)) {
|
||||
inputProperty = BleInputProperty.notification;
|
||||
} else if (properties.contains(CharacteristicProperty.indicate)) {
|
||||
inputProperty = BleInputProperty.indication;
|
||||
} else {
|
||||
throw 'No notify or indicate property';
|
||||
}
|
||||
}
|
||||
await UniversalBle.setNotifiable(
|
||||
widget.deviceId,
|
||||
selectedCharacteristic!.service.uuid,
|
||||
selectedCharacteristic!.characteristic.uuid,
|
||||
inputProperty,
|
||||
);
|
||||
_addLog('BleInputProperty', inputProperty);
|
||||
var subscription = _getCharacteristicSubscription(selectedCharacteristic);
|
||||
if (subscription == null) throw 'No notify or indicate property';
|
||||
await subscription.subscribe();
|
||||
_addLog('BleCharSubscription', 'Subscribed');
|
||||
// Updates can also be handled by
|
||||
// subscription.listen((data) {});
|
||||
} catch (e) {
|
||||
_addLog('NotifyError', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unsubscribeChar() async {
|
||||
try {
|
||||
await selectedCharacteristic?.unsubscribe();
|
||||
_addLog('BleCharSubscription', 'UnSubscribed');
|
||||
} catch (e) {
|
||||
_addLog('NotifyError', e);
|
||||
}
|
||||
}
|
||||
|
||||
CharacteristicSubscription? _getCharacteristicSubscription(
|
||||
BleCharacteristic characteristic) {
|
||||
var properties = characteristic.properties;
|
||||
if (properties.contains(CharacteristicProperty.notify)) {
|
||||
return characteristic.notifications;
|
||||
} else if (properties.contains(CharacteristicProperty.indicate)) {
|
||||
return characteristic.indications;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("${widget.deviceName} - ${widget.deviceId}"),
|
||||
title: Text("${bleDevice.name ?? "Unknown"} - ${bleDevice.deviceId}"),
|
||||
elevation: 4,
|
||||
actions: [
|
||||
Padding(
|
||||
@@ -234,13 +228,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
: ServicesListWidget(
|
||||
discoveredServices: discoveredServices,
|
||||
scrollable: true,
|
||||
onTap: (BleService service,
|
||||
BleCharacteristic characteristic) {
|
||||
onTap: (service, characteristic) {
|
||||
setState(() {
|
||||
selectedCharacteristic = (
|
||||
service: service,
|
||||
characteristic: characteristic
|
||||
);
|
||||
selectedService = service;
|
||||
selectedCharacteristic = characteristic;
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -264,9 +255,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
enabled: !isConnected,
|
||||
onPressed: () async {
|
||||
try {
|
||||
await UniversalBle.connect(
|
||||
widget.deviceId,
|
||||
);
|
||||
await bleDevice.connect();
|
||||
_addLog("ConnectionResult", true);
|
||||
} catch (e) {
|
||||
_addLog('ConnectError (${e.runtimeType})', e);
|
||||
@@ -277,7 +266,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
text: 'Disconnect',
|
||||
enabled: isConnected,
|
||||
onPressed: () {
|
||||
UniversalBle.disconnect(widget.deviceId);
|
||||
bleDevice.disconnect();
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -294,17 +283,17 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
child: Card(
|
||||
child: ListTile(
|
||||
title: SelectableText(
|
||||
"Characteristic: ${selectedCharacteristic!.characteristic.uuid}",
|
||||
"Characteristic: ${selectedCharacteristic?.uuid}",
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
"Service: ${selectedCharacteristic!.service.uuid}",
|
||||
"Service: ${selectedService?.uuid}",
|
||||
),
|
||||
Text(
|
||||
"Properties: ${selectedCharacteristic!.characteristic.properties.map((e) => e.name)}",
|
||||
"Properties: ${selectedCharacteristic?.properties.map((e) => e.name)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -360,9 +349,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
onPressed: () async {
|
||||
_addLog(
|
||||
'ConnectionState',
|
||||
await UniversalBle.getConnectionState(
|
||||
widget.deviceId,
|
||||
),
|
||||
await bleDevice.connectionState,
|
||||
);
|
||||
},
|
||||
text: 'Connection State',
|
||||
@@ -371,8 +358,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
PlatformButton(
|
||||
enabled: isConnected,
|
||||
onPressed: () async {
|
||||
int mtu = await UniversalBle.requestMtu(
|
||||
widget.deviceId, 247);
|
||||
int mtu = await bleDevice.requestMtu(247);
|
||||
_addLog('MTU', mtu);
|
||||
},
|
||||
text: 'Request Mtu',
|
||||
@@ -403,8 +389,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
CharacteristicProperty.notify,
|
||||
CharacteristicProperty.indicate
|
||||
]),
|
||||
onPressed: () => _setBleInputProperty(
|
||||
BleInputProperty.notification),
|
||||
onPressed: _subscribeChar,
|
||||
text: 'Subscribe',
|
||||
),
|
||||
PlatformButton(
|
||||
@@ -414,21 +399,19 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
CharacteristicProperty.notify,
|
||||
CharacteristicProperty.indicate
|
||||
]),
|
||||
onPressed: () => _setBleInputProperty(
|
||||
BleInputProperty.disabled),
|
||||
onPressed: _unsubscribeChar,
|
||||
text: 'Unsubscribe',
|
||||
),
|
||||
PlatformButton(
|
||||
enabled: BleCapabilities.supportsAllPairingKinds,
|
||||
onPressed: () async {
|
||||
try {
|
||||
await UniversalBle.pair(
|
||||
widget.deviceId,
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
await bleDevice.pair(
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
_addLog("Pairing Result", true);
|
||||
} catch (e) {
|
||||
_addLog('PairError (${e.runtimeType})', e);
|
||||
@@ -438,20 +421,19 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
),
|
||||
PlatformButton(
|
||||
onPressed: () async {
|
||||
bool? isPaired = await UniversalBle.isPaired(
|
||||
widget.deviceId,
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
_addLog('IsPaired', isPaired);
|
||||
bool? isPaired = await bleDevice.isPaired(
|
||||
// pairingCommand: BleCommand(
|
||||
// service: "",
|
||||
// characteristic: "",
|
||||
// ),
|
||||
);
|
||||
_addLog('isPaired', isPaired);
|
||||
},
|
||||
text: 'IsPaired',
|
||||
text: 'isPaired',
|
||||
),
|
||||
PlatformButton(
|
||||
onPressed: () async {
|
||||
await UniversalBle.unpair(widget.deviceId);
|
||||
await bleDevice.unpair();
|
||||
},
|
||||
text: 'Unpair',
|
||||
),
|
||||
@@ -462,13 +444,10 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
if (deviceType != DeviceType.desktop)
|
||||
ServicesListWidget(
|
||||
discoveredServices: discoveredServices,
|
||||
onTap: (BleService service,
|
||||
BleCharacteristic characteristic) {
|
||||
onTap: (service, characteristic) {
|
||||
setState(() {
|
||||
selectedCharacteristic = (
|
||||
service: service,
|
||||
characteristic: characteristic
|
||||
);
|
||||
selectedService = service;
|
||||
selectedCharacteristic = characteristic;
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -497,9 +476,8 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
}
|
||||
|
||||
bool _hasSelectedCharacteristicProperty(
|
||||
List<CharacteristicProperty> properties) =>
|
||||
properties.any((property) =>
|
||||
selectedCharacteristic?.characteristic.properties
|
||||
.contains(property) ??
|
||||
false);
|
||||
List<CharacteristicProperty> properties) {
|
||||
return properties.any((property) =>
|
||||
selectedCharacteristic?.properties.contains(property) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||
device_info_plus: b0fafc687fb901e2af612763340f1b0d4352f8e5
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
|
||||
|
||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||
|
||||
|
||||
@@ -402,7 +402,7 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.19.1"
|
||||
version: "0.20.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
/// Extension methods for [BleCharacteristic] to simplify common operations.
|
||||
extension BleCharacteristicExtension on BleCharacteristic {
|
||||
/// A stream of [Uint8List] that emits values received from the characteristic.
|
||||
Stream<Uint8List> get onValueReceived =>
|
||||
UniversalBle.characteristicValueStream(_deviceId, uuid);
|
||||
|
||||
/// Subscribes to notifications for this characteristic.
|
||||
///
|
||||
/// Throws an exception if the characteristic does not support notifications.
|
||||
CharacteristicSubscription get notifications =>
|
||||
CharacteristicSubscription(this, CharacteristicProperty.notify);
|
||||
|
||||
/// Subscribes to indications for this characteristic.
|
||||
///
|
||||
/// Throws an exception if the characteristic does not support indications.
|
||||
CharacteristicSubscription get indications =>
|
||||
CharacteristicSubscription(this, CharacteristicProperty.indicate);
|
||||
|
||||
/// Unsubscribes notifications/indications from this characteristic.
|
||||
Future<void> unsubscribe() =>
|
||||
UniversalBle.unsubscribe(_deviceId, _serviceId, uuid);
|
||||
|
||||
/// Reads the current value of the characteristic.
|
||||
Future<Uint8List> read() => UniversalBle.read(
|
||||
_deviceId,
|
||||
_serviceId,
|
||||
uuid,
|
||||
);
|
||||
|
||||
/// Writes a value to the characteristic.
|
||||
///
|
||||
/// [value] is the list of bytes to write.
|
||||
/// [withResponse] indicates whether the write should be performed with a response from the device.
|
||||
/// Default is true, meaning the device will acknowledge the write operation.
|
||||
/// If set to false, the write operation will be performed without waiting for a response.
|
||||
Future<void> write(List<int> value, {bool withResponse = true}) async {
|
||||
await UniversalBle.write(
|
||||
_deviceId,
|
||||
_serviceId,
|
||||
uuid,
|
||||
Uint8List.fromList(value),
|
||||
withoutResponse: !withResponse,
|
||||
);
|
||||
}
|
||||
|
||||
String get _deviceId {
|
||||
String? deviceId = metaData?.deviceId;
|
||||
if (deviceId == null) {
|
||||
throw "DeviceId is not preset in characteristic metaData";
|
||||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
String get _serviceId {
|
||||
String? serviceId = metaData?.serviceId;
|
||||
if (serviceId == null) {
|
||||
throw "ServiceId is not preset in characteristic metaData";
|
||||
}
|
||||
return serviceId;
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages subscription to a characteristic's notifications or indications.
|
||||
///
|
||||
/// Instances are typically obtained via the `notifications` or `indications`
|
||||
/// getters on `BleCharacteristic`.
|
||||
///
|
||||
/// call [subscribe] to instruct the device to start sending data.
|
||||
/// call [unsubscribe] To stop receiving data and instruct the device to cease sending,
|
||||
/// call [listen] to register a callback to receive this data..
|
||||
/// use [isSupported] to check if this operation is supported by the characteristic
|
||||
///
|
||||
class CharacteristicSubscription {
|
||||
final BleCharacteristic _characteristic;
|
||||
final CharacteristicProperty _property;
|
||||
|
||||
/// Indicates whether the characteristic supports the requested subscription type
|
||||
/// (notifications or indications).
|
||||
final bool isSupported;
|
||||
|
||||
CharacteristicSubscription(
|
||||
this._characteristic,
|
||||
this._property,
|
||||
) : isSupported = _characteristic.properties.contains(_property);
|
||||
|
||||
/// Registers a listener for incoming data from the characteristic.
|
||||
StreamSubscription listen(
|
||||
void Function(Uint8List event) onData, {
|
||||
Function? onError,
|
||||
void Function()? onDone,
|
||||
bool? cancelOnError,
|
||||
}) {
|
||||
return _characteristic.onValueReceived.listen(
|
||||
onData,
|
||||
onError: onError,
|
||||
onDone: onDone,
|
||||
cancelOnError: cancelOnError,
|
||||
);
|
||||
}
|
||||
|
||||
/// Subscribes to this characteristic.
|
||||
Future<void> subscribe() {
|
||||
if (!isSupported) throw Exception('Operation not supported');
|
||||
|
||||
if (_property == CharacteristicProperty.indicate) {
|
||||
return UniversalBle.subscribeIndications(
|
||||
_characteristic._deviceId,
|
||||
_characteristic._serviceId,
|
||||
_characteristic.uuid,
|
||||
);
|
||||
}
|
||||
|
||||
return UniversalBle.subscribeNotifications(
|
||||
_characteristic._deviceId,
|
||||
_characteristic._serviceId,
|
||||
_characteristic.uuid,
|
||||
);
|
||||
}
|
||||
|
||||
/// Unsubscribes from this characteristic.
|
||||
Future<void> unsubscribe() {
|
||||
if (!isSupported) throw Exception('Operation not supported');
|
||||
return UniversalBle.unsubscribe(
|
||||
_characteristic._deviceId,
|
||||
_characteristic._serviceId,
|
||||
_characteristic.uuid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
"CharacteristicSubscription(property: ${_property.name}, isSupported: $isSupported, characteristic: ${_characteristic.uuid})";
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import 'package:universal_ble/src/utils/cache_handler.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
/// Extension methods for [BleDevice] to simplify common operations.
|
||||
extension BleDeviceExtension on BleDevice {
|
||||
/// A stream of [bool] that emits connection status changes for the device.
|
||||
Stream<bool> get connectionStream => UniversalBle.connectionStream(deviceId);
|
||||
|
||||
/// A stream of [bool] that emits pairing status changes for the device.
|
||||
Stream<bool> get pairingStateStream =>
|
||||
UniversalBle.pairingStateStream(deviceId);
|
||||
|
||||
/// Checks if the device is currently connected.
|
||||
Future<bool> get isConnected async =>
|
||||
await UniversalBle.getConnectionState(deviceId) ==
|
||||
BleConnectionState.connected;
|
||||
|
||||
/// Connects to the device.
|
||||
Future<void> connect() => UniversalBle.connect(deviceId);
|
||||
|
||||
/// Disconnects from the device.
|
||||
Future<void> disconnect() => UniversalBle.disconnect(deviceId);
|
||||
|
||||
/// Requests a specific MTU (Maximum Transmission Unit) size for the connection.
|
||||
Future<int> requestMtu(int expectedMtu) =>
|
||||
UniversalBle.requestMtu(deviceId, expectedMtu);
|
||||
|
||||
/// Check if a device is paired.
|
||||
///
|
||||
/// For `Apple` and `Web`, you have to pass a "pairingCommand" with an encrypted read or write characteristic.
|
||||
/// Returns true/false if it manages to execute the command.
|
||||
/// Returns null when no `pairingCommand` is passed.
|
||||
/// Note that it will trigger pairing if the device is not already paired.
|
||||
Future<bool?> isPaired({
|
||||
BleCommand? pairingCommand,
|
||||
Duration? connectionTimeout,
|
||||
}) {
|
||||
return UniversalBle.isPaired(
|
||||
deviceId,
|
||||
pairingCommand: pairingCommand,
|
||||
connectionTimeout: connectionTimeout,
|
||||
);
|
||||
}
|
||||
|
||||
/// Pair a device.
|
||||
///
|
||||
/// It throws error if pairing fails.
|
||||
///
|
||||
/// On `Apple` and `Web`, it only works on devices with encrypted characteristics.
|
||||
/// It is advised to pass a pairingCommand with an encrypted read or write characteristic.
|
||||
/// When not passing a pairingCommand, you should afterwards use [isPaired] with a pairingCommand
|
||||
/// to verify the pairing state.
|
||||
///
|
||||
/// On `Web/Windows` and `Web/Linux`, it does not work for devices that use `ConfirmOnly` pairing.
|
||||
/// Can throw `PairingException`, `ConnectionException` or `PlatformException`.
|
||||
Future<void> pair({
|
||||
BleCommand? pairingCommand,
|
||||
Duration? connectionTimeout,
|
||||
}) {
|
||||
return UniversalBle.pair(
|
||||
deviceId,
|
||||
pairingCommand: pairingCommand,
|
||||
connectionTimeout: connectionTimeout,
|
||||
);
|
||||
}
|
||||
|
||||
/// Unpair a device.
|
||||
///
|
||||
/// It might throw an error if device is not paired.
|
||||
Future<void> unpair() => UniversalBle.unpair(deviceId);
|
||||
|
||||
/// Discovers the services offered by the device.
|
||||
///
|
||||
/// Returns cached services if already discovered after connection.
|
||||
Future<List<BleService>> discoverServices() async {
|
||||
List<BleService> servicesCache =
|
||||
await UniversalBle.discoverServices(deviceId);
|
||||
CacheHandler.instance.saveServices(deviceId, servicesCache);
|
||||
return servicesCache;
|
||||
}
|
||||
|
||||
/// Retrieves a specific service.
|
||||
///
|
||||
/// [service] is the UUID of the service.
|
||||
/// [preferCached] indicates whether to use cached services. If cache is empty, discoverServices() will be called.
|
||||
/// might throw [NotFoundException]
|
||||
Future<BleService> getService(
|
||||
String service, {
|
||||
bool preferCached = true,
|
||||
}) async {
|
||||
List<BleService> discoveredServices = [];
|
||||
if (preferCached) {
|
||||
discoveredServices = CacheHandler.instance.getServices(deviceId) ?? [];
|
||||
}
|
||||
if (discoveredServices.isEmpty) {
|
||||
discoveredServices = await discoverServices();
|
||||
}
|
||||
|
||||
if (discoveredServices.isEmpty) {
|
||||
throw ServiceNotFoundException('No services found');
|
||||
}
|
||||
|
||||
return discoveredServices.firstWhere(
|
||||
(s) => BleUuidParser.compareStrings(s.uuid, service),
|
||||
orElse: () => throw ServiceNotFoundException(
|
||||
'Service "$service" not available',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieves a specific characteristic from a service.
|
||||
///
|
||||
/// [service] is the UUID of the service.
|
||||
/// [characteristic] is the UUID of the characteristic.
|
||||
/// [preferCached] indicates whether to use cached services. If cache is empty, discoverServices() will be called.
|
||||
/// might throw [NotFoundException]
|
||||
Future<BleCharacteristic> getCharacteristic(
|
||||
String characteristic, {
|
||||
required String service,
|
||||
bool preferCached = true,
|
||||
}) async {
|
||||
BleService bluetoothService =
|
||||
await getService(service, preferCached: preferCached);
|
||||
return bluetoothService.getCharacteristic(characteristic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
/// Extension methods for [BleService] objects.
|
||||
extension BleServiceExtension on BleService {
|
||||
/// Retrieves a [BleCharacteristic] from the service by its UUID.
|
||||
///
|
||||
/// Throws an error if no characteristics are found or if the characteristic
|
||||
/// with the given UUID is not available.
|
||||
BleCharacteristic getCharacteristic(String characteristicId) {
|
||||
if (characteristics.isEmpty) {
|
||||
throw CharacteristicNotFoundException('No characteristics found');
|
||||
}
|
||||
return characteristics.firstWhere(
|
||||
(c) => BleUuidParser.compareStrings(c.uuid, characteristicId),
|
||||
orElse: () => throw CharacteristicNotFoundException(
|
||||
'Characteristic "$characteristicId" not available',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export 'package:universal_ble/src/extensions/ble_characteristic_extension.dart';
|
||||
export 'package:universal_ble/src/extensions/ble_service_extension.dart';
|
||||
export 'package:universal_ble/src/extensions/ble_device_extension.dart';
|
||||
@@ -8,8 +8,8 @@ class BleCapabilities {
|
||||
/// because they only support "Numeric Comparison" and "Passkey Entry".
|
||||
///
|
||||
/// For more fine-grained control it is recommended to use `triggersConfirmOnlyPairing`
|
||||
/// in conjunction with the pairing method of your peripheral,
|
||||
/// e.g. if (!BleCapabilities.triggersConfirmOnlyPairing && peripheralUsesConfirmOnlyPairing) throw "In-app pairing not supported";
|
||||
/// in conjunction with the pairing method of your device,
|
||||
/// e.g. if (!BleCapabilities.triggersConfirmOnlyPairing && deviceUsesConfirmOnlyPairing) throw "In-app pairing not supported";
|
||||
static final bool supportsAllPairingKinds =
|
||||
triggersConfirmOnlyPairing || hasSystemPairingApi;
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ class BleDevice {
|
||||
String? name;
|
||||
String? rawName;
|
||||
int? rssi;
|
||||
bool? isPaired;
|
||||
bool? paired;
|
||||
|
||||
/// List of services advertised by the device.
|
||||
List<String> services;
|
||||
bool? isSystemDevice;
|
||||
List<ManufacturerData> manufacturerDataList;
|
||||
@@ -32,7 +34,7 @@ class BleDevice {
|
||||
required this.deviceId,
|
||||
required String? name,
|
||||
this.rssi,
|
||||
this.isPaired,
|
||||
this.paired,
|
||||
this.services = const [],
|
||||
this.isSystemDevice,
|
||||
this.manufacturerDataList = const [],
|
||||
@@ -47,7 +49,7 @@ class BleDevice {
|
||||
'deviceId: $deviceId, '
|
||||
'name: $name, '
|
||||
'rssi: $rssi, '
|
||||
'isPaired: $isPaired, '
|
||||
'paired: $paired, '
|
||||
'services: $services, '
|
||||
'isSystemDevice: $isSystemDevice, '
|
||||
'manufacturerDataList: $manufacturerDataList';
|
||||
|
||||
@@ -2,17 +2,9 @@ enum BleInputProperty {
|
||||
disabled,
|
||||
notification,
|
||||
indication;
|
||||
|
||||
const BleInputProperty();
|
||||
|
||||
factory BleInputProperty.parse(int index) => BleInputProperty.values[index];
|
||||
}
|
||||
|
||||
enum BleOutputProperty {
|
||||
withResponse,
|
||||
withoutResponse;
|
||||
|
||||
const BleOutputProperty();
|
||||
|
||||
factory BleOutputProperty.parse(int index) => BleOutputProperty.values[index];
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
class BleService {
|
||||
late String uuid;
|
||||
String uuid;
|
||||
List<BleCharacteristic> characteristics;
|
||||
|
||||
BleService(String uuid, this.characteristics) {
|
||||
this.uuid = BleUuidParser.string(uuid);
|
||||
}
|
||||
BleService(
|
||||
String uuid,
|
||||
this.characteristics,
|
||||
) : uuid = BleUuidParser.string(uuid);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -15,17 +16,43 @@ class BleService {
|
||||
}
|
||||
|
||||
class BleCharacteristic {
|
||||
late String uuid;
|
||||
String uuid;
|
||||
List<CharacteristicProperty> properties;
|
||||
({String deviceId, String serviceId})? metaData;
|
||||
|
||||
BleCharacteristic(String uuid, this.properties) {
|
||||
this.uuid = BleUuidParser.string(uuid);
|
||||
}
|
||||
BleCharacteristic(
|
||||
String uuid,
|
||||
this.properties,
|
||||
) : uuid = BleUuidParser.string(uuid);
|
||||
|
||||
BleCharacteristic.withMetaData({
|
||||
required String deviceId,
|
||||
required String serviceId,
|
||||
required String uuid,
|
||||
required this.properties,
|
||||
}) : uuid = BleUuidParser.string(uuid),
|
||||
metaData = (
|
||||
deviceId: deviceId,
|
||||
serviceId: BleUuidParser.string(serviceId),
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BleCharacteristic{uuid: $uuid, properties: $properties}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! BleCharacteristic) return false;
|
||||
if (other.uuid != uuid) return false;
|
||||
if (other.properties != properties) return false;
|
||||
if (other.metaData?.deviceId != metaData?.deviceId) return false;
|
||||
if (other.metaData?.serviceId != metaData?.serviceId) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => uuid.hashCode ^ properties.hashCode ^ metaData.hashCode;
|
||||
}
|
||||
|
||||
enum CharacteristicProperty {
|
||||
|
||||
@@ -4,7 +4,7 @@ class ManufacturerDataFilter {
|
||||
/// Must be of integer type, in hex or decimal form (e.g. 0x004c or 76).
|
||||
int companyIdentifier;
|
||||
|
||||
/// Matches as prefix the peripheral's advertised data.
|
||||
/// Matches as prefix the device's advertised data.
|
||||
Uint8List? payloadPrefix;
|
||||
|
||||
/// For each bit in the mask, set it to 1 if it needs to match
|
||||
|
||||
+120
-35
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:universal_ble/src/ble_command_queue.dart';
|
||||
import 'package:universal_ble/src/utils/ble_command_queue.dart';
|
||||
import 'package:universal_ble/src/universal_ble_linux/universal_ble_linux.dart';
|
||||
import 'package:universal_ble/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart';
|
||||
import 'package:universal_ble/src/universal_ble_web/universal_ble_web.dart';
|
||||
import 'package:universal_ble/src/universal_logger.dart';
|
||||
import 'package:universal_ble/src/utils/universal_logger.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
class UniversalBle {
|
||||
@@ -71,12 +71,11 @@ class UniversalBle {
|
||||
ScanFilter? scanFilter,
|
||||
PlatformConfig? platformConfig,
|
||||
}) async {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
return await _bleCommandQueue.queueCommandWithoutTimeout(
|
||||
() => _platform.startScan(
|
||||
scanFilter: scanFilter,
|
||||
platformConfig: platformConfig,
|
||||
),
|
||||
withTimeout: false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,9 +83,8 @@ class UniversalBle {
|
||||
/// Set [onScanResult] listener to `null` if you don't need it anymore.
|
||||
/// It might throw errors if Bluetooth is not available.
|
||||
static Future<void> stopScan() async {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
return await _bleCommandQueue.queueCommandWithoutTimeout(
|
||||
() => _platform.stopScan(),
|
||||
withTimeout: false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -157,29 +155,54 @@ class UniversalBle {
|
||||
}
|
||||
|
||||
/// Set a characteristic notifiable.
|
||||
/// Set `bleInputProperty` to [BleInputProperty.notification] or [BleInputProperty.indication].
|
||||
/// Updates will arrive in [onValueChange] listener.
|
||||
/// To stop listening to a characteristic, set `bleInputProperty` to [BleInputProperty.disabled].
|
||||
static Future<void> setNotifiable(
|
||||
/// Updates will arrive in [onValueChange] listener and [characteristicValueStream]
|
||||
/// call [unsubscribe] to stop updates
|
||||
static Future<void> subscribeNotifications(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
BleInputProperty bleInputProperty,
|
||||
) async {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
() => _platform.setNotifiable(
|
||||
deviceId,
|
||||
BleUuidParser.string(service),
|
||||
BleUuidParser.string(characteristic),
|
||||
bleInputProperty,
|
||||
),
|
||||
deviceId: deviceId,
|
||||
return _sendBleInputPropertyCommand(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
BleInputProperty.notification,
|
||||
);
|
||||
}
|
||||
|
||||
/// Set a characteristic notifiable.
|
||||
/// Updates will arrive in [onValueChange] listener and [characteristicValueStream]
|
||||
/// call [unsubscribe] to stop updates
|
||||
static Future<void> subscribeIndications(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
) async {
|
||||
return _sendBleInputPropertyCommand(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
BleInputProperty.indication,
|
||||
);
|
||||
}
|
||||
|
||||
/// Stop characteristic notifications/indication updates
|
||||
static Future<void> unsubscribe(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
) async {
|
||||
return _sendBleInputPropertyCommand(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
BleInputProperty.disabled,
|
||||
);
|
||||
}
|
||||
|
||||
/// Read a characteristic value.
|
||||
/// On iOS and MacOS this command will also trigger [onValueChange] listener.
|
||||
static Future<Uint8List> readValue(
|
||||
static Future<Uint8List> read(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic, {
|
||||
@@ -198,21 +221,23 @@ class UniversalBle {
|
||||
}
|
||||
|
||||
/// Write a characteristic value.
|
||||
/// To write a characteristic value with response, set `bleOutputProperty` to [BleOutputProperty.withResponse].
|
||||
static Future<void> writeValue(
|
||||
/// To write a characteristic value without response, set [withoutResponse] to [true].
|
||||
static Future<void> write(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
Uint8List value,
|
||||
BleOutputProperty bleOutputProperty,
|
||||
) async {
|
||||
Uint8List value, {
|
||||
bool withoutResponse = false,
|
||||
}) async {
|
||||
await _bleCommandQueue.queueCommand(
|
||||
() => _platform.writeValue(
|
||||
deviceId,
|
||||
BleUuidParser.string(service),
|
||||
BleUuidParser.string(characteristic),
|
||||
value,
|
||||
bleOutputProperty,
|
||||
withoutResponse
|
||||
? BleOutputProperty.withoutResponse
|
||||
: BleOutputProperty.withResponse,
|
||||
),
|
||||
deviceId: deviceId,
|
||||
);
|
||||
@@ -367,6 +392,66 @@ class UniversalBle {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use [subscribeNotifications] or [subscribeIndications] or [unsubscribe] instead")
|
||||
static Future<void> setNotifiable(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
BleInputProperty bleInputProperty,
|
||||
) async {
|
||||
return _sendBleInputPropertyCommand(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
bleInputProperty,
|
||||
);
|
||||
}
|
||||
|
||||
@Deprecated("Use [write] instead")
|
||||
static Future<void> writeValue(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
Uint8List value,
|
||||
BleOutputProperty bleOutputProperty,
|
||||
) async {
|
||||
await write(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
value,
|
||||
withoutResponse: bleOutputProperty == BleOutputProperty.withoutResponse,
|
||||
);
|
||||
}
|
||||
|
||||
@Deprecated("Use [read] instead")
|
||||
static Future<Uint8List> readValue(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic, {
|
||||
final Duration? timeout,
|
||||
}) {
|
||||
return read(deviceId, service, characteristic, timeout: timeout);
|
||||
}
|
||||
|
||||
static Future<void> _sendBleInputPropertyCommand(
|
||||
String deviceId,
|
||||
String service,
|
||||
String characteristic,
|
||||
BleInputProperty bleInputProperty,
|
||||
) async {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
() => _platform.setNotifiable(
|
||||
deviceId,
|
||||
BleUuidParser.string(service),
|
||||
BleUuidParser.string(characteristic),
|
||||
bleInputProperty,
|
||||
),
|
||||
deviceId: deviceId,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _connectAndExecuteBleCommand(
|
||||
String deviceId,
|
||||
BleCommand? bleCommand, {
|
||||
@@ -407,7 +492,7 @@ class UniversalBle {
|
||||
for (BleCharacteristic characteristic in service.characteristics) {
|
||||
if (characteristic.properties.contains(CharacteristicProperty.read)) {
|
||||
containsReadCharacteristics = true;
|
||||
await readValue(
|
||||
await read(
|
||||
deviceId,
|
||||
service.uuid,
|
||||
characteristic.uuid,
|
||||
@@ -447,12 +532,12 @@ class UniversalBle {
|
||||
}
|
||||
|
||||
// Check if BleCommand Supports Read or Write
|
||||
BleOutputProperty? bleOutputProperty;
|
||||
bool? withoutResponse;
|
||||
if (characteristic.properties.contains(CharacteristicProperty.write)) {
|
||||
bleOutputProperty = BleOutputProperty.withResponse;
|
||||
withoutResponse = false;
|
||||
} else if (characteristic.properties
|
||||
.contains(CharacteristicProperty.writeWithoutResponse)) {
|
||||
bleOutputProperty = BleOutputProperty.withoutResponse;
|
||||
withoutResponse = true;
|
||||
} else if (!characteristic.properties
|
||||
.contains(CharacteristicProperty.read)) {
|
||||
throw PairingException(
|
||||
@@ -463,17 +548,17 @@ class UniversalBle {
|
||||
Uint8List? value = bleCommand.writeValue;
|
||||
|
||||
try {
|
||||
if (value != null && bleOutputProperty != null) {
|
||||
await writeValue(
|
||||
if (value != null && withoutResponse != null) {
|
||||
await write(
|
||||
deviceId,
|
||||
bleCommand.service,
|
||||
bleCommand.characteristic,
|
||||
value,
|
||||
bleOutputProperty,
|
||||
withoutResponse: withoutResponse,
|
||||
);
|
||||
} else {
|
||||
// Fallback to read if supported
|
||||
await readValue(
|
||||
await read(
|
||||
deviceId,
|
||||
bleCommand.service,
|
||||
bleCommand.characteristic,
|
||||
@@ -497,7 +582,7 @@ class UniversalBle {
|
||||
static set onConnectionChange(OnConnectionChange? onConnectionChange) =>
|
||||
_platform.onConnectionChange = onConnectionChange;
|
||||
|
||||
/// Get characteristic value updates, set `bleInputProperty` in [setNotifiable] to [BleInputProperty.notification] or [BleInputProperty.indication].
|
||||
/// Get characteristic value updates, after calling [subscribeNotifications] or [subscribeIndications]
|
||||
static set onValueChange(OnValueChange? onValueChange) =>
|
||||
_platform.onValueChange = onValueChange;
|
||||
|
||||
|
||||
@@ -30,6 +30,24 @@ class WebBluetoothGloballyDisabled implements Exception {
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class NotFoundException implements Exception {}
|
||||
|
||||
class ServiceNotFoundException implements NotFoundException {
|
||||
String message;
|
||||
ServiceNotFoundException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class CharacteristicNotFoundException implements NotFoundException {
|
||||
String message;
|
||||
CharacteristicNotFoundException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
String _errorParser(dynamic error) {
|
||||
if (error == null) {
|
||||
return "Failed";
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'dart:async';
|
||||
import 'package:bluez/bluez.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:universal_ble/src/models/model_exports.dart';
|
||||
import 'package:universal_ble/src/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/src/utils/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/src/universal_ble_platform_interface.dart';
|
||||
import 'package:universal_ble/src/universal_logger.dart';
|
||||
import 'package:universal_ble/src/utils/universal_logger.dart';
|
||||
|
||||
class UniversalBleLinux extends UniversalBlePlatform {
|
||||
UniversalBleLinux._();
|
||||
@@ -185,14 +185,23 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
|
||||
List<BleService> services = [];
|
||||
for (final service in device.gattServices) {
|
||||
String serviceId = service.uuid.toString();
|
||||
|
||||
final characteristics = service.characteristics.map((e) {
|
||||
final properties = List<CharacteristicProperty>.from(e.flags
|
||||
.map((e) => e.toCharacteristicProperty())
|
||||
.where((element) => element != null)
|
||||
.toList());
|
||||
return BleCharacteristic(e.uuid.toString(), properties);
|
||||
return BleCharacteristic.withMetaData(
|
||||
deviceId: deviceId,
|
||||
serviceId: serviceId,
|
||||
uuid: e.uuid.toString(),
|
||||
properties: properties,
|
||||
);
|
||||
}).toList();
|
||||
services.add(BleService(service.uuid.toString(), characteristics));
|
||||
services.add(
|
||||
BleService(serviceId, characteristics),
|
||||
);
|
||||
}
|
||||
return services;
|
||||
}
|
||||
@@ -604,7 +613,7 @@ extension BlueZDeviceExtension on BlueZDevice {
|
||||
return BleDevice(
|
||||
name: name,
|
||||
deviceId: address,
|
||||
isPaired: paired,
|
||||
paired: paired,
|
||||
rssi: rssi,
|
||||
isSystemDevice: isSystemDevice,
|
||||
services: uuids.map((e) => e.toString()).toList(),
|
||||
|
||||
@@ -69,7 +69,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
await _channel.discoverServices(deviceId);
|
||||
return List<BleService>.from(universalBleServices
|
||||
.where((e) => e != null)
|
||||
.map((e) => e!.toBleService())
|
||||
.map((e) => e!.toBleService(deviceId))
|
||||
.toList());
|
||||
}
|
||||
|
||||
@@ -169,14 +169,16 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
}
|
||||
|
||||
extension _BleServiceExtension on UniversalBleService {
|
||||
BleService toBleService() {
|
||||
BleService toBleService(String deviceId) {
|
||||
List<BleCharacteristic> bleCharacteristics = [];
|
||||
for (UniversalBleCharacteristic? characteristic in characteristics ?? []) {
|
||||
if (characteristic == null) continue;
|
||||
List<int?>? properties = List<int?>.from(characteristic.properties);
|
||||
bleCharacteristics.add(BleCharacteristic(
|
||||
characteristic.uuid,
|
||||
List<CharacteristicProperty>.from(
|
||||
bleCharacteristics.add(BleCharacteristic.withMetaData(
|
||||
deviceId: deviceId,
|
||||
serviceId: uuid,
|
||||
uuid: characteristic.uuid,
|
||||
properties: List<CharacteristicProperty>.from(
|
||||
properties.map((e) => CharacteristicProperty.parse(e ?? 1)),
|
||||
),
|
||||
));
|
||||
@@ -228,7 +230,7 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult {
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
rssi: rssi,
|
||||
isPaired: isPaired,
|
||||
paired: isPaired,
|
||||
isSystemDevice: isSystemDevice,
|
||||
services: services?.map(BleUuidParser.string).toList() ?? [],
|
||||
manufacturerDataList: manufacturerDataList
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:universal_ble/src/universal_ble_stream_controller.dart';
|
||||
import 'package:universal_ble/src/utils/cache_handler.dart';
|
||||
import 'package:universal_ble/src/utils/universal_ble_stream_controller.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
abstract class UniversalBlePlatform {
|
||||
@@ -122,6 +123,10 @@ abstract class UniversalBlePlatform {
|
||||
try {
|
||||
onConnectionChange?.call(deviceId, isConnected, error);
|
||||
} catch (_) {}
|
||||
|
||||
if (!isConnected) {
|
||||
CacheHandler.instance.resetDeviceCache(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
void updateCharacteristicValue(
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_web_bluetooth/flutter_web_bluetooth.dart';
|
||||
import 'package:universal_ble/src/universal_logger.dart';
|
||||
import 'package:universal_ble/src/utils/universal_logger.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
class UniversalBleWeb extends UniversalBlePlatform {
|
||||
@@ -58,7 +58,9 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
|
||||
@override
|
||||
Future<List<BleService>> discoverServices(String deviceId) async =>
|
||||
(await _getServices(deviceId)).map((e) => e._bleService).toList();
|
||||
(await _getServices(deviceId))
|
||||
.map((e) => e._bleService(deviceId))
|
||||
.toList();
|
||||
|
||||
@override
|
||||
Future<AvailabilityState> getBluetoothAvailabilityState() async {
|
||||
@@ -463,20 +465,24 @@ class _UniversalWebBluetoothService {
|
||||
return null;
|
||||
}
|
||||
|
||||
BleService get _bleService => BleService(
|
||||
BleService _bleService(String deviceId) => BleService(
|
||||
service.uuid,
|
||||
characteristics.map((e) {
|
||||
return BleCharacteristic(e.uuid, [
|
||||
if (e.properties.broadcast) CharacteristicProperty.broadcast,
|
||||
if (e.properties.read) CharacteristicProperty.read,
|
||||
if (e.properties.write) CharacteristicProperty.write,
|
||||
if (e.properties.writeWithoutResponse)
|
||||
CharacteristicProperty.writeWithoutResponse,
|
||||
if (e.properties.notify) CharacteristicProperty.notify,
|
||||
if (e.properties.indicate) CharacteristicProperty.indicate,
|
||||
if (e.properties.authenticatedSignedWrites)
|
||||
CharacteristicProperty.authenticatedSignedWrites,
|
||||
]);
|
||||
return BleCharacteristic.withMetaData(
|
||||
deviceId: deviceId,
|
||||
serviceId: service.uuid,
|
||||
uuid: e.uuid,
|
||||
properties: [
|
||||
if (e.properties.broadcast) CharacteristicProperty.broadcast,
|
||||
if (e.properties.read) CharacteristicProperty.read,
|
||||
if (e.properties.write) CharacteristicProperty.write,
|
||||
if (e.properties.writeWithoutResponse)
|
||||
CharacteristicProperty.writeWithoutResponse,
|
||||
if (e.properties.notify) CharacteristicProperty.notify,
|
||||
if (e.properties.indicate) CharacteristicProperty.indicate,
|
||||
if (e.properties.authenticatedSignedWrites)
|
||||
CharacteristicProperty.authenticatedSignedWrites,
|
||||
]);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,16 +13,28 @@ class BleCommandQueue {
|
||||
|
||||
Future<T> queueCommand<T>(
|
||||
Future<T> Function() command, {
|
||||
bool withTimeout = true,
|
||||
String? deviceId,
|
||||
Duration? timeout,
|
||||
}) {
|
||||
Duration? duration = timeout ?? (withTimeout ? this.timeout : null);
|
||||
Duration? timeoutDuration = timeout ?? this.timeout;
|
||||
if (timeoutDuration == null) {
|
||||
return queueCommandWithoutTimeout(command, deviceId: deviceId);
|
||||
}
|
||||
return switch (queueType) {
|
||||
QueueType.global => _queue().add(command, duration),
|
||||
QueueType.perDevice => _queue(deviceId).add(command, duration),
|
||||
QueueType.none =>
|
||||
duration != null ? command().timeout(duration) : command(),
|
||||
QueueType.global => _queue().add(command, timeoutDuration),
|
||||
QueueType.perDevice => _queue(deviceId).add(command, timeoutDuration),
|
||||
QueueType.none => command().timeout(timeoutDuration),
|
||||
};
|
||||
}
|
||||
|
||||
Future<T> queueCommandWithoutTimeout<T>(
|
||||
Future<T> Function() command, {
|
||||
String? deviceId,
|
||||
}) {
|
||||
return switch (queueType) {
|
||||
QueueType.global => _queue().add(command),
|
||||
QueueType.perDevice => _queue(deviceId).add(command),
|
||||
QueueType.none => command(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:universal_ble/src/models/model_exports.dart';
|
||||
|
||||
/// Manages an in-memory cache for Bluetooth devices
|
||||
class CacheHandler {
|
||||
static CacheHandler? _instance;
|
||||
static CacheHandler get instance => _instance ??= CacheHandler._();
|
||||
CacheHandler._();
|
||||
|
||||
/// Internal cache to store discovered services for each device.
|
||||
final Map<String, List<BleService>> _servicesCache = {};
|
||||
|
||||
/// Saves the discovered Bluetooth services for a specific device in the cache.
|
||||
void saveServices(String deviceId, List<BleService>? services) {
|
||||
if (services == null) {
|
||||
_servicesCache.remove(deviceId);
|
||||
} else {
|
||||
_servicesCache[deviceId] = services;
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the cached Bluetooth services for a specific device.
|
||||
List<BleService>? getServices(String deviceId) => _servicesCache[deviceId];
|
||||
|
||||
/// Resets the cache for a specific device, removing all stored services.
|
||||
void resetDeviceCache(String deviceId) {
|
||||
_servicesCache.remove(deviceId);
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export 'package:universal_ble/src/universal_ble_exceptions.dart';
|
||||
export 'package:universal_ble/src/universal_ble_platform_interface.dart';
|
||||
export 'package:universal_ble/src/universal_ble.dart';
|
||||
export 'package:universal_ble/src/models/model_exports.dart';
|
||||
export 'package:universal_ble/src/extensions/exports.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_ble/src/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/src/utils/universal_ble_filter_util.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_ble/src/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/src/utils/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_ble/src/universal_ble_stream_controller.dart';
|
||||
import 'package:universal_ble/src/utils/universal_ble_stream_controller.dart';
|
||||
|
||||
void main() {
|
||||
group("Test UniversalBleStreamController", () {
|
||||
|
||||
Reference in New Issue
Block a user