From 0711389d00112e4b6782d873a559b5b89aff2a8e Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Tue, 3 Feb 2026 21:26:51 +0100 Subject: [PATCH 1/7] CADA RaceCar investigation --- ...tensions.cs => AdvertisementExtensions.cs} | 5 ++-- .../BluetoothLE/BleScanner.cs | 13 +++++----- .../CaDA/CaDADeviceManager.cs | 24 ++++++++++++++++--- 3 files changed, 31 insertions(+), 11 deletions(-) rename BrickController2/BrickController2.WinUI/Extensions/{AdvertismentExtensions.cs => AdvertisementExtensions.cs} (71%) diff --git a/BrickController2/BrickController2.WinUI/Extensions/AdvertismentExtensions.cs b/BrickController2/BrickController2.WinUI/Extensions/AdvertisementExtensions.cs similarity index 71% rename from BrickController2/BrickController2.WinUI/Extensions/AdvertismentExtensions.cs rename to BrickController2/BrickController2.WinUI/Extensions/AdvertisementExtensions.cs index 5526b3b4..e9d1bac1 100644 --- a/BrickController2/BrickController2.WinUI/Extensions/AdvertismentExtensions.cs +++ b/BrickController2/BrickController2.WinUI/Extensions/AdvertisementExtensions.cs @@ -2,7 +2,7 @@ namespace BrickController2.Windows.Extensions; -public static class AdvertismentExtensions +public static class AdvertisementExtensions { public static string GetLocalName(this BluetoothLEAdvertisementReceivedEventArgs args) => args.Advertisement.LocalName.TrimEnd(); @@ -10,5 +10,6 @@ public static class AdvertismentExtensions public static bool CanCarryData(this BluetoothLEAdvertisementReceivedEventArgs args) => args.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse || - args.AdvertisementType == BluetoothLEAdvertisementType.ConnectableUndirected; + args.AdvertisementType == BluetoothLEAdvertisementType.ConnectableUndirected || + args.AdvertisementType == BluetoothLEAdvertisementType.NonConnectableUndirected; // some per advertisement devices which are not directly connectable } diff --git a/BrickController2/BrickController2.WinUI/PlatformServices/BluetoothLE/BleScanner.cs b/BrickController2/BrickController2.WinUI/PlatformServices/BluetoothLE/BleScanner.cs index d0a4f556..7e54b653 100644 --- a/BrickController2/BrickController2.WinUI/PlatformServices/BluetoothLE/BleScanner.cs +++ b/BrickController2/BrickController2.WinUI/PlatformServices/BluetoothLE/BleScanner.cs @@ -59,7 +59,7 @@ private void _passiveWatcher_Received(BluetoothLEAdvertisementWatcher sender, Bl else if (args.Advertisement.DataSections?.Count > 0) { // allow processing of advertised data from a device - var advertismentData = args.Advertisement.DataSections + var advertisementData = args.Advertisement.DataSections .Where(s => AdvertismentDataTypes.Contains(s.DataType)) .ToDictionary(s => s.DataType, s => s.Data.ToByteArray()); @@ -68,7 +68,7 @@ private void _passiveWatcher_Received(BluetoothLEAdvertisementWatcher sender, Bl // if no local name is set, try to get it from the cache _deviceNameCache.TryGetValue(args.BluetoothAddress, out deviceName); - _scanCallback(new ScanResult(deviceName, bluetoothAddress, advertismentData)); + _scanCallback(new ScanResult(deviceName, bluetoothAddress, advertisementData)); } } @@ -79,6 +79,7 @@ private void _passiveWatcher_Stopped(BluetoothLEAdvertisementWatcher sender, Blu private void _activeWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) { + var bt = args.BluetoothAddress; if (!args.CanCarryData()) { return; @@ -92,17 +93,17 @@ private void _activeWatcher_Received(BluetoothLEAdvertisementWatcher sender, Blu var bluetoothAddress = args.BluetoothAddress.ToBluetoothAddressString(); - var advertismentData = args.Advertisement.DataSections + var advertisementData = args.Advertisement.DataSections .Where(s => AdvertismentDataTypes.Contains(s.DataType)) .ToDictionary(s => s.DataType, s => s.Data.ToByteArray()); // enrich data with name manually (SBrick do not like CompleteLocalName, but Buwizz3 requires it) - if (!advertismentData.ContainsKey(BluetoothLEAdvertisementDataTypes.CompleteLocalName)) + if (!advertisementData.ContainsKey(BluetoothLEAdvertisementDataTypes.CompleteLocalName)) { - advertismentData[BluetoothLEAdvertisementDataTypes.CompleteLocalName] = Encoding.ASCII.GetBytes(deviceName); + advertisementData[BluetoothLEAdvertisementDataTypes.CompleteLocalName] = Encoding.ASCII.GetBytes(deviceName); } - _scanCallback(new ScanResult(deviceName, bluetoothAddress, advertismentData)); + _scanCallback(new ScanResult(deviceName, bluetoothAddress, advertisementData)); } public void Stop() diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs index 624a9865..ad6eae6c 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs @@ -82,7 +82,25 @@ protected override bool TryGetDeviceByManufacturerData(ScanResult scanResult, Fo } break; - // extend if needed to other CaDA devices + // extend if needed to other CaDA devices + case 0x11aa: + //TODO this check does not work as there are only 16 bytes + if (IsCadaRaceCar(manufacturerData)) + { + // the origin deviceAddress is changing on every scan-response + // but inside the manufacturerData are 3 bytes identifying the device + string deviceAddress = BitConverter.ToString(manufacturerData.Slice(4, 3).ToArray()).ToLower(); // change device address + + device = template with + { + DeviceType = DeviceType.CaDA_RaceCar, + DeviceAddress = deviceAddress, // change device address, + DeviceName = template.DeviceName ?? $"CaDA {deviceAddress}" // an empty device name is given so create one + }; + return true; + } + break; + } // no match device = default; @@ -105,14 +123,14 @@ private bool IsCadaRaceCar(ReadOnlySpan manufacturerData) } /// - /// gets or creates an App-persistant AppIdentifier + /// gets or creates an App-persistent AppIdentifier /// /// reference to preferencesService singleton /// byte array containing the AppIdentifier private static byte[] GetAppIdentifier(IPreferencesService preferencesService) { byte[] appIdChecksumMaskArray; - // gets or creates an App-persistant AppIdentifier + // gets or creates an App-persistent AppIdentifier try { if (preferencesService.ContainsKey(APPIDKEY, SECTION)) From 5970812c023d1fbe8d507ac8eab3f236f7829c8d Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Fri, 6 Feb 2026 23:40:31 +0100 Subject: [PATCH 2/7] Base race car rev2 detected by scan --- .../CaDA/CaDADeviceManager.cs | 20 +-- .../DeviceManagement/CaDA/CaDARaceCarRev2.cs | 129 ++++++++++++++++++ .../DeviceManagement/CaDA/CaDa.cs | 24 ++++ .../DI/DeviceManagementModule.cs | 2 - .../DeviceManagement/DeviceType.cs | 1 + .../Converters/DeviceTypeToImageConverter.cs | 1 + .../DeviceTypeToSmallImageConverter.cs | 1 + 7 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/CaDA/CaDa.cs diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs index ad6eae6c..904b4bec 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs @@ -84,18 +84,11 @@ protected override bool TryGetDeviceByManufacturerData(ScanResult scanResult, Fo // extend if needed to other CaDA devices case 0x11aa: - //TODO this check does not work as there are only 16 bytes - if (IsCadaRaceCar(manufacturerData)) + if (IsCadaRaceCarRev2(manufacturerData)) { - // the origin deviceAddress is changing on every scan-response - // but inside the manufacturerData are 3 bytes identifying the device - string deviceAddress = BitConverter.ToString(manufacturerData.Slice(4, 3).ToArray()).ToLower(); // change device address - device = template with { - DeviceType = DeviceType.CaDA_RaceCar, - DeviceAddress = deviceAddress, // change device address, - DeviceName = template.DeviceName ?? $"CaDA {deviceAddress}" // an empty device name is given so create one + DeviceType = DeviceType.CaDA_RaceCar_Rev2 }; return true; } @@ -122,6 +115,15 @@ private bool IsCadaRaceCar(ReadOnlySpan manufacturerData) manufacturerData[9] == _appIdChecksumMaskArray[2]; } + private bool IsCadaRaceCarRev2(ReadOnlySpan manufacturerData) => manufacturerData.Length == 16 && + manufacturerData[2] == 0x11 && + // default advertisement + ( + (manufacturerData[3] == 0x00 && manufacturerData[4] == 0x00) || + // response has to have the same appId + (manufacturerData[3] == _appIdChecksumMaskArray[0] && manufacturerData[4] == _appIdChecksumMaskArray[1]) + ); + /// /// gets or creates an App-persistent AppIdentifier /// diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs new file mode 100644 index 00000000..947abdf3 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs @@ -0,0 +1,129 @@ +using BrickController2.DeviceManagement.IO; +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.Protocols; +using System; + +namespace BrickController2.DeviceManagement.CaDA; + +/// +/// CaDA RaceCar - hardware revision 2 +/// +internal class CaDARaceCarRev2 : BluetoothAdvertisingDevice +{ + /// + /// byte-array including DeviceAddress, AppID and channelData + /// + private readonly byte[] _controlDataArray = + // 16 + [ + 0x75, // [0] const 0x75 (117) + 0x13, // [1] 0x13 (19) STATUS_CONTROL + 0x00, // [2] DeviceAddress + 0x00, // [3] DeviceAddress + 0x00, // [4] DeviceAddress + 0x00, // [5] AppID + 0x00, // [6] AppID + 0x00, // [7] AppID + 0x00, // [8] ChannelData random + 0x00, // [9] ChannelData random + 0x80, // [10] ChannelData verticalValue (min= 0x80 (128)) + 0x80, // [11] ChannelData horizontalValue (min= 0x80 (128)) + 0x00, // [12] ChannelData lightValue + 0x00, // [13] ChannelData + 0x00, // [14] ChannelData + 0x00, // [15] ChannelData + ]; + + private readonly ICaDAPlatformService _cadaPlatformService; + private readonly OutputValuesGroup _outputValues = new(3); + + public CaDARaceCarRev2(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, ICaDAPlatformService cadaPlatformService) + : base(name, address, deviceData, deviceRepository, bleService) + { + _cadaPlatformService = cadaPlatformService; + + // revisions + if (deviceData?.Length == 16) + { + // DeviceData-Array is the manufacturer specific data inside the response telegram sent when + // * scanning for the device. + // * loading the device from database + + // It's containing: + // * DeviceAddress of the real CaDA device + // * AppID sent from this App on scanning + // These values are patched into the DataArray which is advertised to control the device. + _controlDataArray[2] = deviceData[4]; // DeviceAddress + _controlDataArray[3] = deviceData[5]; // DeviceAddress + _controlDataArray[4] = deviceData[6]; // DeviceAddress + + _controlDataArray[5] = deviceData[7]; // AppID + _controlDataArray[6] = deviceData[8]; // AppID + _controlDataArray[7] = deviceData[9]; // AppID + } + else + { + throw new ApplicationException($"Invalid {nameof(deviceData)} array!"); + } + } + public override DeviceType DeviceType => DeviceType.CaDA_RaceCar_Rev2; + + /// + /// manufacturerId to advertise + /// + protected override ushort ManufacturerId => CaDAProtocol.ManufacturerID; + + public override int NumberOfChannels => 3; + + public override void SetOutput(int channelNo, float value) + { + CheckChannel(channelNo); + value = CutOutputValue(value); + + var rawValue = (short)(value * 0x7F); // scale and cast + _outputValues.SetOutput(channelNo, rawValue); + } + + protected override void InitDevice() + { + } + + protected override void DisconnectDevice() + { + } + + protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] currentData) + { + ushort random = (ushort)Random.Shared.Next(ushort.MinValue, ushort.MaxValue); + + _outputValues.TryGetValues(out var values); + + byte[] channelDataArray = + // 8 + [ + (byte)(random & 0xFF), + (byte)((random >> 8) & 0xFF), + (byte)Math.Max(0, Math.Min(0x80 - values[0], 0xFF)), // speed value - reversed + (byte)Math.Max(0, Math.Min(0x80 + values[1], 0xFF)), // + (byte)Math.Max(0, Math.Min(0x80 + values[2], 0xFF)), // light on/off + 0, + 0, + 0 + ]; + + CaDAProtocol.Encrypt(channelDataArray); + // copy channel data to control data as index 8..15 + channelDataArray.AsSpan().CopyTo(_controlDataArray.AsSpan(8)); + + _controlDataArray[0] = 0x75; // 0x75 (117) + _controlDataArray[1] = 0x13; // 0x13 (19); + + return _cadaPlatformService.TryGetRfPayload(_controlDataArray, out currentData); + } + + /// + /// Get or create BluetoothAdvertisingDeviceHandler + /// + protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDeviceHandler() + => new(_bleService, ManufacturerId, TryGetTelegram, TimeSpan.MaxValue); +} diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDa.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDa.cs new file mode 100644 index 00000000..172f0c2c --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDa.cs @@ -0,0 +1,24 @@ +using BrickController2.DeviceManagement.DI; +using BrickController2.DeviceManagement.Vendors; +using BrickController2.Extensions; +using BrickController2.PlatformServices.BluetoothLE; + +namespace BrickController2.DeviceManagement.CaDA; + +/// +/// Vendor: CaDa with all its devices and implementation of IBluetoothLEDeviceManager +/// +internal class CaDa : Vendor +{ + public override string VendorName => "CaDA"; + + protected override void Register(VendorBuilder builder) + { + // classic devices + builder.ContainerBuilder.RegisterDevice(DeviceType.CaDA_RaceCar); + builder.ContainerBuilder.RegisterDevice(DeviceType.CaDA_RaceCar_Rev2); + + // device manager + builder.RegisterDeviceManager().As(); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/DI/DeviceManagementModule.cs b/BrickController2/BrickController2/DeviceManagement/DI/DeviceManagementModule.cs index f8fc1442..76a5e2ee 100644 --- a/BrickController2/BrickController2/DeviceManagement/DI/DeviceManagementModule.cs +++ b/BrickController2/BrickController2/DeviceManagement/DI/DeviceManagementModule.cs @@ -25,7 +25,6 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().Keyed(DeviceType.BuWizz3); builder.RegisterType().Keyed(DeviceType.Infrared); builder.RegisterType().Keyed(DeviceType.CircuitCubes); - builder.RegisterType().Keyed(DeviceType.CaDA_RaceCar); builder.RegisterType().Keyed(DeviceType.PfxBrick); builder.Register(c => @@ -40,7 +39,6 @@ protected override void Load(ContainerBuilder builder) // device managers builder.RegisterDeviceManager(); - builder.RegisterDeviceManager().As(); builder.RegisterDeviceManager(); builder.RegisterDeviceManager(); builder.RegisterDeviceManager(); diff --git a/BrickController2/BrickController2/DeviceManagement/DeviceType.cs b/BrickController2/BrickController2/DeviceManagement/DeviceType.cs index e7f2afd2..c5ab9586 100644 --- a/BrickController2/BrickController2/DeviceManagement/DeviceType.cs +++ b/BrickController2/BrickController2/DeviceManagement/DeviceType.cs @@ -23,5 +23,6 @@ public enum DeviceType MK5, MK3_8, RemoteControl, + CaDA_RaceCar_Rev2, } } diff --git a/BrickController2/BrickController2/UI/Converters/DeviceTypeToImageConverter.cs b/BrickController2/BrickController2/UI/Converters/DeviceTypeToImageConverter.cs index ec45aa58..9ca28313 100644 --- a/BrickController2/BrickController2/UI/Converters/DeviceTypeToImageConverter.cs +++ b/BrickController2/BrickController2/UI/Converters/DeviceTypeToImageConverter.cs @@ -71,6 +71,7 @@ public class DeviceTypeToImageConverter : IValueConverter return ResourceHelper.GetImageResource("mk_diy_image.png"); case DeviceType.CaDA_RaceCar: + case DeviceType.CaDA_RaceCar_Rev2: return ResourceHelper.GetImageResource("cada_racecar_image.png"); case DeviceType.RemoteControl: diff --git a/BrickController2/BrickController2/UI/Converters/DeviceTypeToSmallImageConverter.cs b/BrickController2/BrickController2/UI/Converters/DeviceTypeToSmallImageConverter.cs index 532a6965..2f186487 100644 --- a/BrickController2/BrickController2/UI/Converters/DeviceTypeToSmallImageConverter.cs +++ b/BrickController2/BrickController2/UI/Converters/DeviceTypeToSmallImageConverter.cs @@ -63,6 +63,7 @@ public class DeviceTypeToSmallImageConverter : IValueConverter return ResourceHelper.GetImageResource("mk_diy_image_small.png"); case DeviceType.CaDA_RaceCar: + case DeviceType.CaDA_RaceCar_Rev2: return ResourceHelper.GetImageResource("cada_racecar_image_small.png"); case DeviceType.PfxBrick: From dc5fc5e969c612f3eb8ba6ed8d8c7f5d3ab06ca5 Mon Sep 17 00:00:00 2001 From: vicocz Date: Sun, 8 Feb 2026 13:27:00 +0100 Subject: [PATCH 3/7] finetune - Rev2 detection - only advetised ones --- .../CaDA/CaDADeviceManagerTests.cs | 82 ++++++++++++++++++- .../CaDA/CaDARaceCarRev2Tests.cs | 56 +++++++++++++ .../DeviceManagement/CaDA/MockSetups.cs | 37 +++++++++ .../CaDA/CaDADeviceManager.cs | 14 ++-- .../DeviceManagement/CaDA/CaDARaceCarRev2.cs | 61 +++++++------- 5 files changed, 211 insertions(+), 39 deletions(-) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/CaDA/MockSetups.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs index 3595d7ab..09a470c4 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs @@ -30,6 +30,23 @@ public CaDADeviceManagerTests() _manager = new CaDADeviceManager(_preferencesService.Object, _cadaPlatformService.Object); } + [Fact] + public void CreateScanData_IosPlatform_PatchesAppIdIntoScanData() + { + // arrange + _cadaPlatformService.TryGetRfPayload_ForIosPlatform(); + + var scanData = _manager.CreateScanData(); + + scanData.Should().BeEquivalentTo(new[] + { + 0xC0, 0x3D, 0xCA, 0x66, 0x6D, 0x32, 0xB2, 0x9D, + 0xD2, 0x57, 0xA1, 0x5C, 0xC5, 0x05, 0xB0, 0x75, + 0xB1, 0x91, 0x48, 0x96, 0x77, 0xF8, 0x00, 0x8D, + 0x18, 0x19 + }); + } + [Fact] public void TryGetDevice_CadaCarWithMatchingAppId_ReturnsCaDaRaceCarDevice() { @@ -65,7 +82,7 @@ public void TryGetDevice_CadaCarWithMatchingAppId_ReturnsCaDaRaceCarDevice() } [Fact] - public void TryGetDevice_CadaCarWithDifferentAppId_ReturnsCaDaRaceCarDevice() + public void TryGetDevice_CadaCarWithDifferentAppId_ReturnsFalse() { byte[] manufacturerData = { @@ -91,4 +108,67 @@ public void TryGetDevice_CadaCarWithDifferentAppId_ReturnsCaDaRaceCarDevice() result.Should().BeFalse(); device.DeviceType.Should().Be(DeviceType.Unknown); } + + [Fact] + public void TryGetDevice_CadaCarRev2WithZeroAppId_ReturnsCaDaRaceCarRev2Device() + { + byte[] manufacturerData = + [ + // manufacturerId + 0xAA,0x11, + // CADA RaceCar + 0x11, + // 2 bytes AppID + 0x00, 0x00, + // other data + 0x20, 0xB9, + // flag + 0x85, + 0x00, 0x00, 0x00, 0xA1, 0xCC, 0xB8, 0x92, 0xA0 + ]; + + var scanResult = new ScanResult("RaceCar-Revision2", "AA-BB-CC-DD", new Dictionary() + { + { 0xFF, manufacturerData } + }); + + var result = _manager.TryGetDevice(scanResult, out var device); + + result.Should().BeTrue(); + device.Should().BeEquivalentTo(new FoundDevice() + { + DeviceAddress = "AA-BB-CC-DD", + DeviceName = "RaceCar-Revision2", + DeviceType = DeviceType.CaDA_RaceCar_Rev2, + ManufacturerData = manufacturerData + }); + } + + [Fact] + public void TryGetDevice_CadaCarRev2WithSomeAppId_ReturnsFalse() + { + byte[] manufacturerData = + [ + // manufacturerId + 0xAA,0x11, + // CADA RaceCar + 0x11, + // 2 bytes AppID + 0x12, 0x34, + // other data + 0x20, 0xB9, + // flag + 0x86, + 0x00, 0x00, 0x00, 0xA1, 0xCC, 0xB8, 0x92, 0xA0 + ]; + + var scanResult = new ScanResult("RaceCar-Revision2", "AA-BB-CC-DD", new Dictionary() + { + { 0xFF, manufacturerData } + }); + + var result = _manager.TryGetDevice(scanResult, out var device); + + result.Should().BeFalse(); + } } diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs new file mode 100644 index 00000000..09d8c6c5 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs @@ -0,0 +1,56 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.CaDA; +using BrickController2.PlatformServices.BluetoothLE; +using FluentAssertions; +using Moq; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.CaDA; + +public class CaDARaceCarRev2Tests +{ + private readonly CaDARaceCarRev2 _device; + private readonly Mock _cadaPlatformService = new(MockBehavior.Strict); + + public CaDARaceCarRev2Tests() + { + _device = new CaDARaceCarRev2("RC", + "1-2-3", + [ + // manufacturerId + 0xAA, 0x11, + // CADA RaceCar? + 0x11, + // 2 bytes AppID + 0x88, 0x51, + // other data + 0x20, 0xB9, 0x86, + 0x00, 0x00, 0x00, 0xA1, 0xCC, 0xB8, 0x92, 0xA0 + ], + Mock.Of(MockBehavior.Strict), + Mock.Of(MockBehavior.Strict), + _cadaPlatformService.Object); + } + + [Fact] + public void TryGetTelegram_ZeroValuesAndIosPlatform_ReturnsProperDatagram() + { + // arrange + _cadaPlatformService.TryGetRfPayload_ForIosPlatform(); + + var result = _device.TryGetTelegram(false, out var telegram); + + result.Should().BeTrue(); + telegram.Should().StartWith(new byte[] + { + 0xC0, 0x3D, 0xCA, 0x66, 0x6D, 0x32, 0xB2, 0x9E, + 0xD2, 0x57, 0xA1, 0xB5, 0xF6, 0x66 // other bytes are random and can differ, but the first 13 bytes should be consistent + + // CADA SMart + //0xC0, 0x00, 0xBB, 0x11, 0x11, 0x20, 0xB9, 0x88, + //0x51, 0x76, 0x76, 0x00, 0xF6, 0xA1, 0xCC, 0xB8, + //0x92, 0xB0, 0x27, 0x0F, 0x86, 0x28, 0x17, 0xD3, + //0xAC, 0xCB + }); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/MockSetups.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/MockSetups.cs new file mode 100644 index 00000000..bcdce27a --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/MockSetups.cs @@ -0,0 +1,37 @@ +using BrickController2.DeviceManagement.CaDA; +using BrickController2.Protocols; +using Moq; + +namespace BrickController2.Tests.DeviceManagement.CaDA; + +public static class MockSetups +{ + public static Mock TryGetRfPayload_ForIosPlatform(this Mock mock) + { + const int HeaderOffset = 13; + const int PayloadLength = 26; + + mock.Setup(m => m.TryGetRfPayload(It.IsAny(), out It.Ref.IsAny)) + .Callback((byte[] rawData, out byte[] rfPayload) => + { + rfPayload = new byte[PayloadLength]; + int payloadLength = CryptTools.GetRfPayload(CaDAProtocol.SeedArray, + CaDAProtocol.HeaderArray, + rawData, + HeaderOffset, + CaDAProtocol.CTXValue1, + CaDAProtocol.CTXValue2, + rfPayload); + + // fill rest of array + byte bVar = 0x18; // initial value + for (int index = payloadLength; index < PayloadLength; index++) + { + rfPayload[index] = bVar++; + } + }) + .Returns(true); + + return mock; + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs index 904b4bec..28eb6ff9 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs @@ -115,14 +115,14 @@ private bool IsCadaRaceCar(ReadOnlySpan manufacturerData) manufacturerData[9] == _appIdChecksumMaskArray[2]; } - private bool IsCadaRaceCarRev2(ReadOnlySpan manufacturerData) => manufacturerData.Length == 16 && + private static bool IsCadaRaceCarRev2(ReadOnlySpan manufacturerData) => manufacturerData.Length == 16 && manufacturerData[2] == 0x11 && - // default advertisement - ( - (manufacturerData[3] == 0x00 && manufacturerData[4] == 0x00) || - // response has to have the same appId - (manufacturerData[3] == _appIdChecksumMaskArray[0] && manufacturerData[4] == _appIdChecksumMaskArray[1]) - ); + // response has 2 zeros as AppId - not connected yet + manufacturerData[3] == 0x00 && + manufacturerData[4] == 0x00 && + manufacturerData[5] == 0x20 && + // flag not connected yet + manufacturerData[7] == 0x85; /// /// gets or creates an App-persistent AppIdentifier diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs index 947abdf3..118a80f5 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs @@ -16,22 +16,22 @@ internal class CaDARaceCarRev2 : BluetoothAdvertisingDevice private readonly byte[] _controlDataArray = // 16 [ - 0x75, // [0] const 0x75 (117) - 0x13, // [1] 0x13 (19) STATUS_CONTROL - 0x00, // [2] DeviceAddress - 0x00, // [3] DeviceAddress - 0x00, // [4] DeviceAddress - 0x00, // [5] AppID - 0x00, // [6] AppID - 0x00, // [7] AppID - 0x00, // [8] ChannelData random - 0x00, // [9] ChannelData random - 0x80, // [10] ChannelData verticalValue (min= 0x80 (128)) - 0x80, // [11] ChannelData horizontalValue (min= 0x80 (128)) - 0x00, // [12] ChannelData lightValue - 0x00, // [13] ChannelData - 0x00, // [14] ChannelData - 0x00, // [15] ChannelData + 0x75, // [0] const 0x75 (117) + 0x13, // [1] 0x13 (19) STATUS_CONTROL + 0x00, // [2] DeviceAddress + 0x00, // [3] DeviceAddress + 0x00, // [4] DeviceAddress + 0x00, // [5] AppID + 0x00, // [6] AppID + 0x00, // [7] AppID + 0x00, // [8] ChannelData random + 0x00, // [9] ChannelData random + 0x80, // [10] ChannelData verticalValue (min= 0x80 (128)) + 0x80, // [11] ChannelData horizontalValue (min= 0x80 (128)) + 0x00, // [12] ChannelData lightValue + 0x00, // [13] ChannelData + 0x00, // [14] ChannelData + 0x00, // [15] ChannelData ]; private readonly ICaDAPlatformService _cadaPlatformService; @@ -45,21 +45,20 @@ public CaDARaceCarRev2(string name, string address, byte[] deviceData, IDeviceRe // revisions if (deviceData?.Length == 16) { - // DeviceData-Array is the manufacturer specific data inside the response telegram sent when - // * scanning for the device. - // * loading the device from database - - // It's containing: - // * DeviceAddress of the real CaDA device - // * AppID sent from this App on scanning - // These values are patched into the DataArray which is advertised to control the device. - _controlDataArray[2] = deviceData[4]; // DeviceAddress - _controlDataArray[3] = deviceData[5]; // DeviceAddress - _controlDataArray[4] = deviceData[6]; // DeviceAddress - - _controlDataArray[5] = deviceData[7]; // AppID - _controlDataArray[6] = deviceData[8]; // AppID - _controlDataArray[7] = deviceData[9]; // AppID + //// DeviceData-Array is the manufacturer specific data inside the response telegram sent when + //// * scanning for the device. + //// * loading the device from database + + //// It's containing: + //// * DeviceAddress of the real CaDA device + //// * AppID sent from this App on scanning + //// These values are patched into the DataArray which is advertised to control the device. + //_controlDataArray[2] = deviceData[4]; // DeviceAddress + //_controlDataArray[3] = deviceData[5]; // DeviceAddress + //_controlDataArray[4] = deviceData[6]; // DeviceAddress + + _controlDataArray[5] = deviceData[3]; // AppID + _controlDataArray[6] = deviceData[4]; // AppID } else { From bfc84241ffb65e6d548ba340de90c340234f93d2 Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Mon, 9 Feb 2026 23:28:04 +0100 Subject: [PATCH 4/7] testf --- .../CaDA/CaDARaceCarRev2Tests.cs | 16 +++++++++------- .../DeviceManagement/CaDA/CaDARaceCarRev2.cs | 7 +++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs index 09d8c6c5..b4016154 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs @@ -23,8 +23,11 @@ public CaDARaceCarRev2Tests() 0x11, // 2 bytes AppID 0x88, 0x51, + // some identifying bytes + 0x20, 0xB9, + // some flag + 0x86, // other data - 0x20, 0xB9, 0x86, 0x00, 0x00, 0x00, 0xA1, 0xCC, 0xB8, 0x92, 0xA0 ], Mock.Of(MockBehavior.Strict), @@ -43,12 +46,11 @@ public void TryGetTelegram_ZeroValuesAndIosPlatform_ReturnsProperDatagram() result.Should().BeTrue(); telegram.Should().StartWith(new byte[] { - 0xC0, 0x3D, 0xCA, 0x66, 0x6D, 0x32, 0xB2, 0x9E, - 0xD2, 0x57, 0xA1, 0xB5, 0xF6, 0x66 // other bytes are random and can differ, but the first 13 bytes should be consistent - - // CADA SMart - //0xC0, 0x00, 0xBB, 0x11, 0x11, 0x20, 0xB9, 0x88, - //0x51, 0x76, 0x76, 0x00, 0xF6, 0xA1, 0xCC, 0xB8, + // CADA SMART CAR Rev2 + 0xC0, 0x00, 0xBB, 0x11, 0x11, + 0x20, 0xB9, + 0x88, 0x51, + //0x76, 0x76, 0x00, 0xF6, 0xA1, 0xCC, 0xB8, //0x92, 0xB0, 0x27, 0x0F, 0x86, 0x28, 0x17, 0xD3, //0xAC, 0xCB }); diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs index 118a80f5..bc90a0b2 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs @@ -50,12 +50,11 @@ public CaDARaceCarRev2(string name, string address, byte[] deviceData, IDeviceRe //// * loading the device from database //// It's containing: - //// * DeviceAddress of the real CaDA device + //// * DeviceId of the real CaDA device //// * AppID sent from this App on scanning //// These values are patched into the DataArray which is advertised to control the device. - //_controlDataArray[2] = deviceData[4]; // DeviceAddress - //_controlDataArray[3] = deviceData[5]; // DeviceAddress - //_controlDataArray[4] = deviceData[6]; // DeviceAddress + _controlDataArray[2] = deviceData[5]; // DeviceId + _controlDataArray[3] = deviceData[6]; // DeviceId _controlDataArray[5] = deviceData[3]; // AppID _controlDataArray[6] = deviceData[4]; // AppID From e721ae07af8707825dee37020361c5396d4cc9ba Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Tue, 10 Feb 2026 23:23:03 +0100 Subject: [PATCH 5/7] apply current rev2 payload knowledge --- .../CaDA/CaDARaceCarRev2Tests.cs | 43 +++++-- .../DeviceManagement/CaDA/CaDARaceCarRev2.cs | 114 ++++++++---------- 2 files changed, 84 insertions(+), 73 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs index b4016154..f7bbae09 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2Tests.cs @@ -22,13 +22,13 @@ public CaDARaceCarRev2Tests() // CADA RaceCar? 0x11, // 2 bytes AppID - 0x88, 0x51, - // some identifying bytes + 0x00, 0x00, + // Device Seed 0x20, 0xB9, - // some flag - 0x86, - // other data - 0x00, 0x00, 0x00, 0xA1, 0xCC, 0xB8, 0x92, 0xA0 + // some flag(s) + 0x86, 0x00, 0x00, 0x00, + // hardware id + 0xA1, 0xCC, 0xB8, 0x92, 0xA0 ], Mock.Of(MockBehavior.Strict), Mock.Of(MockBehavior.Strict), @@ -36,7 +36,27 @@ public CaDARaceCarRev2Tests() } [Fact] - public void TryGetTelegram_ZeroValuesAndIosPlatform_ReturnsProperDatagram() + public void TryGetTelegram_ConnectTelegram_ReturnsProperDatagram() + { + // arrange + _cadaPlatformService.TryGetRfPayload_ForIosPlatform(); + + var result = _device.TryGetTelegram(true, out var telegram); + + result.Should().BeTrue(); + telegram.Should().StartWith(new byte[] + { + 0xC0, 0x00, 0xAA, 0x11, 0x11, + 0x20, 0xB9, + 0xAD, 0x42, + 0x6B, 0x6B, 0x00, /*0xEB,*/ 0xD6, //TODO checksum + 0xA1, 0xCC, 0xB8, 0x92, 0xA0, + 0xEF, 0xF2, 0xC5, 0x67, 0x8F, 0x9F, 0xF1, 0xF8 + }); + } + + [Fact] + public void TryGetTelegram_WithZeroValuesTelegram_ReturnsProperDatagram() { // arrange _cadaPlatformService.TryGetRfPayload_ForIosPlatform(); @@ -46,13 +66,12 @@ public void TryGetTelegram_ZeroValuesAndIosPlatform_ReturnsProperDatagram() result.Should().BeTrue(); telegram.Should().StartWith(new byte[] { - // CADA SMART CAR Rev2 0xC0, 0x00, 0xBB, 0x11, 0x11, 0x20, 0xB9, - 0x88, 0x51, - //0x76, 0x76, 0x00, 0xF6, 0xA1, 0xCC, 0xB8, - //0x92, 0xB0, 0x27, 0x0F, 0x86, 0x28, 0x17, 0xD3, - //0xAC, 0xCB + 0xAD, 0x42, + 0x80, 0x80, 0x80, 0x80, //TODO checksum + 0xA1, 0xCC, 0xB8, 0x92, 0xB0, + 0xEF, 0xF2, 0xC5, 0x67, 0x8F, 0x9F, 0xF1, 0xF8 }); } } diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs index bc90a0b2..5e284cc5 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs @@ -10,29 +10,10 @@ namespace BrickController2.DeviceManagement.CaDA; /// internal class CaDARaceCarRev2 : BluetoothAdvertisingDevice { - /// - /// byte-array including DeviceAddress, AppID and channelData - /// - private readonly byte[] _controlDataArray = - // 16 - [ - 0x75, // [0] const 0x75 (117) - 0x13, // [1] 0x13 (19) STATUS_CONTROL - 0x00, // [2] DeviceAddress - 0x00, // [3] DeviceAddress - 0x00, // [4] DeviceAddress - 0x00, // [5] AppID - 0x00, // [6] AppID - 0x00, // [7] AppID - 0x00, // [8] ChannelData random - 0x00, // [9] ChannelData random - 0x80, // [10] ChannelData verticalValue (min= 0x80 (128)) - 0x80, // [11] ChannelData horizontalValue (min= 0x80 (128)) - 0x00, // [12] ChannelData lightValue - 0x00, // [13] ChannelData - 0x00, // [14] ChannelData - 0x00, // [15] ChannelData - ]; + // 7 bytes: AA 11 11 Seed1 Seed2 AppId1, AppId2 + private readonly byte[] _devicePrefix; + private readonly byte[] _deviceHardwareId; + private readonly ICaDAPlatformService _cadaPlatformService; private readonly OutputValuesGroup _outputValues = new(3); @@ -45,19 +26,15 @@ public CaDARaceCarRev2(string name, string address, byte[] deviceData, IDeviceRe // revisions if (deviceData?.Length == 16) { - //// DeviceData-Array is the manufacturer specific data inside the response telegram sent when - //// * scanning for the device. - //// * loading the device from database - - //// It's containing: - //// * DeviceId of the real CaDA device - //// * AppID sent from this App on scanning - //// These values are patched into the DataArray which is advertised to control the device. - _controlDataArray[2] = deviceData[5]; // DeviceId - _controlDataArray[3] = deviceData[6]; // DeviceId - - _controlDataArray[5] = deviceData[3]; // AppID - _controlDataArray[6] = deviceData[4]; // AppID + _devicePrefix = deviceData.AsSpan(0, 7).ToArray(); + // seed + _devicePrefix[3] = deviceData[5]; + _devicePrefix[4] = deviceData[6]; + //TODO app id + _devicePrefix[5] = 0xAD; + _devicePrefix[6] = 0x42; + + _deviceHardwareId = deviceData.AsSpan(11, 5).ToArray(); } else { @@ -92,31 +69,46 @@ protected override void DisconnectDevice() protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] currentData) { - ushort random = (ushort)Random.Shared.Next(ushort.MinValue, ushort.MaxValue); - - _outputValues.TryGetValues(out var values); - - byte[] channelDataArray = - // 8 - [ - (byte)(random & 0xFF), - (byte)((random >> 8) & 0xFF), - (byte)Math.Max(0, Math.Min(0x80 - values[0], 0xFF)), // speed value - reversed - (byte)Math.Max(0, Math.Min(0x80 + values[1], 0xFF)), // - (byte)Math.Max(0, Math.Min(0x80 + values[2], 0xFF)), // light on/off - 0, - 0, - 0 - ]; - - CaDAProtocol.Encrypt(channelDataArray); - // copy channel data to control data as index 8..15 - channelDataArray.AsSpan().CopyTo(_controlDataArray.AsSpan(8)); - - _controlDataArray[0] = 0x75; // 0x75 (117) - _controlDataArray[1] = 0x13; // 0x13 (19); - - return _cadaPlatformService.TryGetRfPayload(_controlDataArray, out currentData); + currentData = + [ + // header + 0xC0, 0x00, + // other data + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + + // device data: prefix, seed, appId + _devicePrefix.AsSpan(0, 7).CopyTo(currentData.AsSpan(2)); + currentData[2] = getConnectTelegram ? (byte)0xAA : (byte)0xBB; + + // command data + if (getConnectTelegram) + { + ReadOnlySpan command = [0x6B, 0x6B, 0x00]; + command.CopyTo(currentData.AsSpan(9)); + } + else + { + _outputValues.TryGetValues(out var values); + + currentData[9] = (byte)Math.Max(0, Math.Min(0x80 - values[0], 0xFF)); + currentData[10] = (byte)Math.Max(0, Math.Min(0x80 + values[1], 0xFF)); + currentData[11] = (byte)Math.Max(0, Math.Min(0x80 + values[2], 0xFF)); + } + //currentData[9] ^= 0xAD; + // currentData[10] ^= 0x42; + currentData[12] = (byte)(currentData[9] + currentData[10] + currentData[11]); + + // hardware id + _deviceHardwareId.AsSpan().CopyTo(currentData.AsSpan(13)); + currentData[17] = getConnectTelegram ? (byte)0xA0 : (byte)0xB0; + + ReadOnlySpan session = [0xEF, 0xF2, 0xC5, 0x67, 0x8F, 0x9F, 0xF1, 0xF8]; + session.CopyTo(currentData.AsSpan(18)); + + return true; } /// From 46c1b22b2e76c9d3d80b3c2d7c0d8aeb53cbecae Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Wed, 11 Feb 2026 21:34:55 +0100 Subject: [PATCH 6/7] per platform payload --- .../CaDA/CaDAPlatformService.cs | 10 +++ .../CaDA/CaDAPlatformService.cs | 10 +++ .../CaDA/CaDAPlatformService.cs | 26 ++++++++ .../DeviceManagement/CaDA/CaDARaceCarRev2.cs | 66 ++++++++----------- .../CaDA/ICaDAPlatformService.cs | 6 +- .../DeviceManagement/IO/OutputValuesGroup.cs | 6 +- 6 files changed, 82 insertions(+), 42 deletions(-) diff --git a/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs b/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs index c31c1ba2..881e0877 100644 --- a/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs +++ b/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs @@ -1,5 +1,6 @@ using BrickController2.DeviceManagement.CaDA; using BrickController2.Protocols; +using System; namespace BrickController2.Droid.PlatformServices.DeviceManagement.CaDA; @@ -8,6 +9,8 @@ public class CaDAPlatformService : ICaDAPlatformService private const int HeaderOffset = 15; private const int PayloadLength = 24; + private const int PayloadRev2Length = 16; + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) { rfPayload = new byte[PayloadLength]; @@ -16,4 +19,11 @@ public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) return true; } + + public bool TryGetRfPayload(ushort manufacturerId, ReadOnlySpan rawData, out byte[] rfPayload) + { + // copy past data + rfPayload = rawData.ToArray(); + return rawData.Length == PayloadRev2Length; + } } diff --git a/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs b/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs index 04bc9467..245a6be5 100644 --- a/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs +++ b/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs @@ -1,5 +1,6 @@ using BrickController2.DeviceManagement.CaDA; using BrickController2.Protocols; +using System; namespace BrickController2.Windows.PlatformServices.DeviceManagement.CaDA; @@ -9,6 +10,8 @@ public class CaDAPlatformService : ICaDAPlatformService private const int PayloadOffset = 3; private const int PayloadLength = 24 + PayloadOffset; + private const int PayloadRev2Length = 16; + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) { // JK: @@ -28,4 +31,11 @@ public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) return true; } + + public bool TryGetRfPayload(ushort manufacturerId, ReadOnlySpan rawData, out byte[] rfPayload) + { + // copy past data + rfPayload = rawData.ToArray(); + return rawData.Length == PayloadRev2Length; + } } diff --git a/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs b/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs index 3bca9878..73b1c792 100644 --- a/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs +++ b/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/CaDA/CaDAPlatformService.cs @@ -1,5 +1,6 @@ using BrickController2.DeviceManagement.CaDA; using BrickController2.Protocols; +using System; namespace BrickController2.iOS.PlatformServices.DeviceManagement.CaDA; @@ -8,6 +9,19 @@ public class CaDAPlatformService : ICaDAPlatformService private const int HeaderOffset = 13; private const int PayloadLength = 26; + private const int SessionLength = 8; + private const int PayloadRev2Length = 16; + + // Session - 8 bytes per application run + private static readonly Lazy _sessionPostfix = new(() => + { + var session = new byte[SessionLength]; + Random.Shared.NextBytes(session); + return session; + }); + + public ReadOnlySpan Session => _sessionPostfix.Value; + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) { rfPayload = new byte[PayloadLength]; @@ -22,4 +36,16 @@ public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) return true; } + + public bool TryGetRfPayload(short manufacturerId, ReadOnlySpan rawData, out byte[] rfPayload) + { + rfPayload = new byte[2 + rawData.Length + PayloadLength]; + + BitConverter.TryWriteBytes(rfPayload, manufacturerId); + + rawData.CopyTo(rfPayload.AsSpan(2)); + Session.CopyTo(rfPayload.AsSpan(2 + rawData.Length)); + + return rawData.Length == PayloadRev2Length; + } } diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs index 5e284cc5..df52db1f 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCarRev2.cs @@ -10,10 +10,7 @@ namespace BrickController2.DeviceManagement.CaDA; /// internal class CaDARaceCarRev2 : BluetoothAdvertisingDevice { - // 7 bytes: AA 11 11 Seed1 Seed2 AppId1, AppId2 - private readonly byte[] _devicePrefix; - private readonly byte[] _deviceHardwareId; - + private readonly byte[] _payloadTemplate; private readonly ICaDAPlatformService _cadaPlatformService; private readonly OutputValuesGroup _outputValues = new(3); @@ -23,18 +20,15 @@ public CaDARaceCarRev2(string name, string address, byte[] deviceData, IDeviceRe { _cadaPlatformService = cadaPlatformService; - // revisions if (deviceData?.Length == 16) { - _devicePrefix = deviceData.AsSpan(0, 7).ToArray(); + _payloadTemplate = deviceData; // seed - _devicePrefix[3] = deviceData[5]; - _devicePrefix[4] = deviceData[6]; + _payloadTemplate[3] = deviceData[5]; + _payloadTemplate[4] = deviceData[6]; //TODO app id - _devicePrefix[5] = 0xAD; - _devicePrefix[6] = 0x42; - - _deviceHardwareId = deviceData.AsSpan(11, 5).ToArray(); + _payloadTemplate[5] = 0xAD; + _payloadTemplate[6] = 0x42; } else { @@ -56,7 +50,12 @@ public override void SetOutput(int channelNo, float value) value = CutOutputValue(value); var rawValue = (short)(value * 0x7F); // scale and cast - _outputValues.SetOutput(channelNo, rawValue); + + if (_outputValues.SetOutput(channelNo, rawValue)) + { + _bluetoothAdvertisingDeviceHandler.SetChannelState(channelNo, rawValue == 0x80); + _bluetoothAdvertisingDeviceHandler.NotifyDataChanged(); + } } protected override void InitDevice() @@ -69,46 +68,35 @@ protected override void DisconnectDevice() protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] currentData) { - currentData = - [ - // header - 0xC0, 0x00, - // other data - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - ]; - - // device data: prefix, seed, appId - _devicePrefix.AsSpan(0, 7).CopyTo(currentData.AsSpan(2)); - currentData[2] = getConnectTelegram ? (byte)0xAA : (byte)0xBB; + // compose payload + var payload = _payloadTemplate.AsSpan(); + + // device data: AA vs BB flag + payload[2] = getConnectTelegram ? (byte)0xAA : (byte)0xBB; // command data if (getConnectTelegram) { ReadOnlySpan command = [0x6B, 0x6B, 0x00]; - command.CopyTo(currentData.AsSpan(9)); + command.CopyTo(payload.Slice(7)); } else { _outputValues.TryGetValues(out var values); - currentData[9] = (byte)Math.Max(0, Math.Min(0x80 - values[0], 0xFF)); - currentData[10] = (byte)Math.Max(0, Math.Min(0x80 + values[1], 0xFF)); - currentData[11] = (byte)Math.Max(0, Math.Min(0x80 + values[2], 0xFF)); + payload[7] = (byte)Math.Max(0, Math.Min(0x80 - values[0], 0xFF)); + payload[8] = (byte)Math.Max(0, Math.Min(0x80 + values[1], 0xFF)); + payload[9] = (byte)Math.Max(0, Math.Min(0x80 + values[2], 0xFF)); } + //TODO XOR using AppId //currentData[9] ^= 0xAD; - // currentData[10] ^= 0x42; - currentData[12] = (byte)(currentData[9] + currentData[10] + currentData[11]); - - // hardware id - _deviceHardwareId.AsSpan().CopyTo(currentData.AsSpan(13)); - currentData[17] = getConnectTelegram ? (byte)0xA0 : (byte)0xB0; + // currentData[10] ^= 0x42; + payload[10] = (byte)(payload[7] + payload[8] + payload[9]); - ReadOnlySpan session = [0xEF, 0xF2, 0xC5, 0x67, 0x8F, 0x9F, 0xF1, 0xF8]; - session.CopyTo(currentData.AsSpan(18)); + // update flags + payload[15] = getConnectTelegram ? (byte)0xA0 : (byte)0xB0; - return true; + return _cadaPlatformService.TryGetRfPayload(ManufacturerId, payload, out currentData); } /// diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/ICaDAPlatformService.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/ICaDAPlatformService.cs index 466f8add..45d4d186 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/ICaDAPlatformService.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/ICaDAPlatformService.cs @@ -1,4 +1,6 @@ -namespace BrickController2.DeviceManagement.CaDA; +using System; + +namespace BrickController2.DeviceManagement.CaDA; /// /// Interface definition for CaDA specific PlatformService @@ -6,4 +8,6 @@ public interface ICaDAPlatformService { bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload); + + bool TryGetRfPayload(ushort manufacturerId, ReadOnlySpan rawData, out byte[] rfPayload); } diff --git a/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs b/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs index 87422415..71fd650c 100644 --- a/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs +++ b/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs @@ -26,7 +26,7 @@ public OutputValuesGroup(int channelCount) _sendAttemptsLeft = 0; } - public void SetOutput(int channel, TValue value) + public bool SetOutput(int channel, TValue value) { lock (_outputLock) { @@ -34,8 +34,10 @@ public void SetOutput(int channel, TValue value) { _outputValues[channel] = value; _sendAttemptsLeft = MAX_SEND_ATTEMPTS; + return true; } - } + } + return false; } public void Initialize() From b568300c2e75d3291ffc27b1535df63577adebc2 Mon Sep 17 00:00:00 2001 From: Vit Nemecky Date: Wed, 11 Feb 2026 22:40:47 +0100 Subject: [PATCH 7/7] . --- .../DeviceManagement/CaDA/CaDADeviceManagerTests.cs | 4 ++-- .../DeviceManagement/CaDA/CaDADeviceManager.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs index 09a470c4..0f3fa083 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs @@ -120,7 +120,7 @@ public void TryGetDevice_CadaCarRev2WithZeroAppId_ReturnsCaDaRaceCarRev2Device() 0x11, // 2 bytes AppID 0x00, 0x00, - // other data + // Seed 0x20, 0xB9, // flag 0x85, @@ -155,7 +155,7 @@ public void TryGetDevice_CadaCarRev2WithSomeAppId_ReturnsFalse() 0x11, // 2 bytes AppID 0x12, 0x34, - // other data + // Seed 0x20, 0xB9, // flag 0x86, diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs index 28eb6ff9..253d15c3 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs @@ -120,7 +120,6 @@ private static bool IsCadaRaceCarRev2(ReadOnlySpan manufacturerData) => ma // response has 2 zeros as AppId - not connected yet manufacturerData[3] == 0x00 && manufacturerData[4] == 0x00 && - manufacturerData[5] == 0x20 && // flag not connected yet manufacturerData[7] == 0x85;