From 3bfcb68d7f3336791815fbe52268a8d7b4a3a054 Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Sun, 6 Oct 2024 11:30:34 +0800 Subject: [PATCH 1/7] feat: uint8list instead of list of int --- README.md | 8 +- .../FlutterBluePlusPlugin.java | 67 +++++++---------- example/lib/widgets/characteristic_tile.dart | 12 +-- example/lib/widgets/descriptor_tile.dart | 9 ++- example/lib/widgets/scan_result_tile.dart | 7 +- ios/Classes/FlutterBluePlusPlugin.m | 75 +++++-------------- lib/src/bluetooth_characteristic.dart | 18 ++--- lib/src/bluetooth_descriptor.dart | 16 ++-- lib/src/bluetooth_events.dart | 8 +- lib/src/bluetooth_msgs.dart | 61 ++++++--------- lib/src/flutter_blue_plus.dart | 26 +++---- lib/src/guid.dart | 10 +-- lib/src/utils.dart | 33 +++----- 13 files changed, 139 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index 52da7eb4..fda19ee3 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,7 @@ services.forEach((service) { var characteristics = service.characteristics; for(BluetoothCharacteristic c in characteristics) { if (c.properties.read) { - List value = await c.read(); + Uint8List value = await c.read(); print(value); } } @@ -340,10 +340,10 @@ import 'dart:math'; // 2. it can only be used *with* response to avoid data loss // 3. The characteristic must be designed to support split data extension splitWrite on BluetoothCharacteristic { - Future splitWrite(List value, {int timeout = 15}) async { + Future splitWrite(Uint8List value, {int timeout = 15}) async { int chunk = min(device.mtuNow - 3, 512); // 3 bytes BLE overhead, 512 bytes max for (int i = 0; i < value.length; i += chunk) { - List subvalue = value.sublist(i, min(i + chunk, value.length)); + Uint8List subvalue = value.sublist(i, min(i + chunk, value.length)); await write(subvalue, withoutResponse:false, timeout: timeout); } } @@ -398,7 +398,7 @@ await characteristic.setNotifyValue(true); // Reads all descriptors var descriptors = characteristic.descriptors; for(BluetoothDescriptor d in descriptors) { - List value = await d.read(); + Uint8List value = await d.read(); print(value); } diff --git a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java index 27869317..b91f9795 100644 --- a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java +++ b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java @@ -106,8 +106,8 @@ public class FlutterBluePlusPlugin implements private final Map mBondingDevices = new ConcurrentHashMap<>(); private final Map mMtu = new ConcurrentHashMap<>(); private final Map mAutoConnected = new ConcurrentHashMap<>(); - private final Map mWriteChr = new ConcurrentHashMap<>(); - private final Map mWriteDesc = new ConcurrentHashMap<>(); + private final Map mWriteChr = new ConcurrentHashMap<>(); + private final Map mWriteDesc = new ConcurrentHashMap<>(); private final Map mAdvSeen = new ConcurrentHashMap<>(); private final Map mScanCounts = new ConcurrentHashMap<>(); private HashMap mScanFilters = new HashMap(); @@ -372,7 +372,7 @@ public void onMethodCall(@NonNull MethodCall call, break; } - case "getAdapterName": + case "getAdapterName": { ArrayList permissions = new ArrayList<>(); @@ -580,8 +580,8 @@ public void onMethodCall(@NonNull MethodCall call, for (int i = 0; i < withMsd.size(); i++) { HashMap m = (HashMap) withMsd.get(i); int id = (int) m.get("manufacturer_id"); - byte[] mdata = hexToBytes((String) m.get("data")); - byte[] mask = hexToBytes((String) m.get("mask")); + byte[] mdata = (byte[]) m.get("data"); + byte[] mask = (byte[]) m.get("mask"); ScanFilter f = null; if (mask.length == 0) { f = new ScanFilter.Builder().setManufacturerData(id, mdata).build(); @@ -594,9 +594,9 @@ public void onMethodCall(@NonNull MethodCall call, // service data for (int i = 0; i < withServiceData.size(); i++) { HashMap m = (HashMap) withServiceData.get(i); - ParcelUuid s = ParcelUuid.fromString(uuid128((String) m.get("service"))); - byte[] mdata = hexToBytes((String) m.get("data")); - byte[] mask = hexToBytes((String) m.get("mask")); + ParcelUuid s = ParcelUuid.fromString((String) m.get("service")); + byte[] mdata = (byte[]) m.get("data"); + byte[] mask = (byte[]) m.get("mask"); ScanFilter f = null; if (mask.length == 0) { f = new ScanFilter.Builder().setServiceData(s, mdata).build(); @@ -894,7 +894,7 @@ public void onMethodCall(@NonNull MethodCall call, String serviceUuid = (String) data.get("service_uuid"); String characteristicUuid = (String) data.get("characteristic_uuid"); String primaryServiceUuid = (String) data.get("primary_service_uuid"); - String value = (String) data.get("value"); + String value = (byte[]) data.get("value"); int writeTypeInt = (int) data.get("write_type"); boolean allowLongWrite = ((int) data.get("allow_long_write")) != 0; @@ -938,7 +938,7 @@ public void onMethodCall(@NonNull MethodCall call, // check maximum payload int maxLen = getMaxPayload(remoteId, writeType, allowLongWrite); - int dataLen = hexToBytes(value).length; + int dataLen = value.length; if (dataLen > maxLen) { String a = writeTypeInt == 0 ? "withResponse" : "withoutResponse"; String b = writeTypeInt == 0 ? (allowLongWrite ? ", allowLongWrite" : ", noLongWrite") : ""; @@ -955,7 +955,7 @@ public void onMethodCall(@NonNull MethodCall call, // write characteristic if (Build.VERSION.SDK_INT >= 33) { // Android 13 (August 2022) - int rv = gatt.writeCharacteristic(characteristic, hexToBytes(value), writeType); + int rv = gatt.writeCharacteristic(characteristic, value, writeType); if (rv != BluetoothStatusCodes.SUCCESS) { String s = "gatt.writeCharacteristic() returned " + rv + " : " + bluetoothStatusString(rv); @@ -965,7 +965,7 @@ public void onMethodCall(@NonNull MethodCall call, } else { // set value - if(!characteristic.setValue(hexToBytes(value))) { + if(!characteristic.setValue(value)) { result.error("writeCharacteristic", "characteristic.setValue() returned false", null); break; } @@ -1040,7 +1040,7 @@ public void onMethodCall(@NonNull MethodCall call, String characteristicUuid = (String) data.get("characteristic_uuid"); String descriptorUuid = (String) data.get("descriptor_uuid"); String primaryServiceUuid = (String) data.get("primary_service_uuid"); - String value = (String) data.get("value"); + String value = (byte) data.get("value"); // check connection BluetoothGatt gatt = mConnectedDevices.get(remoteId); @@ -1071,9 +1071,9 @@ public void onMethodCall(@NonNull MethodCall call, // check mtu int mtu = mMtu.get(remoteId); - if ((mtu-3) < hexToBytes(value).length) { + if ((mtu-3) < value.length) { String s = "data longer than mtu allows. dataLength: " + - hexToBytes(value).length + "> max: " + (mtu-3); + value.length + "> max: " + (mtu-3); result.error("writeDescriptor", s, null); break; } @@ -1086,7 +1086,7 @@ public void onMethodCall(@NonNull MethodCall call, // write descriptor if (Build.VERSION.SDK_INT >= 33) { // Android 13 (August 2022) - int rv = gatt.writeDescriptor(descriptor, hexToBytes(value)); + int rv = gatt.writeDescriptor(descriptor, value); if (rv != BluetoothStatusCodes.SUCCESS) { String s = "gatt.writeDescriptor() returned " + rv + " : " + bluetoothStatusString(rv); result.error("writeDescriptor", s, null); @@ -1096,7 +1096,7 @@ public void onMethodCall(@NonNull MethodCall call, } else { // Set descriptor - if(!descriptor.setValue(hexToBytes(value))){ + if(!descriptor.setValue(value)){ result.error("writeDescriptor", "descriptor.setValue() returned false", null); break; } @@ -1194,7 +1194,7 @@ public void onMethodCall(@NonNull MethodCall call, // remember the data we are writing if (primaryServiceUuid == null) {primaryServiceUuid = "";} String key = remoteId + ":" + serviceUuid + ":" + characteristicUuid + ":" + CCCD + ":" + primaryServiceUuid; - mWriteDesc.put(key, bytesToHex(descriptorValue)); + mWriteDesc.put(key, descriptorValue); // write descriptor if (Build.VERSION.SDK_INT >= 33) { // Android 13 (August 2022) @@ -2299,7 +2299,7 @@ public void onCharacteristicReceived(BluetoothGatt gatt, BluetoothGattCharacteri response.put("remote_id", gatt.getDevice().getAddress()); response.put("service_uuid", uuidStr(characteristic.getService().getUuid())); response.put("characteristic_uuid", uuidStr(characteristic.getUuid())); - response.put("value", bytesToHex(value)); + response.put("value", value); response.put("success", status == BluetoothGatt.GATT_SUCCESS ? 1 : 0); response.put("error_code", status); response.put("error_string", gattErrorString(status)); @@ -2357,7 +2357,7 @@ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristi // what data did we write? String key = remoteId + ":" + serviceUuid + ":" + characteristicUuid + ":" + primaryServiceUuid; - String value = mWriteChr.get(key) != null ? mWriteChr.get(key) : ""; + []byte value = mWriteChr.get(key) != null ? mWriteChr.get(key) : new byte[0]; mWriteChr.remove(key); // see: BmCharacteristicData @@ -2395,7 +2395,7 @@ public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descrip response.put("service_uuid", uuidStr(descriptor.getCharacteristic().getService().getUuid())); response.put("characteristic_uuid", uuidStr(descriptor.getCharacteristic().getUuid())); response.put("descriptor_uuid", uuidStr(descriptor.getUuid())); - response.put("value", bytesToHex(value)); + response.put("value", value); response.put("success", status == BluetoothGatt.GATT_SUCCESS ? 1 : 0); response.put("error_code", status); response.put("error_string", gattErrorString(status)); @@ -2427,7 +2427,7 @@ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descri // what data did we write? String key = remoteId + ":" + serviceUuid + ":" + characteristicUuid + ":" + descriptorUuid + ":" + primaryServiceUuid; - String value = mWriteDesc.get(key) != null ? mWriteDesc.get(key) : ""; + byte[] value = mWriteDesc.get(key) != null ? mWriteDesc.get(key) : new byte[0]; mWriteDesc.remove(key); // see: BmDescriptorData @@ -2567,20 +2567,20 @@ HashMap bmScanAdvertisement(BluetoothDevice device, ScanResult r Map serviceData = adv != null ? adv.getServiceData() : null; // Manufacturer Specific Data - HashMap manufDataB = new HashMap<>(); + HashMap manufDataB = new HashMap<>(); if (manufData != null) { for (Map.Entry entry : manufData.entrySet()) { - manufDataB.put(entry.getKey(), bytesToHex(entry.getValue())); + manufDataB.put(entry.getKey(), entry.getValue()); } } // Service Data - HashMap serviceDataB = new HashMap<>(); + HashMap serviceDataB = new HashMap<>(); if (serviceData != null) { for (Map.Entry entry : serviceData.entrySet()) { ParcelUuid key = entry.getKey(); byte[] value = entry.getValue(); - serviceDataB.put(uuidStr(key.getUuid()), bytesToHex(value)); + serviceDataB.put(uuidStr(key.getUuid()), value); } } @@ -2806,21 +2806,6 @@ private boolean isAdapterOn() } } - private static byte[] hexToBytes(String s) { - if (s == null) { - return new byte[0]; - } - int len = s.length(); - byte[] data = new byte[len / 2]; - - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i+1), 16)); - } - - return data; - } - private static String bytesToHex(byte[] bytes) { if (bytes == null) { return ""; diff --git a/example/lib/widgets/characteristic_tile.dart b/example/lib/widgets/characteristic_tile.dart index 8c357245..c0c949f2 100644 --- a/example/lib/widgets/characteristic_tile.dart +++ b/example/lib/widgets/characteristic_tile.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'dart:math'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import "../utils/snackbar.dart"; - import "descriptor_tile.dart"; class CharacteristicTile extends StatefulWidget { @@ -19,9 +19,9 @@ class CharacteristicTile extends StatefulWidget { } class _CharacteristicTileState extends State { - List _value = []; + Uint8List _value = Uint8List(0); - late StreamSubscription> _lastValueSubscription; + late StreamSubscription _lastValueSubscription; @override void initState() { @@ -42,9 +42,9 @@ class _CharacteristicTileState extends State { BluetoothCharacteristic get c => widget.characteristic; - List _getRandomBytes() { + Uint8List _getRandomBytes() { final math = Random(); - return [math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]; + return Uint8List.fromList([math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]); } Future onReadPressed() async { @@ -134,7 +134,7 @@ class _CharacteristicTileState extends State { Widget buildButtonRow(BuildContext context) { bool read = widget.characteristic.properties.read; - bool write = widget.characteristic.properties.write; + bool write = widget.characteristic.properties.write || widget.characteristic.properties.writeWithoutResponse; bool notify = widget.characteristic.properties.notify; bool indicate = widget.characteristic.properties.indicate; return Row( diff --git a/example/lib/widgets/descriptor_tile.dart b/example/lib/widgets/descriptor_tile.dart index 0b078e34..f9e8b388 100644 --- a/example/lib/widgets/descriptor_tile.dart +++ b/example/lib/widgets/descriptor_tile.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:math'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -16,9 +17,9 @@ class DescriptorTile extends StatefulWidget { } class _DescriptorTileState extends State { - List _value = []; + Uint8List _value = Uint8List(0); - late StreamSubscription> _lastValueSubscription; + late StreamSubscription _lastValueSubscription; @override void initState() { @@ -39,9 +40,9 @@ class _DescriptorTileState extends State { BluetoothDescriptor get d => widget.descriptor; - List _getRandomBytes() { + Uint8List _getRandomBytes() { final math = Random(); - return [math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]; + return Uint8List.fromList([math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]); } Future onReadPressed() async { diff --git a/example/lib/widgets/scan_result_tile.dart b/example/lib/widgets/scan_result_tile.dart index 084ab04d..f37a4133 100644 --- a/example/lib/widgets/scan_result_tile.dart +++ b/example/lib/widgets/scan_result_tile.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -36,15 +37,15 @@ class _ScanResultTileState extends State { super.dispose(); } - String getNiceHexArray(List bytes) { + String getNiceHexArray(Uint8List bytes) { return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]'; } - String getNiceManufacturerData(List> data) { + String getNiceManufacturerData(List data) { return data.map((val) => '${getNiceHexArray(val)}').join(', ').toUpperCase(); } - String getNiceServiceData(Map> data) { + String getNiceServiceData(Map data) { return data.entries.map((v) => '${v.key}: ${getNiceHexArray(v.value)}').join(', ').toUpperCase(); } diff --git a/ios/Classes/FlutterBluePlusPlugin.m b/ios/Classes/FlutterBluePlusPlugin.m index c4f407f0..ec9182ad 100644 --- a/ios/Classes/FlutterBluePlusPlugin.m +++ b/ios/Classes/FlutterBluePlusPlugin.m @@ -495,7 +495,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result NSString *primaryServiceUuid = args[@"primary_service_uuid"]; NSNumber *writeTypeNumber = args[@"write_type"]; NSNumber *allowLongWrite = args[@"allow_long_write"]; - NSString *value = args[@"value"]; + NSData *value = args[@"value"]; // Find peripheral CBPeripheral *peripheral = [self getConnectedPeripheral:remoteId]; @@ -513,7 +513,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result // check maximum payload int maxLen = [self getMaxPayload:peripheral forType:writeType allowLongWrite:[allowLongWrite boolValue]]; - int dataLen = (int) [self convertHexToData:value].length; + int dataLen = value.length; if (dataLen > maxLen) { NSString* t = [writeTypeNumber intValue] == 0 ? @"withResponse" : @"withoutResponse"; NSString* a = [allowLongWrite boolValue] ? @", allowLongWrite" : @", noLongWrite"; @@ -565,7 +565,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self.writeChrs setObject:value forKey:key]; // Write to characteristic - [peripheral writeValue:[self convertHexToData:value] forCharacteristic:characteristic type:writeType]; + [peripheral writeValue:value forCharacteristic:characteristic type:writeType]; // remember the most recent write withoutResponse if (writeType == CBCharacteristicWriteWithoutResponse) { @@ -624,7 +624,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result NSString *characteristicUuid = args[@"characteristic_uuid"]; NSString *descriptorUuid = args[@"descriptor_uuid"]; NSString *primaryServiceUuid = args[@"primary_service_uuid"]; - NSString *value = args[@"value"]; + NSData *value = args[@"value"]; // Find peripheral CBPeripheral *peripheral = [self getConnectedPeripheral:remoteId]; @@ -636,7 +636,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result // check mtu int mtu = (int) [self getMtu:peripheral]; - int dataLen = (int) [self convertHexToData:value].length; + int dataLen = value.length; if ((mtu-3) < dataLen) { NSString* f = @"data is longer than MTU allows. dataLen: %d > maxDataLen: %d"; NSString* s = [NSString stringWithFormat:f, dataLen, (mtu-3)]; @@ -668,7 +668,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self.writeDescs setObject:value forKey:key]; // Write descriptor - [peripheral writeValue:[self convertHexToData:value] forDescriptor:descriptor]; + [peripheral writeValue:value forDescriptor:descriptor]; result(@YES); } @@ -1437,7 +1437,7 @@ - (void)peripheral:(CBPeripheral *)peripheral @"service_uuid": [characteristic.service.UUID uuidStr], @"characteristic_uuid": [characteristic.UUID uuidStr], @"primary_service_uuid": primaryService ? [primaryService.UUID uuidStr] : [NSNull null], - @"value": [self convertDataToHex:characteristic.value], + @"value": characteristic.value, @"success": error == nil ? @(1) : @(0), @"error_string": error ? [error localizedDescription] : @"success", @"error_code": error ? @(error.code) : @(0), @@ -1473,7 +1473,7 @@ - (void)peripheral:(CBPeripheral *)peripheral // what data did we write? NSString *key = [NSString stringWithFormat:@"%@:%@:%@:%@", remoteId, serviceUuid, characteristicUuid, primaryService != nil ? [primaryService.UUID uuidStr] : @""]; - NSString *value = self.writeChrs[key] ? self.writeChrs[key] : @""; + NSData *value = self.writeChrs[key] ? self.writeChrs[key] : [NSMutableData data]; [self.writeChrs removeObjectForKey:key]; // See BmCharacteristicData @@ -1529,7 +1529,7 @@ - (void)peripheral:(CBPeripheral *)peripheral @"characteristic_uuid": [characteristic.UUID uuidStr], @"descriptor_uuid": CCCD, @"primary_service_uuid": primaryService ? [primaryService.UUID uuidStr] : [NSNull null], - @"value": [self convertDataToHex:[NSData dataWithBytes:&value length:sizeof(value)]], + @"value": [NSData dataWithBytes:&value length:sizeof(value)], @"success": @(error == nil), @"error_string": error ? [error localizedDescription] : @"success", @"error_code": error ? @(error.code) : @(0), @@ -1567,7 +1567,7 @@ - (void)peripheral:(CBPeripheral *)peripheral @"characteristic_uuid": [descriptor.characteristic.UUID uuidStr], @"descriptor_uuid": [descriptor.UUID uuidStr], @"primary_service_uuid": primaryService ? [primaryService.UUID uuidStr] : [NSNull null], - @"value": [self convertDataToHex:data], + @"value": data, @"success": @(error == nil), @"error_string": error ? [error localizedDescription] : @"success", @"error_code": error ? @(error.code) : @(0), @@ -1605,7 +1605,7 @@ - (void)peripheral:(CBPeripheral *)peripheral // what data did we write? NSString *key = [NSString stringWithFormat:@"%@:%@:%@:%@:%@", remoteId, serviceUuid, characteristicUuid, descriptorUuid, primaryService != nil ? [primaryService.UUID uuidStr] : @""]; - NSString *value = self.writeChrs[key] ? self.writeChrs[key] : @""; + NSData *value = self.writeChrs[key] ? self.writeChrs[key] : [NSMutableData data]; [self.writeDescs removeObjectForKey:key]; // See BmDescriptorData @@ -1773,10 +1773,9 @@ - (NSDictionary *)bmScanAdvertisement:(NSString*)remoteId // trim off first 2 bytes NSData* trimmed = [manufData subdataWithRange:NSMakeRange(2, manufData.length - 2)]; - NSString* hex = [self convertDataToHex:trimmed]; manufDataB = @{ - @(manufId): hex, + @(manufId): trimmed, }; } @@ -1796,8 +1795,8 @@ - (NSDictionary *)bmScanAdvertisement:(NSString*)remoteId { NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init]; for (CBUUID *uuid in serviceData) { - NSString* hex = [self convertDataToHex:serviceData[uuid]]; - [mutable setObject:hex forKey:[uuid uuidStr]]; + NSData *data = serviceData[uuid]; + [mutable setObject:data forKey:[uuid uuidStr]]; } serviceDataB = [mutable copy]; } @@ -2013,9 +2012,9 @@ - (BOOL)foundServiceData:(NSArray*)filters return NO; } for (NSDictionary *f in filters) { - NSString *service = f[@"service"]; - NSData *data = [self convertHexToData:f[@"data"]]; - NSData *mask = [self convertHexToData:f[@"mask"]]; + NSString *service = f[@"service"]; + NSData *data = f[@"data"]; + NSData *mask = f[@"mask"]; // mask if (mask.length == 0 && data.length > 0) { @@ -2043,9 +2042,9 @@ - (BOOL)foundMsd:(NSArray*)filters return NO; } for (NSDictionary *f in filters) { - NSNumber *manufacturerId = f[@"manufacturer_id"]; - NSData *data = [self convertHexToData:f[@"data"]]; - NSData *mask = [self convertHexToData:f[@"mask"]]; + NSNumber *manufacturerId = f[@"manufacturer_id"]; + NSData *data = f[@"data"]; + NSData *mask = f[@"mask"]; // first 2 bytes are manufacturer id unsigned short mId = 0; @@ -2089,40 +2088,6 @@ - (BOOL)findData:(NSData *)find inData:(NSData *)data usingMask:(NSData *)mask { return YES; } -- (NSString *)convertDataToHex:(NSData *)data -{ - if (data == nil) { - return @""; - } - - const unsigned char *bytes = (const unsigned char *)[data bytes]; - NSMutableString *hexString = [NSMutableString new]; - - for (NSInteger i = 0; i < data.length; i++) { - [hexString appendFormat:@"%02x", bytes[i]]; - } - - return [hexString copy]; -} - -- (NSData *)convertHexToData:(NSString *)hexString -{ - if (hexString.length % 2 != 0) { - return nil; - } - - NSMutableData *data = [NSMutableData new]; - - for (NSInteger i = 0; i < hexString.length; i += 2) { - unsigned int byte = 0; - NSRange range = NSMakeRange(i, 2); - [[NSScanner scannerWithString:[hexString substringWithRange:range]] scanHexInt:&byte]; - [data appendBytes:&byte length:1]; - } - - return [data copy]; -} - - (NSString *)cbManagerStateString:(CBManagerState)adapterState { switch (adapterState) diff --git a/lib/src/bluetooth_characteristic.dart b/lib/src/bluetooth_characteristic.dart index 2257145a..46a60f65 100644 --- a/lib/src/bluetooth_characteristic.dart +++ b/lib/src/bluetooth_characteristic.dart @@ -46,9 +46,9 @@ class BluetoothCharacteristic { /// - anytime `write()` is called /// - anytime a notification arrives (if subscribed) /// - when the device is disconnected it is cleared - List get lastValue { + Uint8List get lastValue { String key = "$serviceUuid:$characteristicUuid"; - return FlutterBluePlus._lastChrs[remoteId]?[key] ?? []; + return FlutterBluePlus._lastChrs[remoteId]?[key] ?? Uint8List(0); } /// this stream emits values: @@ -56,7 +56,7 @@ class BluetoothCharacteristic { /// - anytime `write()` is called /// - anytime a notification arrives (if subscribed) /// - and when first listened to, it re-emits the last value for convenience - Stream> get lastValueStream => FlutterBluePlus._methodStream.stream + Stream get lastValueStream => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnCharacteristicReceived" || m.method == "OnCharacteristicWritten") .map((m) => m.arguments) .map((args) => BmCharacteristicData.fromMap(args)) @@ -71,7 +71,7 @@ class BluetoothCharacteristic { /// this stream emits values: /// - anytime `read()` is called /// - anytime a notification arrives (if subscribed) - Stream> get onValueReceived => FlutterBluePlus._methodStream.stream + Stream get onValueReceived => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnCharacteristicReceived") .map((m) => m.arguments) .map((args) => BmCharacteristicData.fromMap(args)) @@ -95,7 +95,7 @@ class BluetoothCharacteristic { } /// read a characteristic - Future> read({int timeout = 15}) async { + Future read({int timeout = 15}) async { // check connected if (device.isDisconnected) { throw FlutterBluePlusException( @@ -107,7 +107,7 @@ class BluetoothCharacteristic { await mtx.take(); // return value - List responseValue = []; + Uint8List responseValue = Uint8List(0); try { var request = BmReadCharacteristicRequest( @@ -162,7 +162,7 @@ class BluetoothCharacteristic { /// 2. the peripheral device must support the 'long write' ble protocol. /// 3. Interrupted transfers can leave the characteristic in a partially written state /// 4. If the mtu is small, it is very very slow. - Future write(List value, + Future write(Uint8List value, {bool withoutResponse = false, bool allowLongWrite = false, int timeout = 15}) async { // check args if (withoutResponse && allowLongWrite) { @@ -336,10 +336,10 @@ class BluetoothCharacteristic { DeviceIdentifier get deviceId => remoteId; @Deprecated('Use lastValueStream instead') - Stream> get value => lastValueStream; + Stream get value => lastValueStream; @Deprecated('Use onValueReceived instead') - Stream> get onValueChangedStream => onValueReceived; + Stream get onValueChangedStream => onValueReceived; } class CharacteristicProperties { diff --git a/lib/src/bluetooth_descriptor.dart b/lib/src/bluetooth_descriptor.dart index 1a8b2d9c..c11a46f4 100644 --- a/lib/src/bluetooth_descriptor.dart +++ b/lib/src/bluetooth_descriptor.dart @@ -36,16 +36,16 @@ class BluetoothDescriptor { /// - anytime `read()` is called /// - anytime `write()` is called /// - when the device is disconnected it is cleared - List get lastValue { + Uint8List get lastValue { String key = "$serviceUuid:$characteristicUuid:$descriptorUuid"; - return FlutterBluePlus._lastDescs[remoteId]?[key] ?? []; + return FlutterBluePlus._lastDescs[remoteId]?[key] ?? Uint8List(0); } /// this stream emits values: /// - anytime `read()` is called /// - anytime `write()` is called /// - and when first listened to, it re-emits the last value for convenience - Stream> get lastValueStream => FlutterBluePlus._methodStream.stream + Stream get lastValueStream => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnDescriptorRead" || m.method == "OnDescriptorWritten") .map((m) => m.arguments) .map((args) => BmDescriptorData.fromMap(args)) @@ -60,7 +60,7 @@ class BluetoothDescriptor { /// this stream emits values: /// - anytime `read()` is called - Stream> get onValueReceived => FlutterBluePlus._methodStream.stream + Stream get onValueReceived => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnDescriptorRead") .map((m) => m.arguments) .map((args) => BmDescriptorData.fromMap(args)) @@ -73,7 +73,7 @@ class BluetoothDescriptor { .map((p) => p.value); /// Retrieves the value of a specified descriptor - Future> read({int timeout = 15}) async { + Future read({int timeout = 15}) async { // check connected if (device.isDisconnected) { throw FlutterBluePlusException( @@ -85,7 +85,7 @@ class BluetoothDescriptor { await mtx.take(); // return value - List readValue = []; + Uint8List readValue = Uint8List(0); try { var request = BmReadDescriptorRequest( @@ -132,7 +132,7 @@ class BluetoothDescriptor { } /// Writes the value of a descriptor - Future write(List value, {int timeout = 15}) async { + Future write(Uint8List value, {int timeout = 15}) async { // check connected if (device.isDisconnected) { throw FlutterBluePlusException( @@ -199,7 +199,7 @@ class BluetoothDescriptor { } @Deprecated('Use onValueReceived instead') - Stream> get value => onValueReceived; + Stream get value => onValueReceived; @Deprecated('Use remoteId instead') DeviceIdentifier get deviceId => remoteId; diff --git a/lib/src/bluetooth_events.dart b/lib/src/bluetooth_events.dart index 84e092a9..16152375 100644 --- a/lib/src/bluetooth_events.dart +++ b/lib/src/bluetooth_events.dart @@ -189,7 +189,7 @@ class OnCharacteristicReceivedEvent { primaryServiceUuid: _response.primaryServiceUuid); /// the new data - List get value => _response.value; + Uint8List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); @@ -212,7 +212,7 @@ class OnCharacteristicWrittenEvent { primaryServiceUuid: _response.primaryServiceUuid); /// the new data - List get value => _response.value; + Uint8List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); @@ -235,7 +235,7 @@ class OnDescriptorReadEvent { descriptorUuid: _response.descriptorUuid); /// the new data - List get value => _response.value; + Uint8List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); @@ -258,7 +258,7 @@ class OnDescriptorWrittenEvent { descriptorUuid: _response.descriptorUuid); /// the new data - List get value => _response.value; + Uint8List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); diff --git a/lib/src/bluetooth_msgs.dart b/lib/src/bluetooth_msgs.dart index ebdd9863..b8d950e0 100644 --- a/lib/src/bluetooth_msgs.dart +++ b/lib/src/bluetooth_msgs.dart @@ -30,28 +30,28 @@ class BmBluetoothAdapterState { class BmMsdFilter { int manufacturerId; - List? data; - List? mask; + Uint8List? data; + Uint8List? mask; BmMsdFilter(this.manufacturerId, this.data, this.mask); Map toMap() { final Map map = {}; map['manufacturer_id'] = manufacturerId; - map['data'] = _hexEncode(data ?? []); - map['mask'] = _hexEncode(mask ?? []); + map['data'] = data ?? []; + map['mask'] = mask ?? []; return map; } } class BmServiceDataFilter { Guid service; - List data; - List mask; + Uint8List data; + Uint8List mask; BmServiceDataFilter(this.service, this.data, this.mask); Map toMap() { final Map map = {}; map['service'] = service.str; - map['data'] = _hexEncode(data); - map['mask'] = _hexEncode(mask); + map['data'] = data; + map['mask'] = mask; return map; } } @@ -107,8 +107,8 @@ class BmScanAdvertisement { final bool connectable; final int? txPowerLevel; final int? appearance; // not supported on iOS / macOS - final Map> manufacturerData; - final Map> serviceData; + final Map manufacturerData; + final Map serviceData; final List serviceUuids; final int rssi; @@ -127,27 +127,18 @@ class BmScanAdvertisement { factory BmScanAdvertisement.fromMap(Map json) { // Get raw data - var rawManufacturerData = json['manufacturer_data'] ?? {}; - var rawServiceData = json['service_data'] ?? {}; - var rawServiceUuids = json['service_uuids'] ?? []; + var rawManufacturerData = json['manufacturer_data'] ?? {}; + var rawServiceData = json['service_data'] ?? {}; + var rawServiceUuids = json['service_uuids'] ?? []; // Cast the data to the right type - Map> manufacturerData = {}; - for (var key in rawManufacturerData.keys) { - manufacturerData[key] = _hexDecode(rawManufacturerData[key]); - } + Map manufacturerData = rawManufacturerData.cast(); // Cast the data to the right type - Map> serviceData = {}; - for (var key in rawServiceData.keys) { - serviceData[Guid(key)] = _hexDecode(rawServiceData[key]); - } + Map serviceData = rawServiceData.cast(); // Cast the data to the right type - List serviceUuids = []; - for (var val in rawServiceUuids) { - serviceUuids.add(Guid(val)); - } + List serviceUuids = rawServiceUuids.map((e) => Guid(e)).toList(); return BmScanAdvertisement( remoteId: DeviceIdentifier(json['remote_id']), @@ -296,7 +287,6 @@ class BmBluetoothCharacteristic { List descriptors; BmCharacteristicProperties properties; - BmBluetoothCharacteristic({ required this.remoteId, required this.serviceUuid, @@ -448,12 +438,11 @@ class BmCharacteristicData { final Guid serviceUuid; final Guid characteristicUuid; final Guid? primaryServiceUuid; - final List value; + final Uint8List value; final bool success; final int errorCode; final String errorString; - BmCharacteristicData({ required this.remoteId, required this.serviceUuid, @@ -471,7 +460,7 @@ class BmCharacteristicData { serviceUuid: Guid(json['service_uuid']), characteristicUuid: Guid(json['characteristic_uuid']), primaryServiceUuid: Guid.parse(json['primary_service_uuid']), - value: _hexDecode(json['value']), + value: json['value'], success: json['success'] != 0, errorCode: json['error_code'], errorString: json['error_string'], @@ -518,8 +507,7 @@ class BmWriteCharacteristicRequest { final Guid? primaryServiceUuid; final BmWriteType writeType; final bool allowLongWrite; - final List value; - + final Uint8List value; BmWriteCharacteristicRequest({ required this.remoteId, @@ -539,7 +527,7 @@ class BmWriteCharacteristicRequest { data['primary_service_uuid'] = primaryServiceUuid?.str; data['write_type'] = writeType.index; data['allow_long_write'] = allowLongWrite ? 1 : 0; - data['value'] = _hexEncode(value); + data['value'] = value; data.removeWhere((key, value) => value == null); return data; } @@ -551,7 +539,7 @@ class BmWriteDescriptorRequest { final Guid characteristicUuid; final Guid? primaryServiceUuid; final Guid descriptorUuid; - final List value; + final Uint8List value; BmWriteDescriptorRequest({ required this.remoteId, @@ -569,7 +557,7 @@ class BmWriteDescriptorRequest { data['characteristic_uuid'] = characteristicUuid.str; data['descriptor_uuid'] = descriptorUuid.str; data['primary_service_uuid'] = primaryServiceUuid?.str; - data['value'] = _hexEncode(value); + data['value'] = value; data.removeWhere((key, value) => value == null); return data; } @@ -581,7 +569,7 @@ class BmDescriptorData { final Guid characteristicUuid; final Guid descriptorUuid; final Guid? primaryServiceUuid; - final List value; + final Uint8List value; final bool success; final int errorCode; final String errorString; @@ -605,7 +593,7 @@ class BmDescriptorData { characteristicUuid: Guid(json['characteristic_uuid']), descriptorUuid: Guid(json['descriptor_uuid']), primaryServiceUuid: Guid.parse(json['primary_service_uuid']), - value: _hexDecode(json['value']), + value: json['value'], success: json['success'] != 0, errorCode: json['error_code'], errorString: json['error_string'], @@ -621,7 +609,6 @@ class BmSetNotifyValueRequest { final bool forceIndications; final bool enable; - BmSetNotifyValueRequest({ required this.remoteId, required this.serviceUuid, diff --git a/lib/src/flutter_blue_plus.dart b/lib/src/flutter_blue_plus.dart index 71fc7072..af24e0fd 100644 --- a/lib/src/flutter_blue_plus.dart +++ b/lib/src/flutter_blue_plus.dart @@ -25,8 +25,8 @@ class FlutterBluePlus { static final Map _mtuValues = {}; static final Map _platformNames = {}; static final Map _advNames = {}; - static final Map>> _lastChrs = {}; - static final Map>> _lastDescs = {}; + static final Map> _lastChrs = {}; + static final Map> _lastDescs = {}; static final Map> _deviceSubscriptions = {}; static final Map> _delayedSubscriptions = {}; static final Map _connectTimestamp = {}; @@ -719,14 +719,14 @@ class MsdFilter { int manufacturerId; /// filter for this data - List data; + Uint8List data; /// For any bit in the mask, set it the 1 if it needs to match /// the one in manufacturer data, otherwise set it to 0. /// The 'mask' must have the same length as 'data'. - List mask; + Uint8List mask; - MsdFilter(this.manufacturerId, {this.data = const [], this.mask = const []}); + MsdFilter(this.manufacturerId, {required this.data, required this.mask}); // convert to bmMsg BmMsdFilter get _bm { @@ -739,14 +739,14 @@ class ServiceDataFilter { Guid service; // filter for this data - List data; + Uint8List data; // For any bit in the mask, set it the 1 if it needs to match // the one in service data, otherwise set it to 0. // The 'mask' must have the same length as 'data'. - List mask; + Uint8List mask; - ServiceDataFilter(this.service, {this.data = const [], this.mask = const []}); + ServiceDataFilter(this.service, {required this.data, required this.mask}); // convert to bmMsg BmServiceDataFilter get _bm { @@ -814,19 +814,19 @@ class AdvertisementData { final int? txPowerLevel; final int? appearance; // not supported on iOS / macOS final bool connectable; - final Map> manufacturerData; // key: manufacturerId - final Map> serviceData; // key: service guid + final Map manufacturerData; // key: manufacturerId + final Map serviceData; // key: service guid final List serviceUuids; /// for convenience, raw msd data /// * interprets the first two byte as raw data, /// as opposed to a `manufacturerId` - List> get msd { - List> output = []; + List get msd { + List output = []; manufacturerData.forEach((manufacturerId, bytes) { int low = manufacturerId & 0xFF; int high = (manufacturerId >> 8) & 0xFF; - output.add([low, high] + bytes); + output.add(Uint8List.fromList([low, high] + bytes)); }); return output; } diff --git a/lib/src/guid.dart b/lib/src/guid.dart index 2de11c33..8698b700 100644 --- a/lib/src/guid.dart +++ b/lib/src/guid.dart @@ -6,9 +6,9 @@ part of flutter_blue_plus; // Supports 16-bit, 32-bit, or 128-bit UUIDs class Guid { - final List bytes; + final Uint8List bytes; - Guid.empty() : bytes = List.filled(16, 0); + Guid.empty() : bytes = Uint8List(16); Guid.fromBytes(this.bytes) : assert(_checkLen(bytes.length), 'GUID must be 16, 32, or 128 bit.'); @@ -24,14 +24,14 @@ class Guid { } } - static List _toBytes(String input) { + static Uint8List _toBytes(String input) { if (input.isEmpty) { - return List.filled(16, 0); + return Guid.empty().bytes; } input = input.replaceAll('-', ''); - List? bytes = _tryHexDecode(input); + Uint8List? bytes = _tryHexDecode(input); if (bytes == null) { throw FormatException("GUID not hex format: $input"); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index f9202d9a..3e20674c 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,28 +1,18 @@ part of flutter_blue_plus; -String _hexEncode(List numbers) { +String _hexEncode(Uint8List numbers) { return numbers.map((n) => (n & 0xFF).toRadixString(16).padLeft(2, '0')).join(); } -List? _tryHexDecode(String hex) { - List numbers = []; +Uint8List? _tryHexDecode(String hex) { + Uint8List numbers = Uint8List(hex.length ~/ 2); for (int i = 0; i < hex.length; i += 2) { String hexPart = hex.substring(i, i + 2); int? num = int.tryParse(hexPart, radix: 16); if (num == null) { return null; } - numbers.add(num); - } - return numbers; -} - -List _hexDecode(String hex) { - List numbers = []; - for (int i = 0; i < hex.length; i += 2) { - String hexPart = hex.substring(i, i + 2); - int num = int.parse(hexPart, radix: 16); - numbers.add(num); + numbers[i ~/ 2] = num; } return numbers; } @@ -250,10 +240,9 @@ class _NewStreamWithInitialValueTransformer extends StreamTransformerBase _bind(Stream stream, {bool broadcast = false}) { - ///////////////////////////////////////// /// Original Stream Subscription Callbacks - /// + /// /// When the original stream emits data, forward it to our new stream void onData(T data) { @@ -291,22 +280,22 @@ class _NewStreamWithInitialValueTransformer extends StreamTransformerBase extends StreamTransformerBase Date: Sun, 6 Oct 2024 11:53:30 +0800 Subject: [PATCH 2/7] fix typo --- .../java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java index b91f9795..4749b6e1 100644 --- a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java +++ b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java @@ -2357,7 +2357,7 @@ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristi // what data did we write? String key = remoteId + ":" + serviceUuid + ":" + characteristicUuid + ":" + primaryServiceUuid; - []byte value = mWriteChr.get(key) != null ? mWriteChr.get(key) : new byte[0]; + byte[] value = mWriteChr.get(key) != null ? mWriteChr.get(key) : new byte[0]; mWriteChr.remove(key); // see: BmCharacteristicData From 2044f3a92dfb3a88a0464216b5c99cd693dcae8d Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Fri, 11 Oct 2024 10:59:38 +0800 Subject: [PATCH 3/7] fix: correct type mapping --- lib/src/bluetooth_msgs.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/bluetooth_msgs.dart b/lib/src/bluetooth_msgs.dart index b8d950e0..c0c5a62e 100644 --- a/lib/src/bluetooth_msgs.dart +++ b/lib/src/bluetooth_msgs.dart @@ -127,9 +127,9 @@ class BmScanAdvertisement { factory BmScanAdvertisement.fromMap(Map json) { // Get raw data - var rawManufacturerData = json['manufacturer_data'] ?? {}; - var rawServiceData = json['service_data'] ?? {}; - var rawServiceUuids = json['service_uuids'] ?? []; + Map rawManufacturerData = json['manufacturer_data'] ?? {}; + Map rawServiceData = json['service_data'] ?? {}; + List rawServiceUuids = json['service_uuids'] ?? []; // Cast the data to the right type Map manufacturerData = rawManufacturerData.cast(); @@ -138,7 +138,7 @@ class BmScanAdvertisement { Map serviceData = rawServiceData.cast(); // Cast the data to the right type - List serviceUuids = rawServiceUuids.map((e) => Guid(e)).toList(); + List serviceUuids = rawServiceUuids.cast().map((e) => Guid(e)).toList(); return BmScanAdvertisement( remoteId: DeviceIdentifier(json['remote_id']), From 7389e587dea502fb50c91ec98323d3370b17923e Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Sun, 10 Nov 2024 16:37:41 +0800 Subject: [PATCH 4/7] add performance screen --- .../FlutterBluePlusPlugin.java | 4 +- example/lib/screens/device_screen.dart | 26 +- example/lib/screens/performance_screen.dart | 243 ++++++++++++++++++ 3 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 example/lib/screens/performance_screen.dart diff --git a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java index 4749b6e1..603e3898 100644 --- a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java +++ b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java @@ -894,7 +894,7 @@ public void onMethodCall(@NonNull MethodCall call, String serviceUuid = (String) data.get("service_uuid"); String characteristicUuid = (String) data.get("characteristic_uuid"); String primaryServiceUuid = (String) data.get("primary_service_uuid"); - String value = (byte[]) data.get("value"); + byte[] value = (byte[]) data.get("value"); int writeTypeInt = (int) data.get("write_type"); boolean allowLongWrite = ((int) data.get("allow_long_write")) != 0; @@ -1040,7 +1040,7 @@ public void onMethodCall(@NonNull MethodCall call, String characteristicUuid = (String) data.get("characteristic_uuid"); String descriptorUuid = (String) data.get("descriptor_uuid"); String primaryServiceUuid = (String) data.get("primary_service_uuid"); - String value = (byte) data.get("value"); + byte[] value = (byte[]) data.get("value"); // check connection BluetoothGatt gatt = mConnectedDevices.get(remoteId); diff --git a/example/lib/screens/device_screen.dart b/example/lib/screens/device_screen.dart index accf8694..acc97dac 100644 --- a/example/lib/screens/device_screen.dart +++ b/example/lib/screens/device_screen.dart @@ -2,12 +2,13 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutter_blue_plus_example/screens/performance_screen.dart'; -import '../widgets/service_tile.dart'; +import '../utils/extra.dart'; +import '../utils/snackbar.dart'; import '../widgets/characteristic_tile.dart'; import '../widgets/descriptor_tile.dart'; -import '../utils/snackbar.dart'; -import '../utils/extra.dart'; +import '../widgets/service_tile.dart'; class DeviceScreen extends StatefulWidget { final BluetoothDevice device; @@ -228,6 +229,20 @@ class _DeviceScreenState extends State { )); } + Widget buildPerformanceButton(BuildContext context) { + if (!isConnected) { + return const SizedBox.shrink(); + } + + return IconButton( + onPressed: () { + final route = MaterialPageRoute(builder: (context) => PerformanceScreen(device: widget.device)); + Navigator.of(context).push(route); + }, + icon: Icon(Icons.speed), + ); + } + Widget buildConnectButton(BuildContext context) { return Row(children: [ if (_isConnecting || _isDisconnecting) buildSpinner(context), @@ -247,7 +262,10 @@ class _DeviceScreenState extends State { child: Scaffold( appBar: AppBar( title: Text(widget.device.platformName), - actions: [buildConnectButton(context)], + actions: [ + buildPerformanceButton(context), + buildConnectButton(context), + ], ), body: SingleChildScrollView( child: Column( diff --git a/example/lib/screens/performance_screen.dart b/example/lib/screens/performance_screen.dart new file mode 100644 index 00000000..54d114e3 --- /dev/null +++ b/example/lib/screens/performance_screen.dart @@ -0,0 +1,243 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; + +class PerformanceScreen extends StatefulWidget { + final BluetoothDevice device; + + const PerformanceScreen({ + Key? key, + required this.device, + }) : super(key: key); + + @override + State createState() => _PerformanceScreenState(); +} + +class _PerformanceScreenState extends State { + final epochText = TextEditingController(text: '1'); + final bytesText = TextEditingController(text: '512'); + final requestsText = TextEditingController(text: '100'); + final form = GlobalKey(); + final elapsedTimes = <_ElapsedTime>[]; + + String? error; + Stream? epochProgress; + Stream? writeProgress; + + BluetoothCharacteristic? get writableChar => widget.device.servicesList + .cast() + .firstWhere((service) { + return service!.characteristics.any((char) { + return char.properties.write | char.properties.writeWithoutResponse; + }); + }, orElse: () => null) + ?.characteristics + .firstWhere((char) => char.properties.write | char.properties.writeWithoutResponse); + + @override + Widget build(BuildContext context) { + late Widget body; + if (writeProgress == null) { + body = Form( + key: form, + child: ListView(children: [ + if (error != null) + Center( + child: Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text(error!, style: const TextStyle(color: Colors.red)), + ), + ), + if (elapsedTimes.isNotEmpty) ...[ + Center(child: Text('History Table')), + SelectionArea( + child: Table( + border: TableBorder.all(color: Colors.grey), + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + TableRow(children: const [ + TableCell(child: Text('Start', textAlign: TextAlign.center)), + TableCell(child: Text('Duration', textAlign: TextAlign.center)), + ]), + for (final time in elapsedTimes) + TableRow(children: [ + TableCell(child: Text(time.startString(), textAlign: TextAlign.center)), + TableCell(child: Text(time.durationString(), textAlign: TextAlign.center)), + ]), + ], + ), + ), + const SizedBox(height: 8), + ], + Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + ElevatedButton( + onPressed: start, + child: const Text('Start'), + ), + ]), + const SizedBox(height: 8), + TextFormField( + controller: epochText, + decoration: const InputDecoration(labelText: 'Epoch'), + keyboardType: TextInputType.number, + validator: _validateNumber, + ), + TextFormField( + controller: bytesText, + decoration: const InputDecoration(labelText: 'Bytes to send'), + keyboardType: TextInputType.number, + validator: _validateNumber, + ), + TextFormField( + controller: requestsText, + decoration: const InputDecoration(labelText: 'Send requests'), + keyboardType: TextInputType.number, + validator: _validateNumber, + ), + ]), + ); + } else { + body = Center( + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + StreamBuilder( + stream: writeProgress, + builder: (context, snapshot) { + final v = ((snapshot.data ?? 0) * 100).toInt(); + return Column(mainAxisSize: MainAxisSize.min, children: [ + CircularProgressIndicator(value: snapshot.data), + const SizedBox(height: 8), + Text('$v%'), + ]); + }, + ), + const SizedBox(height: 8), + StreamBuilder( + stream: epochProgress, + builder: (context, snapshot) { + final v = snapshot.data ?? 1; + final total = int.parse(epochText.text); + return Text('Epoch: $v/$total'); + }, + ), + ]), + ); + } + + return Scaffold( + appBar: AppBar(title: const Text('Performance')), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: body, + ), + ); + } + + void start() { + if (form.currentState?.validate() != true) { + return; + } + + final char = writableChar; + if (char == null) { + setState(() { + error = 'No writable characteristic found'; + }); + return; + } + + elapsedTimes.clear(); + + final controller = StreamController(); + final stream = batch(char, controller); + + setState(() { + writeProgress = stream; + epochProgress = controller.stream; + }); + } + + Stream batch(BluetoothCharacteristic char, StreamController controller) async* { + final bytes = await widget.device.requestMtu(int.parse(bytesText.text)); + + final epochs = int.parse(epochText.text); + final count = int.parse(requestsText.text); + final data = Uint8List.fromList(List.generate(bytes - 5, (index) => index)); + + var stop = false; + for (var i = 0; i < epochs; i++) { + elapsedTimes.add(_ElapsedTime.start()); + yield* _write(char, count, bytes, data).handleError((e) { + if (mounted) { + setState(() { + writeProgress = null; + epochProgress = null; + elapsedTimes.last.finish(); + error = e.toString(); + }); + } + + stop = true; + }); + if (stop) { + break; + } + + elapsedTimes.last.finish(); + controller.add(i + 1); + } + + await controller.close(); + setState(() { + writeProgress = null; + epochProgress = null; + error = null; + }); + } + + /// Write [bytes] length of data one by one and will send [count] times. + Stream _write( + BluetoothCharacteristic char, + int count, + int bytes, + Uint8List data, + ) async* { + for (var i = 0; i < count; i++) { + await char.write(data, withoutResponse: char.properties.writeWithoutResponse); + + yield (i + 1) / count; + } + } +} + +String? _validateNumber(String? value) { + return int.tryParse(value ?? '') == null ? 'Invalid number' : null; +} + +class _ElapsedTime { + final DateTime start; + late final DateTime end; + late final Duration elapsed; + + _ElapsedTime(this.start); + + factory _ElapsedTime.start() => _ElapsedTime(DateTime.now()); + + void finish() { + end = DateTime.now(); + elapsed = end.difference(start); + } + + String startString() { + final hour = start.hour.toString().padLeft(2, '0'); + final minute = start.minute.toString().padLeft(2, '0'); + final second = start.second.toString().padLeft(2, '0'); + return '$hour:$minute:$second'; + } + + String durationString() { + return (elapsed.inMilliseconds / 1000).toStringAsPrecision(5); + } +} From ff40a87218d23bf72f37ee9840a04901dfac572c Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Sun, 1 Dec 2024 11:50:12 +0800 Subject: [PATCH 5/7] only use uint8list internally --- README.md | 8 ++-- .../FlutterBluePlusPlugin.java | 2 +- example/lib/widgets/characteristic_tile.dart | 9 ++-- example/lib/widgets/descriptor_tile.dart | 9 ++-- example/lib/widgets/scan_result_tile.dart | 7 ++- lib/src/bluetooth_characteristic.dart | 18 ++++---- lib/src/bluetooth_descriptor.dart | 16 +++---- lib/src/bluetooth_events.dart | 8 ++-- lib/src/bluetooth_msgs.dart | 45 ++++++++++--------- lib/src/flutter_blue_plus.dart | 22 ++++----- lib/src/guid.dart | 10 ++--- lib/src/utils.dart | 8 ++-- 12 files changed, 80 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index fda19ee3..52da7eb4 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,7 @@ services.forEach((service) { var characteristics = service.characteristics; for(BluetoothCharacteristic c in characteristics) { if (c.properties.read) { - Uint8List value = await c.read(); + List value = await c.read(); print(value); } } @@ -340,10 +340,10 @@ import 'dart:math'; // 2. it can only be used *with* response to avoid data loss // 3. The characteristic must be designed to support split data extension splitWrite on BluetoothCharacteristic { - Future splitWrite(Uint8List value, {int timeout = 15}) async { + Future splitWrite(List value, {int timeout = 15}) async { int chunk = min(device.mtuNow - 3, 512); // 3 bytes BLE overhead, 512 bytes max for (int i = 0; i < value.length; i += chunk) { - Uint8List subvalue = value.sublist(i, min(i + chunk, value.length)); + List subvalue = value.sublist(i, min(i + chunk, value.length)); await write(subvalue, withoutResponse:false, timeout: timeout); } } @@ -398,7 +398,7 @@ await characteristic.setNotifyValue(true); // Reads all descriptors var descriptors = characteristic.descriptors; for(BluetoothDescriptor d in descriptors) { - Uint8List value = await d.read(); + List value = await d.read(); print(value); } diff --git a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java index 603e3898..916ac4e7 100644 --- a/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java +++ b/android/src/main/java/com/lib/flutter_blue_plus/FlutterBluePlusPlugin.java @@ -594,7 +594,7 @@ public void onMethodCall(@NonNull MethodCall call, // service data for (int i = 0; i < withServiceData.size(); i++) { HashMap m = (HashMap) withServiceData.get(i); - ParcelUuid s = ParcelUuid.fromString((String) m.get("service")); + ParcelUuid s = ParcelUuid.fromString(uuid128((String) m.get("service"))); byte[] mdata = (byte[]) m.get("data"); byte[] mask = (byte[]) m.get("mask"); ScanFilter f = null; diff --git a/example/lib/widgets/characteristic_tile.dart b/example/lib/widgets/characteristic_tile.dart index c0c949f2..0385a876 100644 --- a/example/lib/widgets/characteristic_tile.dart +++ b/example/lib/widgets/characteristic_tile.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -19,9 +18,9 @@ class CharacteristicTile extends StatefulWidget { } class _CharacteristicTileState extends State { - Uint8List _value = Uint8List(0); + List _value = []; - late StreamSubscription _lastValueSubscription; + late StreamSubscription> _lastValueSubscription; @override void initState() { @@ -42,9 +41,9 @@ class _CharacteristicTileState extends State { BluetoothCharacteristic get c => widget.characteristic; - Uint8List _getRandomBytes() { + List _getRandomBytes() { final math = Random(); - return Uint8List.fromList([math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]); + return [math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]; } Future onReadPressed() async { diff --git a/example/lib/widgets/descriptor_tile.dart b/example/lib/widgets/descriptor_tile.dart index f9e8b388..0b078e34 100644 --- a/example/lib/widgets/descriptor_tile.dart +++ b/example/lib/widgets/descriptor_tile.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:math'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -17,9 +16,9 @@ class DescriptorTile extends StatefulWidget { } class _DescriptorTileState extends State { - Uint8List _value = Uint8List(0); + List _value = []; - late StreamSubscription _lastValueSubscription; + late StreamSubscription> _lastValueSubscription; @override void initState() { @@ -40,9 +39,9 @@ class _DescriptorTileState extends State { BluetoothDescriptor get d => widget.descriptor; - Uint8List _getRandomBytes() { + List _getRandomBytes() { final math = Random(); - return Uint8List.fromList([math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]); + return [math.nextInt(255), math.nextInt(255), math.nextInt(255), math.nextInt(255)]; } Future onReadPressed() async { diff --git a/example/lib/widgets/scan_result_tile.dart b/example/lib/widgets/scan_result_tile.dart index f37a4133..084ab04d 100644 --- a/example/lib/widgets/scan_result_tile.dart +++ b/example/lib/widgets/scan_result_tile.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -37,15 +36,15 @@ class _ScanResultTileState extends State { super.dispose(); } - String getNiceHexArray(Uint8List bytes) { + String getNiceHexArray(List bytes) { return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]'; } - String getNiceManufacturerData(List data) { + String getNiceManufacturerData(List> data) { return data.map((val) => '${getNiceHexArray(val)}').join(', ').toUpperCase(); } - String getNiceServiceData(Map data) { + String getNiceServiceData(Map> data) { return data.entries.map((v) => '${v.key}: ${getNiceHexArray(v.value)}').join(', ').toUpperCase(); } diff --git a/lib/src/bluetooth_characteristic.dart b/lib/src/bluetooth_characteristic.dart index 46a60f65..7b981271 100644 --- a/lib/src/bluetooth_characteristic.dart +++ b/lib/src/bluetooth_characteristic.dart @@ -46,9 +46,9 @@ class BluetoothCharacteristic { /// - anytime `write()` is called /// - anytime a notification arrives (if subscribed) /// - when the device is disconnected it is cleared - Uint8List get lastValue { + List get lastValue { String key = "$serviceUuid:$characteristicUuid"; - return FlutterBluePlus._lastChrs[remoteId]?[key] ?? Uint8List(0); + return FlutterBluePlus._lastChrs[remoteId]?[key]?.toList() ?? []; } /// this stream emits values: @@ -56,7 +56,7 @@ class BluetoothCharacteristic { /// - anytime `write()` is called /// - anytime a notification arrives (if subscribed) /// - and when first listened to, it re-emits the last value for convenience - Stream get lastValueStream => FlutterBluePlus._methodStream.stream + Stream> get lastValueStream => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnCharacteristicReceived" || m.method == "OnCharacteristicWritten") .map((m) => m.arguments) .map((args) => BmCharacteristicData.fromMap(args)) @@ -71,7 +71,7 @@ class BluetoothCharacteristic { /// this stream emits values: /// - anytime `read()` is called /// - anytime a notification arrives (if subscribed) - Stream get onValueReceived => FlutterBluePlus._methodStream.stream + Stream> get onValueReceived => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnCharacteristicReceived") .map((m) => m.arguments) .map((args) => BmCharacteristicData.fromMap(args)) @@ -95,7 +95,7 @@ class BluetoothCharacteristic { } /// read a characteristic - Future read({int timeout = 15}) async { + Future> read({int timeout = 15}) async { // check connected if (device.isDisconnected) { throw FlutterBluePlusException( @@ -107,7 +107,7 @@ class BluetoothCharacteristic { await mtx.take(); // return value - Uint8List responseValue = Uint8List(0); + List responseValue = []; try { var request = BmReadCharacteristicRequest( @@ -162,7 +162,7 @@ class BluetoothCharacteristic { /// 2. the peripheral device must support the 'long write' ble protocol. /// 3. Interrupted transfers can leave the characteristic in a partially written state /// 4. If the mtu is small, it is very very slow. - Future write(Uint8List value, + Future write(List value, {bool withoutResponse = false, bool allowLongWrite = false, int timeout = 15}) async { // check args if (withoutResponse && allowLongWrite) { @@ -336,10 +336,10 @@ class BluetoothCharacteristic { DeviceIdentifier get deviceId => remoteId; @Deprecated('Use lastValueStream instead') - Stream get value => lastValueStream; + Stream> get value => lastValueStream; @Deprecated('Use onValueReceived instead') - Stream get onValueChangedStream => onValueReceived; + Stream> get onValueChangedStream => onValueReceived; } class CharacteristicProperties { diff --git a/lib/src/bluetooth_descriptor.dart b/lib/src/bluetooth_descriptor.dart index c11a46f4..f560e8ec 100644 --- a/lib/src/bluetooth_descriptor.dart +++ b/lib/src/bluetooth_descriptor.dart @@ -36,16 +36,16 @@ class BluetoothDescriptor { /// - anytime `read()` is called /// - anytime `write()` is called /// - when the device is disconnected it is cleared - Uint8List get lastValue { + List get lastValue { String key = "$serviceUuid:$characteristicUuid:$descriptorUuid"; - return FlutterBluePlus._lastDescs[remoteId]?[key] ?? Uint8List(0); + return FlutterBluePlus._lastDescs[remoteId]?[key]?.toList() ?? []; } /// this stream emits values: /// - anytime `read()` is called /// - anytime `write()` is called /// - and when first listened to, it re-emits the last value for convenience - Stream get lastValueStream => FlutterBluePlus._methodStream.stream + Stream> get lastValueStream => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnDescriptorRead" || m.method == "OnDescriptorWritten") .map((m) => m.arguments) .map((args) => BmDescriptorData.fromMap(args)) @@ -60,7 +60,7 @@ class BluetoothDescriptor { /// this stream emits values: /// - anytime `read()` is called - Stream get onValueReceived => FlutterBluePlus._methodStream.stream + Stream> get onValueReceived => FlutterBluePlus._methodStream.stream .where((m) => m.method == "OnDescriptorRead") .map((m) => m.arguments) .map((args) => BmDescriptorData.fromMap(args)) @@ -73,7 +73,7 @@ class BluetoothDescriptor { .map((p) => p.value); /// Retrieves the value of a specified descriptor - Future read({int timeout = 15}) async { + Future> read({int timeout = 15}) async { // check connected if (device.isDisconnected) { throw FlutterBluePlusException( @@ -85,7 +85,7 @@ class BluetoothDescriptor { await mtx.take(); // return value - Uint8List readValue = Uint8List(0); + List readValue = []; try { var request = BmReadDescriptorRequest( @@ -132,7 +132,7 @@ class BluetoothDescriptor { } /// Writes the value of a descriptor - Future write(Uint8List value, {int timeout = 15}) async { + Future write(List value, {int timeout = 15}) async { // check connected if (device.isDisconnected) { throw FlutterBluePlusException( @@ -199,7 +199,7 @@ class BluetoothDescriptor { } @Deprecated('Use onValueReceived instead') - Stream get value => onValueReceived; + Stream> get value => onValueReceived; @Deprecated('Use remoteId instead') DeviceIdentifier get deviceId => remoteId; diff --git a/lib/src/bluetooth_events.dart b/lib/src/bluetooth_events.dart index 16152375..84e092a9 100644 --- a/lib/src/bluetooth_events.dart +++ b/lib/src/bluetooth_events.dart @@ -189,7 +189,7 @@ class OnCharacteristicReceivedEvent { primaryServiceUuid: _response.primaryServiceUuid); /// the new data - Uint8List get value => _response.value; + List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); @@ -212,7 +212,7 @@ class OnCharacteristicWrittenEvent { primaryServiceUuid: _response.primaryServiceUuid); /// the new data - Uint8List get value => _response.value; + List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); @@ -235,7 +235,7 @@ class OnDescriptorReadEvent { descriptorUuid: _response.descriptorUuid); /// the new data - Uint8List get value => _response.value; + List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); @@ -258,7 +258,7 @@ class OnDescriptorWrittenEvent { descriptorUuid: _response.descriptorUuid); /// the new data - Uint8List get value => _response.value; + List get value => _response.value; /// failed? FbpError? get error => _response.success ? null : FbpError(_response.errorCode, _response.errorString); diff --git a/lib/src/bluetooth_msgs.dart b/lib/src/bluetooth_msgs.dart index c0c5a62e..fe98d9cc 100644 --- a/lib/src/bluetooth_msgs.dart +++ b/lib/src/bluetooth_msgs.dart @@ -30,28 +30,28 @@ class BmBluetoothAdapterState { class BmMsdFilter { int manufacturerId; - Uint8List? data; - Uint8List? mask; + List? data; + List? mask; BmMsdFilter(this.manufacturerId, this.data, this.mask); Map toMap() { final Map map = {}; map['manufacturer_id'] = manufacturerId; - map['data'] = data ?? []; - map['mask'] = mask ?? []; + map['data'] = Uint8List.fromList(data ?? []); + map['mask'] = Uint8List.fromList(mask ?? []); return map; } } class BmServiceDataFilter { Guid service; - Uint8List data; - Uint8List mask; + List data; + List mask; BmServiceDataFilter(this.service, this.data, this.mask); Map toMap() { final Map map = {}; map['service'] = service.str; - map['data'] = data; - map['mask'] = mask; + map['data'] = Uint8List.fromList(data); + map['mask'] = Uint8List.fromList(mask); return map; } } @@ -107,8 +107,8 @@ class BmScanAdvertisement { final bool connectable; final int? txPowerLevel; final int? appearance; // not supported on iOS / macOS - final Map manufacturerData; - final Map serviceData; + final Map> manufacturerData; + final Map> serviceData; final List serviceUuids; final int rssi; @@ -127,15 +127,16 @@ class BmScanAdvertisement { factory BmScanAdvertisement.fromMap(Map json) { // Get raw data - Map rawManufacturerData = json['manufacturer_data'] ?? {}; - Map rawServiceData = json['service_data'] ?? {}; + Map rawManufacturerData = json['manufacturer_data'] ?? >{}; + Map rawServiceData = json['service_data'] ?? >{}; List rawServiceUuids = json['service_uuids'] ?? []; // Cast the data to the right type - Map manufacturerData = rawManufacturerData.cast(); + Map> manufacturerData = + rawManufacturerData.map((k, v) => MapEntry(k as int, (v as Uint8List).toList())); // Cast the data to the right type - Map serviceData = rawServiceData.cast(); + Map> serviceData = rawServiceData.map((k, v) => MapEntry(k as Guid, (v as Uint8List).toList())); // Cast the data to the right type List serviceUuids = rawServiceUuids.cast().map((e) => Guid(e)).toList(); @@ -438,7 +439,7 @@ class BmCharacteristicData { final Guid serviceUuid; final Guid characteristicUuid; final Guid? primaryServiceUuid; - final Uint8List value; + final List value; final bool success; final int errorCode; final String errorString; @@ -460,7 +461,7 @@ class BmCharacteristicData { serviceUuid: Guid(json['service_uuid']), characteristicUuid: Guid(json['characteristic_uuid']), primaryServiceUuid: Guid.parse(json['primary_service_uuid']), - value: json['value'], + value: (json['value'] as Uint8List).toList(), success: json['success'] != 0, errorCode: json['error_code'], errorString: json['error_string'], @@ -507,7 +508,7 @@ class BmWriteCharacteristicRequest { final Guid? primaryServiceUuid; final BmWriteType writeType; final bool allowLongWrite; - final Uint8List value; + final List value; BmWriteCharacteristicRequest({ required this.remoteId, @@ -527,7 +528,7 @@ class BmWriteCharacteristicRequest { data['primary_service_uuid'] = primaryServiceUuid?.str; data['write_type'] = writeType.index; data['allow_long_write'] = allowLongWrite ? 1 : 0; - data['value'] = value; + data['value'] = Uint8List.fromList(value); data.removeWhere((key, value) => value == null); return data; } @@ -539,7 +540,7 @@ class BmWriteDescriptorRequest { final Guid characteristicUuid; final Guid? primaryServiceUuid; final Guid descriptorUuid; - final Uint8List value; + final List value; BmWriteDescriptorRequest({ required this.remoteId, @@ -557,7 +558,7 @@ class BmWriteDescriptorRequest { data['characteristic_uuid'] = characteristicUuid.str; data['descriptor_uuid'] = descriptorUuid.str; data['primary_service_uuid'] = primaryServiceUuid?.str; - data['value'] = value; + data['value'] = Uint8List.fromList(value); data.removeWhere((key, value) => value == null); return data; } @@ -569,7 +570,7 @@ class BmDescriptorData { final Guid characteristicUuid; final Guid descriptorUuid; final Guid? primaryServiceUuid; - final Uint8List value; + final List value; final bool success; final int errorCode; final String errorString; @@ -593,7 +594,7 @@ class BmDescriptorData { characteristicUuid: Guid(json['characteristic_uuid']), descriptorUuid: Guid(json['descriptor_uuid']), primaryServiceUuid: Guid.parse(json['primary_service_uuid']), - value: json['value'], + value: (json['value'] as Uint8List).toList(), success: json['success'] != 0, errorCode: json['error_code'], errorString: json['error_string'], diff --git a/lib/src/flutter_blue_plus.dart b/lib/src/flutter_blue_plus.dart index af24e0fd..3a6d74d1 100644 --- a/lib/src/flutter_blue_plus.dart +++ b/lib/src/flutter_blue_plus.dart @@ -25,8 +25,8 @@ class FlutterBluePlus { static final Map _mtuValues = {}; static final Map _platformNames = {}; static final Map _advNames = {}; - static final Map> _lastChrs = {}; - static final Map> _lastDescs = {}; + static final Map>> _lastChrs = {}; + static final Map>> _lastDescs = {}; static final Map> _deviceSubscriptions = {}; static final Map> _delayedSubscriptions = {}; static final Map _connectTimestamp = {}; @@ -719,12 +719,12 @@ class MsdFilter { int manufacturerId; /// filter for this data - Uint8List data; + List data; /// For any bit in the mask, set it the 1 if it needs to match /// the one in manufacturer data, otherwise set it to 0. /// The 'mask' must have the same length as 'data'. - Uint8List mask; + List mask; MsdFilter(this.manufacturerId, {required this.data, required this.mask}); @@ -739,12 +739,12 @@ class ServiceDataFilter { Guid service; // filter for this data - Uint8List data; + List data; // For any bit in the mask, set it the 1 if it needs to match // the one in service data, otherwise set it to 0. // The 'mask' must have the same length as 'data'. - Uint8List mask; + List mask; ServiceDataFilter(this.service, {required this.data, required this.mask}); @@ -814,19 +814,19 @@ class AdvertisementData { final int? txPowerLevel; final int? appearance; // not supported on iOS / macOS final bool connectable; - final Map manufacturerData; // key: manufacturerId - final Map serviceData; // key: service guid + final Map> manufacturerData; // key: manufacturerId + final Map> serviceData; // key: service guid final List serviceUuids; /// for convenience, raw msd data /// * interprets the first two byte as raw data, /// as opposed to a `manufacturerId` - List get msd { - List output = []; + List> get msd { + List> output = []; manufacturerData.forEach((manufacturerId, bytes) { int low = manufacturerId & 0xFF; int high = (manufacturerId >> 8) & 0xFF; - output.add(Uint8List.fromList([low, high] + bytes)); + output.add([low, high] + bytes); }); return output; } diff --git a/lib/src/guid.dart b/lib/src/guid.dart index 8698b700..2de11c33 100644 --- a/lib/src/guid.dart +++ b/lib/src/guid.dart @@ -6,9 +6,9 @@ part of flutter_blue_plus; // Supports 16-bit, 32-bit, or 128-bit UUIDs class Guid { - final Uint8List bytes; + final List bytes; - Guid.empty() : bytes = Uint8List(16); + Guid.empty() : bytes = List.filled(16, 0); Guid.fromBytes(this.bytes) : assert(_checkLen(bytes.length), 'GUID must be 16, 32, or 128 bit.'); @@ -24,14 +24,14 @@ class Guid { } } - static Uint8List _toBytes(String input) { + static List _toBytes(String input) { if (input.isEmpty) { - return Guid.empty().bytes; + return List.filled(16, 0); } input = input.replaceAll('-', ''); - Uint8List? bytes = _tryHexDecode(input); + List? bytes = _tryHexDecode(input); if (bytes == null) { throw FormatException("GUID not hex format: $input"); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 3e20674c..d650381d 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,18 +1,18 @@ part of flutter_blue_plus; -String _hexEncode(Uint8List numbers) { +String _hexEncode(List numbers) { return numbers.map((n) => (n & 0xFF).toRadixString(16).padLeft(2, '0')).join(); } -Uint8List? _tryHexDecode(String hex) { - Uint8List numbers = Uint8List(hex.length ~/ 2); +List? _tryHexDecode(String hex) { + List numbers = []; for (int i = 0; i < hex.length; i += 2) { String hexPart = hex.substring(i, i + 2); int? num = int.tryParse(hexPart, radix: 16); if (num == null) { return null; } - numbers[i ~/ 2] = num; + numbers.add(num); } return numbers; } From d1df6dfd4427e86e1f7f1ab948198723112ffb79 Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Sun, 8 Dec 2024 09:07:20 +0800 Subject: [PATCH 6/7] remove perf screen and rollback breaking changes --- example/lib/screens/device_screen.dart | 20 +- example/lib/screens/performance_screen.dart | 243 -------------------- lib/src/bluetooth_msgs.dart | 6 +- lib/src/flutter_blue_plus.dart | 4 +- 4 files changed, 6 insertions(+), 267 deletions(-) delete mode 100644 example/lib/screens/performance_screen.dart diff --git a/example/lib/screens/device_screen.dart b/example/lib/screens/device_screen.dart index acc97dac..022e4883 100644 --- a/example/lib/screens/device_screen.dart +++ b/example/lib/screens/device_screen.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:flutter_blue_plus_example/screens/performance_screen.dart'; import '../utils/extra.dart'; import '../utils/snackbar.dart'; @@ -229,20 +228,6 @@ class _DeviceScreenState extends State { )); } - Widget buildPerformanceButton(BuildContext context) { - if (!isConnected) { - return const SizedBox.shrink(); - } - - return IconButton( - onPressed: () { - final route = MaterialPageRoute(builder: (context) => PerformanceScreen(device: widget.device)); - Navigator.of(context).push(route); - }, - icon: Icon(Icons.speed), - ); - } - Widget buildConnectButton(BuildContext context) { return Row(children: [ if (_isConnecting || _isDisconnecting) buildSpinner(context), @@ -262,10 +247,7 @@ class _DeviceScreenState extends State { child: Scaffold( appBar: AppBar( title: Text(widget.device.platformName), - actions: [ - buildPerformanceButton(context), - buildConnectButton(context), - ], + actions: [buildConnectButton(context)], ), body: SingleChildScrollView( child: Column( diff --git a/example/lib/screens/performance_screen.dart b/example/lib/screens/performance_screen.dart deleted file mode 100644 index 54d114e3..00000000 --- a/example/lib/screens/performance_screen.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_blue_plus/flutter_blue_plus.dart'; - -class PerformanceScreen extends StatefulWidget { - final BluetoothDevice device; - - const PerformanceScreen({ - Key? key, - required this.device, - }) : super(key: key); - - @override - State createState() => _PerformanceScreenState(); -} - -class _PerformanceScreenState extends State { - final epochText = TextEditingController(text: '1'); - final bytesText = TextEditingController(text: '512'); - final requestsText = TextEditingController(text: '100'); - final form = GlobalKey(); - final elapsedTimes = <_ElapsedTime>[]; - - String? error; - Stream? epochProgress; - Stream? writeProgress; - - BluetoothCharacteristic? get writableChar => widget.device.servicesList - .cast() - .firstWhere((service) { - return service!.characteristics.any((char) { - return char.properties.write | char.properties.writeWithoutResponse; - }); - }, orElse: () => null) - ?.characteristics - .firstWhere((char) => char.properties.write | char.properties.writeWithoutResponse); - - @override - Widget build(BuildContext context) { - late Widget body; - if (writeProgress == null) { - body = Form( - key: form, - child: ListView(children: [ - if (error != null) - Center( - child: Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Text(error!, style: const TextStyle(color: Colors.red)), - ), - ), - if (elapsedTimes.isNotEmpty) ...[ - Center(child: Text('History Table')), - SelectionArea( - child: Table( - border: TableBorder.all(color: Colors.grey), - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow(children: const [ - TableCell(child: Text('Start', textAlign: TextAlign.center)), - TableCell(child: Text('Duration', textAlign: TextAlign.center)), - ]), - for (final time in elapsedTimes) - TableRow(children: [ - TableCell(child: Text(time.startString(), textAlign: TextAlign.center)), - TableCell(child: Text(time.durationString(), textAlign: TextAlign.center)), - ]), - ], - ), - ), - const SizedBox(height: 8), - ], - Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - ElevatedButton( - onPressed: start, - child: const Text('Start'), - ), - ]), - const SizedBox(height: 8), - TextFormField( - controller: epochText, - decoration: const InputDecoration(labelText: 'Epoch'), - keyboardType: TextInputType.number, - validator: _validateNumber, - ), - TextFormField( - controller: bytesText, - decoration: const InputDecoration(labelText: 'Bytes to send'), - keyboardType: TextInputType.number, - validator: _validateNumber, - ), - TextFormField( - controller: requestsText, - decoration: const InputDecoration(labelText: 'Send requests'), - keyboardType: TextInputType.number, - validator: _validateNumber, - ), - ]), - ); - } else { - body = Center( - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamBuilder( - stream: writeProgress, - builder: (context, snapshot) { - final v = ((snapshot.data ?? 0) * 100).toInt(); - return Column(mainAxisSize: MainAxisSize.min, children: [ - CircularProgressIndicator(value: snapshot.data), - const SizedBox(height: 8), - Text('$v%'), - ]); - }, - ), - const SizedBox(height: 8), - StreamBuilder( - stream: epochProgress, - builder: (context, snapshot) { - final v = snapshot.data ?? 1; - final total = int.parse(epochText.text); - return Text('Epoch: $v/$total'); - }, - ), - ]), - ); - } - - return Scaffold( - appBar: AppBar(title: const Text('Performance')), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: body, - ), - ); - } - - void start() { - if (form.currentState?.validate() != true) { - return; - } - - final char = writableChar; - if (char == null) { - setState(() { - error = 'No writable characteristic found'; - }); - return; - } - - elapsedTimes.clear(); - - final controller = StreamController(); - final stream = batch(char, controller); - - setState(() { - writeProgress = stream; - epochProgress = controller.stream; - }); - } - - Stream batch(BluetoothCharacteristic char, StreamController controller) async* { - final bytes = await widget.device.requestMtu(int.parse(bytesText.text)); - - final epochs = int.parse(epochText.text); - final count = int.parse(requestsText.text); - final data = Uint8List.fromList(List.generate(bytes - 5, (index) => index)); - - var stop = false; - for (var i = 0; i < epochs; i++) { - elapsedTimes.add(_ElapsedTime.start()); - yield* _write(char, count, bytes, data).handleError((e) { - if (mounted) { - setState(() { - writeProgress = null; - epochProgress = null; - elapsedTimes.last.finish(); - error = e.toString(); - }); - } - - stop = true; - }); - if (stop) { - break; - } - - elapsedTimes.last.finish(); - controller.add(i + 1); - } - - await controller.close(); - setState(() { - writeProgress = null; - epochProgress = null; - error = null; - }); - } - - /// Write [bytes] length of data one by one and will send [count] times. - Stream _write( - BluetoothCharacteristic char, - int count, - int bytes, - Uint8List data, - ) async* { - for (var i = 0; i < count; i++) { - await char.write(data, withoutResponse: char.properties.writeWithoutResponse); - - yield (i + 1) / count; - } - } -} - -String? _validateNumber(String? value) { - return int.tryParse(value ?? '') == null ? 'Invalid number' : null; -} - -class _ElapsedTime { - final DateTime start; - late final DateTime end; - late final Duration elapsed; - - _ElapsedTime(this.start); - - factory _ElapsedTime.start() => _ElapsedTime(DateTime.now()); - - void finish() { - end = DateTime.now(); - elapsed = end.difference(start); - } - - String startString() { - final hour = start.hour.toString().padLeft(2, '0'); - final minute = start.minute.toString().padLeft(2, '0'); - final second = start.second.toString().padLeft(2, '0'); - return '$hour:$minute:$second'; - } - - String durationString() { - return (elapsed.inMilliseconds / 1000).toStringAsPrecision(5); - } -} diff --git a/lib/src/bluetooth_msgs.dart b/lib/src/bluetooth_msgs.dart index fe98d9cc..8b6681f0 100644 --- a/lib/src/bluetooth_msgs.dart +++ b/lib/src/bluetooth_msgs.dart @@ -127,9 +127,9 @@ class BmScanAdvertisement { factory BmScanAdvertisement.fromMap(Map json) { // Get raw data - Map rawManufacturerData = json['manufacturer_data'] ?? >{}; - Map rawServiceData = json['service_data'] ?? >{}; - List rawServiceUuids = json['service_uuids'] ?? []; + var rawManufacturerData = json['manufacturer_data'] ?? {}; + var rawServiceData = json['service_data'] ?? {}; + var rawServiceUuids = json['service_uuids'] ?? []; // Cast the data to the right type Map> manufacturerData = diff --git a/lib/src/flutter_blue_plus.dart b/lib/src/flutter_blue_plus.dart index 3a6d74d1..71fc7072 100644 --- a/lib/src/flutter_blue_plus.dart +++ b/lib/src/flutter_blue_plus.dart @@ -726,7 +726,7 @@ class MsdFilter { /// The 'mask' must have the same length as 'data'. List mask; - MsdFilter(this.manufacturerId, {required this.data, required this.mask}); + MsdFilter(this.manufacturerId, {this.data = const [], this.mask = const []}); // convert to bmMsg BmMsdFilter get _bm { @@ -746,7 +746,7 @@ class ServiceDataFilter { // The 'mask' must have the same length as 'data'. List mask; - ServiceDataFilter(this.service, {required this.data, required this.mask}); + ServiceDataFilter(this.service, {this.data = const [], this.mask = const []}); // convert to bmMsg BmServiceDataFilter get _bm { From 49a808baf043c029c888f1af49afeeb86ea0151a Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Sun, 8 Dec 2024 09:20:24 +0800 Subject: [PATCH 7/7] log hex when getting uint8list --- lib/src/flutter_blue_plus.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/src/flutter_blue_plus.dart b/lib/src/flutter_blue_plus.dart index 71fc7072..801fb142 100644 --- a/lib/src/flutter_blue_plus.dart +++ b/lib/src/flutter_blue_plus.dart @@ -619,8 +619,18 @@ class FlutterBluePlus { // log args if (logLevel == LogLevel.verbose) { + var logArgs = arguments; + if (arguments is Map) { + logArgs = arguments.map((key, value) { + if (value is Uint8List) { + value = _hexEncode(value); + } + return MapEntry(key, value); + }); + } + String func = '<$method>'; - String args = arguments.toString(); + String args = logArgs.toString(); func = _logColor ? _black(func) : func; args = _logColor ? _magenta(args) : args; log("[FBP] $func args: $args");