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:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user