From b988809c0e3a5f654c82082fade0702b52dec07c Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Fri, 10 Jan 2025 20:10:49 +0100 Subject: [PATCH 1/2] [iOS] Fix detection of devices via their Service UUID (CircuitCubes, WeDo2) --- .../BluetoothLE/BluetoothLEService.cs | 34 +++++++++++++++++-- .../BluetoothDeviceManager.cs | 8 +++-- .../Protocols/BluetoothLowEnergy.cs | 9 +++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 BrickController2/BrickController2/Protocols/BluetoothLowEnergy.cs diff --git a/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs b/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs index 5f88b253..33655f09 100644 --- a/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs +++ b/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs @@ -9,6 +9,8 @@ using Foundation; using BrickController2.PlatformServices.BluetoothLE; +using static BrickController2.Protocols.BluetoothLowEnergy; + namespace BrickController2.iOS.PlatformServices.BluetoothLE { public class BluetoothLEService : CBCentralManagerDelegate, IBluetoothLEService @@ -112,13 +114,19 @@ private IDictionary ProcessAdvertisementData(NSDictionary advertis var manufacturerData = GetDataForKey(advertisementData, CBAdvertisement.DataManufacturerDataKey); if (manufacturerData is not null) { - result[0xFF] = manufacturerData; + result[ADTYPE_MANUFACTURER_SPECIFIC] = manufacturerData; } var completeDeviceName = GetDataForKey(advertisementData, CBAdvertisement.DataLocalNameKey); if (completeDeviceName is not null) { - result[0x09] = completeDeviceName; + result[ADTYPE_LOCAL_NAME_COMPLETE] = completeDeviceName; + } + + var serviceUuid = GetServiceUuidForKey(advertisementData, CBAdvertisement.DataServiceUUIDsKey); + if (serviceUuid is not null) + { + result[ADTYPE_SERVICE_128BIT] = serviceUuid; } // TODO: add the rest of the advertisementdata... @@ -145,5 +153,27 @@ private IDictionary ProcessAdvertisementData(NSDictionary advertis return null; } + + private static byte[]? GetServiceUuidForKey(NSDictionary advertisementData, NSString key) + { + if (advertisementData != null && + advertisementData.TryGetValue(key, out var rawObject) && + rawObject is NSArray arrayObject) + { + // find first available 128-bit UUID + for (nuint i = 0; i < arrayObject.Count; i++) + { + var cbuuid = arrayObject.GetItem(i); + if (cbuuid.Data.Length == 16) + { + // Service UUID's are read backwards (little endian) according to specs + var serviceUUid = cbuuid.Data.ToArray(); + Array.Reverse(serviceUUid); + return serviceUUid; + } + } + } + return null; + } } } \ No newline at end of file diff --git a/BrickController2/BrickController2/DeviceManagement/BluetoothDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/BluetoothDeviceManager.cs index 821959b4..9b066daa 100644 --- a/BrickController2/BrickController2/DeviceManagement/BluetoothDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/BluetoothDeviceManager.cs @@ -6,6 +6,8 @@ using BrickController2.Helpers; using BrickController2.PlatformServices.BluetoothLE; +using static BrickController2.Protocols.BluetoothLowEnergy; + namespace BrickController2.DeviceManagement { internal class BluetoothDeviceManager : IBluetoothDeviceManager @@ -61,7 +63,7 @@ public async Task ScanAsync(Func ScanAsync(Func ScanAsync(Func advertismentData) { // 0x06: 128 bits Service UUID type - if (!advertismentData.TryGetValue(0x06, out byte[]? serviceData) || serviceData.Length < 16) + if (!advertismentData.TryGetValue(ADTYPE_SERVICE_128BIT, out byte[]? serviceData) || serviceData.Length < 16) { return (DeviceType.Unknown, null); } diff --git a/BrickController2/BrickController2/Protocols/BluetoothLowEnergy.cs b/BrickController2/BrickController2/Protocols/BluetoothLowEnergy.cs new file mode 100644 index 00000000..b28141d8 --- /dev/null +++ b/BrickController2/BrickController2/Protocols/BluetoothLowEnergy.cs @@ -0,0 +1,9 @@ +namespace BrickController2.Protocols; + +public static class BluetoothLowEnergy +{ + // advertisment data types + public const byte ADTYPE_SERVICE_128BIT = 0x06; // Service: Additional 128-bit UUIDs + public const byte ADTYPE_LOCAL_NAME_COMPLETE = 0x09; // Complete local name + public const byte ADTYPE_MANUFACTURER_SPECIFIC = 0xFF; // Manufacturer specific data +} From 522ce1fc0220b9fa0b91c752702af970cc4608b2 Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Fri, 10 Jan 2025 23:25:01 +0100 Subject: [PATCH 2/2] Fix characteristic writes as iOS seems to be sensitive if mistyped. --- .../BrickController2/DeviceManagement/CircuitCubeDevice.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BrickController2/BrickController2/DeviceManagement/CircuitCubeDevice.cs b/BrickController2/BrickController2/DeviceManagement/CircuitCubeDevice.cs index 66d655fc..636f9e04 100644 --- a/BrickController2/BrickController2/DeviceManagement/CircuitCubeDevice.cs +++ b/BrickController2/BrickController2/DeviceManagement/CircuitCubeDevice.cs @@ -187,7 +187,7 @@ private async Task SendDriveCommandAsync(int[] values, CancellationToken t commendBytes.CopyTo(_driveMotorsBuffer, idx); idx += 5; } - return await _bleDevice!.WriteAsync(_writeCharacteristic!, _driveMotorsBuffer, token); + return await _bleDevice!.WriteNoResponseAsync(_writeCharacteristic!, _driveMotorsBuffer, token); } catch (Exception) { @@ -199,7 +199,7 @@ private async Task SendStopCommandAsync(CancellationToken token) { try { - return await _bleDevice!.WriteAsync(_writeCharacteristic!, TURN_OFF_ALL_COMMAND, token); + return await _bleDevice!.WriteNoResponseAsync(_writeCharacteristic!, TURN_OFF_ALL_COMMAND, token); } catch (Exception) { @@ -223,7 +223,7 @@ private async Task ReadDeviceInfo(CancellationToken token) HardwareVersion = hardwareRevision; } - await _bleDevice!.WriteAsync(_writeCharacteristic!, BATTERY_STATUS_COMMAND, token); + await _bleDevice!.WriteNoResponseAsync(_writeCharacteristic!, BATTERY_STATUS_COMMAND, token); } } } \ No newline at end of file