diff --git a/example/lib/peripheral_details/peripheral_detail_page.dart b/example/lib/peripheral_details/peripheral_detail_page.dart index a6e0a2f..b37273a 100644 --- a/example/lib/peripheral_details/peripheral_detail_page.dart +++ b/example/lib/peripheral_details/peripheral_detail_page.dart @@ -152,7 +152,10 @@ class _PeripheralDetailPageState extends State { selectedCharacteristic!.service.uuid, selectedCharacteristic!.characteristic.uuid, value, - BleOutputProperty.withResponse, + _hasSelectedCharacteristicProperty( + [CharacteristicProperty.writeWithoutResponse]) + ? BleOutputProperty.withoutResponse + : BleOutputProperty.withResponse, ); _addLog('Write', value); } catch (e) { @@ -187,17 +190,6 @@ class _PeripheralDetailPageState extends State { } } - bool isValidProperty(List properties) { - for (CharacteristicProperty property in properties) { - if (selectedCharacteristic?.characteristic.properties - .contains(property) ?? - false) { - return true; - } - } - return false; - } - @override Widget build(BuildContext context) { return Scaffold( @@ -307,7 +299,7 @@ class _PeripheralDetailPageState extends State { ), ), - if (isValidProperty([ + if (_hasSelectedCharacteristicProperty([ CharacteristicProperty.write, CharacteristicProperty.writeWithoutResponse ])) @@ -375,7 +367,7 @@ class _PeripheralDetailPageState extends State { PlatformButton( enabled: isConnected && discoveredServices.isNotEmpty && - isValidProperty([ + _hasSelectedCharacteristicProperty([ CharacteristicProperty.read, ]), onPressed: _readValue, @@ -384,7 +376,7 @@ class _PeripheralDetailPageState extends State { PlatformButton( enabled: isConnected && discoveredServices.isNotEmpty && - isValidProperty([ + _hasSelectedCharacteristicProperty([ CharacteristicProperty.write, CharacteristicProperty.writeWithoutResponse ]), @@ -394,7 +386,7 @@ class _PeripheralDetailPageState extends State { PlatformButton( enabled: isConnected && discoveredServices.isNotEmpty && - isValidProperty([ + _hasSelectedCharacteristicProperty([ CharacteristicProperty.notify, CharacteristicProperty.indicate ]), @@ -405,7 +397,7 @@ class _PeripheralDetailPageState extends State { PlatformButton( enabled: isConnected && discoveredServices.isNotEmpty && - isValidProperty([ + _hasSelectedCharacteristicProperty([ CharacteristicProperty.notify, CharacteristicProperty.indicate ]), @@ -476,4 +468,11 @@ class _PeripheralDetailPageState extends State { }), ); } + + bool _hasSelectedCharacteristicProperty( + List properties) => + properties.any((property) => + selectedCharacteristic?.characteristic.properties + .contains(property) ?? + false); } diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index 26dd1c7..0c7661a 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -18,6 +18,7 @@ class UniversalBleWeb extends UniversalBlePlatform { final Map _deviceAdvertisementStreamList = {}; final Map _connectedDeviceStreamList = {}; final Map _characteristicStreamList = {}; + final Map> _serviceCache = {}; @override Future getConnectionState(String deviceId) async { @@ -56,47 +57,8 @@ class UniversalBleWeb extends UniversalBlePlatform { } @override - Future> discoverServices(String deviceId) async { - final device = _getDeviceById(deviceId); - final services = await device?.discoverServices(); - if (services == null) return []; - var discoveredServices = []; - for (var service in services) { - final characteristics = await service.getCharacteristics(); - List bleCharacteristics = []; - for (BluetoothCharacteristic characteristic in characteristics) { - List properties = []; - if (characteristic.properties.broadcast) { - properties.add(CharacteristicProperty.broadcast); - } - if (characteristic.properties.read) { - properties.add(CharacteristicProperty.read); - } - if (characteristic.properties.writeWithoutResponse) { - properties.add(CharacteristicProperty.writeWithoutResponse); - } - if (characteristic.properties.write) { - properties.add(CharacteristicProperty.write); - } - if (characteristic.properties.notify) { - properties.add(CharacteristicProperty.notify); - } - if (characteristic.properties.indicate) { - properties.add(CharacteristicProperty.indicate); - } - if (characteristic.properties.authenticatedSignedWrites) { - properties.add(CharacteristicProperty.authenticatedSignedWrites); - } - bleCharacteristics.add( - BleCharacteristic(characteristic.uuid.toString(), properties), - ); - } - discoveredServices.add( - BleService(service.uuid.toString(), bleCharacteristics), - ); - } - return discoveredServices; - } + Future> discoverServices(String deviceId) async => + (await _getServices(deviceId)).map((e) => e._bleService).toList(); @override Future getBluetoothAvailabilityState() async { @@ -118,6 +80,7 @@ class UniversalBleWeb extends UniversalBlePlatform { ScanFilter? scanFilter, PlatformConfig? platformConfig, }) async { + FlutterWebBluetooth.instance.isAvailable; BluetoothDevice device = await FlutterWebBluetooth.instance.requestDevice( _getRequestOptionBuilder(scanFilter, platformConfig?.web), ); @@ -322,6 +285,7 @@ class UniversalBleWeb extends UniversalBlePlatform { return key.contains(deviceId); }); _disposeAdvertisementWatcher(deviceId); + _serviceCache.remove(deviceId); // _bluetoothDeviceList.removeWhere((element) => element.id == deviceId); } @@ -330,20 +294,33 @@ class UniversalBleWeb extends UniversalBlePlatform { required String serviceId, required String characteristicId, }) async { - final device = _getDeviceById(deviceId); - List services = await device?.discoverServices() ?? []; - BluetoothService? service; - for (BluetoothService bleService in services) { - if (bleService.uuid.toString() == serviceId.toString()) { - service = bleService; - break; + for (var service in await _getServices(deviceId)) { + if (BleUuidParser.compareStrings(service.uuid, serviceId)) { + return service.getCharacteristic(characteristicId); } } - return await service?.getCharacteristic(characteristicId.toString()); + return null; } BluetoothDevice? _getDeviceById(String id) => _bluetoothDeviceList[id]; + /// Get services and their characteristics. + /// Services and characteristics are cached. + /// Clears cache on disconnection. + Future> _getServices( + String deviceId, + ) async { + BluetoothDevice? device = _getDeviceById(deviceId); + if (device == null) return []; + var services = _serviceCache[deviceId] ?? []; + if (services.isNotEmpty) return services; + for (var service in await device.discoverServices()) { + services.add(await _UniversalWebBluetoothService.fromService(service)); + } + _serviceCache[deviceId] = services; + return services; + } + void _disposeAdvertisementWatcher([String? deviceId]) { _deviceAdvertisementStreamList.removeWhere((key, value) { if (deviceId != null && key != deviceId) return false; @@ -463,3 +440,51 @@ extension _UnmodifiableMapViewExtension on UnmodifiableMapView { return Uint8List.fromList(bytes + manufacturerDataValue); } } + +class _UniversalWebBluetoothService { + late String uuid; + BluetoothService service; + List characteristics; + + _UniversalWebBluetoothService({ + required this.service, + required this.characteristics, + }) { + uuid = service.uuid; + } + + static Future<_UniversalWebBluetoothService> fromService( + BluetoothService service, + ) async { + return _UniversalWebBluetoothService( + service: service, + characteristics: await service.getCharacteristics(), + ); + } + + BluetoothCharacteristic? getCharacteristic(String characteristicId) { + for (var characteristic in characteristics) { + if (BleUuidParser.compareStrings(characteristic.uuid, characteristicId)) { + return characteristic; + } + } + return null; + } + + BleService get _bleService => BleService( + service.uuid, + characteristics.map((e) { + return BleCharacteristic(e.uuid, [ + if (e.properties.broadcast) CharacteristicProperty.broadcast, + if (e.properties.read) CharacteristicProperty.read, + if (e.properties.write) CharacteristicProperty.write, + if (e.properties.writeWithoutResponse) + CharacteristicProperty.writeWithoutResponse, + if (e.properties.notify) CharacteristicProperty.notify, + if (e.properties.indicate) CharacteristicProperty.indicate, + if (e.properties.authenticatedSignedWrites) + CharacteristicProperty.authenticatedSignedWrites, + ]); + }).toList(), + ); +}