Unify UUID formatting (#55)
* Unify UUID formatting * Update Readme * Update Readme * Apply suggestions from code review Co-authored-by: Foti Dim <foti@navideck.com> * Resolve Comments * Add test and support 32bit uuid parsing * Add a toString() method to the BleDevice class for easier debugging and logging * Rename Uuid to BleUuid * Rename uuid file to ble_uuid * Update Readme --------- Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
@@ -101,6 +101,7 @@ Already connected devices, connected either through previous sessions, other app
|
||||
// On `Apple`, `withServices` is required to get connected devices, else [1800] service will be used as default filter.
|
||||
List<BleDevice> devices = await UniversalBle.getSystemDevices(withServices: []);
|
||||
```
|
||||
|
||||
For each such device the `isSystemDevice` property will be `true`.
|
||||
|
||||
You still need to explicitly [connect](#connecting) to them before being able to use them.
|
||||
@@ -259,6 +260,33 @@ UniversalBle.timeout = const Duration(seconds: 10);
|
||||
UniversalBle.timeout = null;
|
||||
```
|
||||
|
||||
## UUID format
|
||||
|
||||
All characteristic and service UUIDs will be returned in lowercase and in 128 bit format, across all platforms.
|
||||
e.g. `0000180a-0000-1000-8000-00805f9b34fb`
|
||||
|
||||
When passing a UUID you can pass it in any character case or format (long/short) you want.
|
||||
|
||||
You can use `BleUuid.parse()` to convert a string to 128 bit UUID format. For example:
|
||||
|
||||
```dart
|
||||
BleUuid.parse("180A"); // "0000180a-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
BleUuid.parse("0000180A-0000-1000-8000-00805F9B34FB"); // "0000180a-0000-1000-8000-00805f9b34fb"
|
||||
```
|
||||
|
||||
or `BleUuid.extend()` to create a valid 128 bit Bluetooth UUID from short (16 or 32 bit) encoding. For example:
|
||||
|
||||
```dart
|
||||
BleUuid.extend(0x180A); // "0000180a-0000-1000-8000-00805f9b34fb"
|
||||
```
|
||||
|
||||
or `BleUuid.equals()` to compare two UUIDs. For example:
|
||||
|
||||
```dart
|
||||
BleUuid.equals("180a","0000180A-0000-1000-8000-00805F9B34FB"); // true
|
||||
```
|
||||
|
||||
## Platform-Specific Setup
|
||||
|
||||
### Android
|
||||
|
||||
@@ -31,6 +31,19 @@ class BleDevice {
|
||||
this.manufacturerDataHead = manufacturerDataHead ?? Uint8List.fromList([]);
|
||||
this.manufacturerData = manufacturerData ?? manufacturerDataHead;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BleDevice: '
|
||||
'deviceId: $deviceId, '
|
||||
'name: $name, '
|
||||
'rssi: $rssi, '
|
||||
'isPaired: $isPaired, '
|
||||
'services: $services, '
|
||||
'isSystemDevice: $isSystemDevice, '
|
||||
'manufacturerDataHead: $manufacturerDataHead, '
|
||||
'manufacturerData: $manufacturerData';
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the manufacturer data of a BLE device.
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
class BleService {
|
||||
String uuid;
|
||||
late String uuid;
|
||||
List<BleCharacteristic> characteristics;
|
||||
BleService(this.uuid, this.characteristics);
|
||||
|
||||
BleService(String uuid, this.characteristics) {
|
||||
this.uuid = BleUuid.parse(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
class BleCharacteristic {
|
||||
String uuid;
|
||||
late String uuid;
|
||||
List<CharacteristicProperty> properties;
|
||||
BleCharacteristic(this.uuid, this.properties);
|
||||
|
||||
BleCharacteristic(String uuid, this.properties) {
|
||||
this.uuid = BleUuid.parse(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
enum CharacteristicProperty {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:bluez/bluez.dart';
|
||||
|
||||
class BleUuid {
|
||||
/// Parse a String to valid UUID and convert a 16 bit UUID to 128 bit UUID
|
||||
/// Throws `FormatException` if the UUID is invalid
|
||||
static String parse(String uuid) {
|
||||
if (uuid.length < 4) throw const FormatException("Invalid UUID");
|
||||
if (uuid.length <= 8) {
|
||||
uuid = "${uuid.padLeft(8, '0')}-0000-1000-8000-00805f9b34fb";
|
||||
} else if (!uuid.contains("-")) {
|
||||
if (uuid.length != 32) throw const FormatException("Invalid UUID");
|
||||
uuid = "${uuid.substring(0, 8)}-${uuid.substring(8, 12)}"
|
||||
"-${uuid.substring(12, 16)}-${uuid.substring(16, 20)}-${uuid.substring(20, 32)}";
|
||||
}
|
||||
return BlueZUUID.fromString(uuid).toString();
|
||||
}
|
||||
|
||||
/// Parse 16/32 bit uuid like `0x1800` to 128 bit uuid like `00001800-0000-1000-8000-00805f9b34fb`
|
||||
static String extend(int short) {
|
||||
BlueZUUID blueZUUID = BlueZUUID.short(short);
|
||||
return blueZUUID.toString();
|
||||
}
|
||||
|
||||
/// Compare two UUIDs to automatically convert both to 128 bit UUIDs
|
||||
/// Throws `FormatException` if the UUID is invalid
|
||||
static bool equals(String uuid1, String uuid2) {
|
||||
return parse(uuid1) == parse(uuid2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a list of strings to a list of UUIDs
|
||||
extension StringListToUUID on List<String> {
|
||||
List<String> toValidUUIDList() => map(BleUuid.parse).toList();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export 'package:universal_ble/src/models/queue_type.dart';
|
||||
export 'package:universal_ble/src/models/uuid.dart';
|
||||
export 'package:universal_ble/src/models/ble_uuid.dart';
|
||||
export 'package:universal_ble/src/models/scan_filter.dart';
|
||||
export 'package:universal_ble/src/models/ble_property.dart';
|
||||
export 'package:universal_ble/src/models/ble_service.dart';
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:bluez/bluez.dart';
|
||||
|
||||
class UUID {
|
||||
late String value;
|
||||
|
||||
UUID(String uuid) {
|
||||
// To validate the short UUID
|
||||
if (uuid.length <= 4) {
|
||||
try {
|
||||
int shortValue = int.parse(uuid, radix: 16);
|
||||
value = BlueZUUID.short(shortValue).toString();
|
||||
return;
|
||||
} catch (_) {}
|
||||
}
|
||||
// To validate the UUID
|
||||
BlueZUUID.fromString(uuid);
|
||||
value = uuid;
|
||||
}
|
||||
|
||||
factory UUID.fromShort(int short) {
|
||||
BlueZUUID blueZUUID = BlueZUUID.short(short);
|
||||
return UUID(blueZUUID.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a list of strings to a list of UUIDs
|
||||
extension StringListToUUID on List<String> {
|
||||
List<String> toValidUUIDList() => map((e) => UUID(e).value).toList();
|
||||
}
|
||||
@@ -111,8 +111,8 @@ class UniversalBle {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
() => _platform.setNotifiable(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
BleUuid.parse(service),
|
||||
BleUuid.parse(characteristic),
|
||||
bleInputProperty,
|
||||
),
|
||||
deviceId: deviceId,
|
||||
@@ -127,7 +127,11 @@ class UniversalBle {
|
||||
String characteristic,
|
||||
) async {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
() => _platform.readValue(deviceId, service, characteristic),
|
||||
() => _platform.readValue(
|
||||
deviceId,
|
||||
BleUuid.parse(service),
|
||||
BleUuid.parse(characteristic),
|
||||
),
|
||||
deviceId: deviceId,
|
||||
);
|
||||
}
|
||||
@@ -144,8 +148,8 @@ class UniversalBle {
|
||||
await _bleCommandQueue.queueCommand(
|
||||
() => _platform.writeValue(
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
BleUuid.parse(service),
|
||||
BleUuid.parse(characteristic),
|
||||
value,
|
||||
bleOutputProperty,
|
||||
),
|
||||
|
||||
@@ -192,7 +192,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
for (String property in properties) {
|
||||
switch (property) {
|
||||
case BluezProperty.value:
|
||||
onValueChange?.call(
|
||||
updateCharacteristicValue(
|
||||
deviceId,
|
||||
characteristic,
|
||||
Uint8List.fromList(char.value),
|
||||
|
||||
@@ -132,7 +132,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
onConnectionChange?.call(deviceId, connected),
|
||||
valueChanged:
|
||||
(String deviceId, String characteristicId, Uint8List value) =>
|
||||
onValueChange?.call(deviceId, characteristicId, value),
|
||||
updateCharacteristicValue(deviceId, characteristicId, value),
|
||||
pairStateChange: (String deviceId, bool isPaired, String? error) =>
|
||||
onPairingStateChange?.call(deviceId, isPaired, error),
|
||||
));
|
||||
@@ -209,7 +209,7 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult {
|
||||
isSystemDevice: isSystemDevice,
|
||||
services: services
|
||||
?.where((e) => e != null)
|
||||
.map((e) => UUID(e!).toString())
|
||||
.map((e) => BleUuid.parse(e!))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -50,6 +50,7 @@ abstract class UniversalBlePlatform {
|
||||
List<String>? withServices,
|
||||
);
|
||||
|
||||
/// `onScanResult` interceptor to filter by name
|
||||
void updateScanResult(BleDevice bleDevice) {
|
||||
// Filter by name
|
||||
ScanFilter? scanFilter = _scanFilter;
|
||||
@@ -61,6 +62,12 @@ abstract class UniversalBlePlatform {
|
||||
onScanResult?.call(bleDevice);
|
||||
}
|
||||
|
||||
/// `onValueChange` interceptor to parse the native uuids to 128 bit uuid, to keep consistency
|
||||
void updateCharacteristicValue(
|
||||
String deviceId, String characteristicId, Uint8List value) {
|
||||
onValueChange?.call(deviceId, BleUuid.parse(characteristicId), value);
|
||||
}
|
||||
|
||||
OnAvailabilityChange? onAvailabilityChange;
|
||||
OnScanResult? onScanResult;
|
||||
OnConnectionChange? onConnectionChange;
|
||||
|
||||
@@ -21,7 +21,6 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
|
||||
@override
|
||||
Future<BleConnectionState> getConnectionState(String deviceId) async {
|
||||
// TODO: Test this on Web (All platforms)
|
||||
BluetoothDevice? device = _getDeviceById(deviceId);
|
||||
bool connected = await device?.connected.first ?? false;
|
||||
return connected
|
||||
@@ -214,7 +213,7 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
_characteristicStreamList[characteristicKey] = bleCharacteristic.value
|
||||
.map((event) => event.buffer.asUint8List())
|
||||
.listen((event) {
|
||||
onValueChange?.call(deviceId, characteristic, event);
|
||||
updateCharacteristicValue(deviceId, characteristic, event);
|
||||
});
|
||||
}
|
||||
// Cancel Notification
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
void main() {
|
||||
group("UUID", () {
|
||||
test("Invalid UUID", () {
|
||||
expect(
|
||||
() => BleUuid.parse('0x0180a'),
|
||||
throwsFormatException,
|
||||
);
|
||||
expect(
|
||||
() => BleUuid.parse('0000-180a-0000-1000-8000-0080-5f9b-34fb'),
|
||||
throwsFormatException,
|
||||
);
|
||||
});
|
||||
|
||||
test("Valid UUID", () {
|
||||
// Parse 128-bit lowercase uuid
|
||||
expect(
|
||||
BleUuid.parse('0000180a-0000-1000-8000-00805f9b34fb'),
|
||||
equals("0000180a-0000-1000-8000-00805f9b34fb"),
|
||||
);
|
||||
// Parse 128-bit uppercase uuid
|
||||
expect(
|
||||
BleUuid.parse('0000180A-0000-1000-8000-00805F9B34FB'),
|
||||
equals("0000180a-0000-1000-8000-00805f9b34fb"),
|
||||
);
|
||||
// Parse 16-bit uuid string to 128-bit uuid string
|
||||
expect(
|
||||
BleUuid.parse("180a"),
|
||||
equals("0000180a-0000-1000-8000-00805f9b34fb"),
|
||||
);
|
||||
// Parse 16-bit uuid to 128-bit
|
||||
expect(
|
||||
BleUuid.extend(0x180A),
|
||||
equals("0000180a-0000-1000-8000-00805f9b34fb"),
|
||||
);
|
||||
// 32-bit UUID
|
||||
expect(
|
||||
BleUuid.parse("0000180a"),
|
||||
equals("0000180a-0000-1000-8000-00805f9b34fb"),
|
||||
);
|
||||
// UUID without dashes
|
||||
expect(
|
||||
BleUuid.parse('0000180a00001000800000805f9b34fb'),
|
||||
equals("0000180a-0000-1000-8000-00805f9b34fb"),
|
||||
);
|
||||
});
|
||||
|
||||
test("Compare UUID", () {
|
||||
// Compare UUID strings, case and format insensitive
|
||||
expect(
|
||||
BleUuid.equals('0000180a-0000-1000-8000-00805f9b34fb', '180a'),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
BleUuid.equals('180A', '180a'),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
BleUuid.equals('0000180A00001000800000805F9B34FB', '180a'),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
BleUuid.equals('0000180A', '0000180a-0000-1000-8000-00805f9b34fb'),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user