Repository: vicocz/brickcontroller2
Pull request #133: iOS advertiser for MouldKing MK4 and MK6
Author: login: J0EK3R
Labels: name: enhancement
description: New feature or request
Created: 2025-05-20T07:04:26Z | Merged: 2025-05-21T21:09:46Z

I've added the iOS advertiser for MK4 and MK6.

Please have a look... :)


Diff:
diff --git a/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs b/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs
index c2ea2bae..6bf2e5e9 100644
--- a/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs
+++ b/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs
@@ -1,4 +1,6 @@
 ﻿using Autofac;
+using BrickController2.DeviceManagement;
+using BrickController2.Droid.PlatformServices.DeviceManagement;
 using BrickController2.Droid.PlatformServices.BluetoothLE;
 using BrickController2.Droid.PlatformServices.GameController;
 using BrickController2.Droid.PlatformServices.Infrared;
@@ -29,6 +31,7 @@ protected override void Load(ContainerBuilder builder)
             builder.RegisterType<ReadWriteExternalStoragePermission>().As<IReadWriteExternalStoragePermission>().InstancePerDependency();
             builder.RegisterType<BluetoothPermission>().As<IBluetoothPermission>().InstancePerDependency();
             builder.RegisterType<CameraPermission>().As<ICameraPermission>().InstancePerDependency();
+            builder.RegisterType<MKPlatformService>().As<IMKPlatformService>().SingleInstance();
         }
     }
 }
\ No newline at end of file
diff --git a/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs b/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
new file mode 100644
index 00000000..0d78c82e
--- /dev/null
+++ b/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
@@ -0,0 +1,24 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.Droid.PlatformServices.DeviceManagement;
+
+public class MKPlatformService : IMKPlatformService
+{
+    private const int HeaderOffset = 15;
+    private const int PayloadLength = 24;
+
+    public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload)
+    {
+        rfPayload = new byte[PayloadLength];
+        int payloadLength = MKProtocol.GetRfPayload(MKProtocol.SeedArray, rawData, HeaderOffset, MKProtocol.CTXValue1, MKProtocol.CTXValue2, rfPayload);
+
+        // fill rest of array
+        for (int index = payloadLength; index < PayloadLength; index++)
+        {
+            rfPayload[index] = (byte)(index + 1);
+        }
+
+        return true;
+    }
+}
diff --git a/BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs b/BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs
index 999292ee..e7149f23 100644
--- a/BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs
+++ b/BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs
@@ -1,4 +1,5 @@
-﻿using BrickController2.Windows.PlatformServices.BluetoothLE;
+﻿using BrickController2.Windows.PlatformServices.DeviceManagement;
+using BrickController2.Windows.PlatformServices.BluetoothLE;
 using BrickController2.Windows.PlatformServices.Infrared;
 using BrickController2.Windows.PlatformServices.Versioning;
 using BrickController2.Windows.PlatformServices.Localization;
@@ -6,6 +7,7 @@
 using BrickController2.Windows.PlatformServices.SharedFileStorage;
 using BrickController2.Windows.PlatformServices.Permission;
 using Autofac;
+using BrickController2.DeviceManagement;
 using BrickController2.PlatformServices.Infrared;
 using BrickController2.PlatformServices.GameController;
 using BrickController2.PlatformServices.Versioning;
@@ -28,5 +30,6 @@ protected override void Load(ContainerBuilder builder)
         builder.RegisterType<SharedFileStorageService>().As<ISharedFileStorageService>().SingleInstance();
         builder.RegisterType<ReadWriteExternalStoragePermission>().As<IReadWriteExternalStoragePermission>().InstancePerDependency();
         builder.RegisterType<BluetoothPermission>().As<IBluetoothPermission>().InstancePerDependency();
+        builder.RegisterType<MKPlatformService>().As<IMKPlatformService>().SingleInstance();
     }
 }
\ No newline at end of file
diff --git a/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs b/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
new file mode 100644
index 00000000..a097b888
--- /dev/null
+++ b/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
@@ -0,0 +1,25 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.Windows.PlatformServices.DeviceManagement;
+
+public class MKPlatformService : IMKPlatformService
+{
+    private const int HeaderOffset = 15;
+    private const int PayloadLength = 26;
+
+    public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload)
+    {
+        rfPayload = new byte[PayloadLength];
+        int payloadLength = MKProtocol.GetRfPayload(MKProtocol.SeedArray, rawData, HeaderOffset, MKProtocol.CTXValue1, MKProtocol.CTXValue2, rfPayload);
+
+        // fill rest of array
+        byte bVar = 0x12; // initial value
+        for (int index = payloadLength; index < PayloadLength; index++)
+        {
+            rfPayload[index] = bVar++;
+        }
+
+        return true;
+    }
+}
diff --git a/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs b/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs
new file mode 100644
index 00000000..72375295
--- /dev/null
+++ b/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs
@@ -0,0 +1,98 @@
+﻿using System.Threading.Tasks;
+using CoreBluetooth;
+using BrickController2.PlatformServices.BluetoothLE;
+
+namespace BrickController2.iOS.PlatformServices.BluetoothLE;
+
+internal class BluetoothLEAdvertiserDevice : CBPeripheralManagerDelegate, IBluetoothLEAdvertiserDevice
+{
+    private CBPeripheralManager? _peripheralManager;
+    private StartAdvertisingOptions? _advData;
+
+    public BluetoothLEAdvertiserDevice()
+    {
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        StopAdvertiseInternal();
+        _peripheralManager?.Dispose();
+
+        base.Dispose(disposing);
+    }
+
+    public Task StartAdvertiseAsync(AdvertisingInterval advertisingIterval, TxPowerLevel txPowerLevel, ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    public Task StopAdvertiseAsync()
+    {
+        StopAdvertiseInternal();
+
+        return Task.CompletedTask;
+    }
+
+    public Task UpdateAdvertisedDataAsync(ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    private void SetAdvertisingData(ushort manufacturerId, byte[] rawData)
+    {
+        // JK: no check - rawDataLength has to be even!
+
+        int entryCount = rawData.Length / 2;
+        CBUUID[] servicesUUID = new CBUUID[entryCount];
+
+        for (int index = 0; index < entryCount; index++)
+        {
+            int manufacturerSpecificDataIndex = index * 2;
+
+            servicesUUID[index] = CBUUID.FromBytes([
+                rawData[manufacturerSpecificDataIndex + 1],
+                rawData[manufacturerSpecificDataIndex + 0]]);
+        }
+
+        _advData = new StartAdvertisingOptions { ServicesUUID = servicesUUID };
+
+        if (_peripheralManager?.Advertising == true)
+        {
+            _peripheralManager.StopAdvertising();
+        }
+
+        StartAdvertisingInternal();
+    }
+
+    private void StartAdvertisingInternal()
+    {
+        // Initialize peripheral manager if not already done.
+        if (_peripheralManager == null)
+        {
+            _peripheralManager = new CBPeripheralManager();
+            _peripheralManager.StateUpdated += (sender, e) =>
+            {
+                if (_peripheralManager.State == CBManagerState.PoweredOn && _advData != null)
+                {
+                    _peripheralManager.StartAdvertising(_advData);
+                }
+            };
+        }
+
+        if (_peripheralManager.State == CBManagerState.PoweredOn && _advData != null)
+        {
+            _peripheralManager.StartAdvertising(_advData);
+        }
+    }
+
+    private void StopAdvertiseInternal()
+    {
+        _peripheralManager?.StopAdvertising();
+        // Reset data so that it does not start advertising automatically if the interface goes ON.
+        _advData = null;
+    }
+}
diff --git a/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs b/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs
index b5fb9015..f1565fbb 100644
--- a/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs
+++ b/BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEService.cs
@@ -179,7 +179,7 @@ private Dictionary<byte, byte[]> ProcessAdvertisementData(NSDictionary advertise
 
         public IBluetoothLEAdvertiserDevice? CreateBluetoothLEAdvertiserDevice()
         {
-            return null; // Not supported yet - has to be implemented
+            return new BluetoothLEAdvertiserDevice();
         }
     }
 }
\ No newline at end of file
diff --git a/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs b/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs
index 30ba71b2..7c671d7a 100644
--- a/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs
+++ b/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs
@@ -1,4 +1,6 @@
 ﻿using Autofac;
+using BrickController2.DeviceManagement;
+using BrickController2.iOS.PlatformServices.DeviceManagement;
 using BrickController2.iOS.PlatformServices.BluetoothLE;
 using BrickController2.iOS.PlatformServices.GameController;
 using BrickController2.iOS.PlatformServices.Infrared;
@@ -29,6 +31,7 @@ protected override void Load(ContainerBuilder builder)
             builder.RegisterType<ReadWriteExternalStoragePermission>().As<IReadWriteExternalStoragePermission>().InstancePerDependency();
             builder.RegisterType<BluetoothPermission>().As<IBluetoothPermission>().InstancePerDependency();
             builder.RegisterType<CameraPermission>().As<ICameraPermission>().InstancePerDependency();
+            builder.RegisterType<MKPlatformService>().As<IMKPlatformService>().SingleInstance();
         }
     }
 }
\ No newline at end of file
diff --git a/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs b/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
new file mode 100644
index 00000000..f7ec1e30
--- /dev/null
+++ b/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
@@ -0,0 +1,25 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.iOS.PlatformServices.DeviceManagement;
+
+public class MKPlatformService : IMKPlatformService
+{
+    private const int HeaderOffset = 13;
+    private const int PayloadLength = 26;
+
+    public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload)
+    {
+        rfPayload = new byte[PayloadLength];
+        int payloadLength = MKProtocol.GetRfPayload(MKProtocol.SeedArray, rawData, HeaderOffset, MKProtocol.CTXValue1, MKProtocol.CTXValue2, rfPayload);
+
+        // fill rest of array
+        byte bVar = 0x12; // initial value
+        for (int index = payloadLength; index < PayloadLength; index++)
+        {
+            rfPayload[index] = bVar++;
+        }
+
+        return true;
+    }
+}
diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/IMKPlatformService.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/IMKPlatformService.cs
new file mode 100644
index 00000000..469782e8
--- /dev/null
+++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/IMKPlatformService.cs
@@ -0,0 +1,9 @@
+﻿namespace BrickController2.DeviceManagement;
+
+/// <summary>
+/// Interface definition for MouldKing specific PlatformService
+/// </summary>
+public interface IMKPlatformService
+{
+    bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload);
+}
diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs
index 6754fd9b..464cdeb7 100644
--- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs
+++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs
@@ -53,8 +53,8 @@ internal class MK4 : MKBaseNible
         /// </summary>
         protected override int BaseTelegram_ChannelStartOffset => 3;
 
-        public MK4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService)
-          : base(name, address, deviceData, deviceRepository, bleService, MK4.GetChannelStartOffset(address), MK4.Telegram_Connect, MK4.Telegram_Base)
+        public MK4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService)
+          : base(name, address, deviceData, deviceRepository, bleService, MK4.GetChannelStartOffset(address), MK4.Telegram_Connect, MK4.Telegram_Base, mkPlatformService)
         {
         }
 
diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs
index 686b5d96..6f13d87c 100644
--- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs
+++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs
@@ -53,8 +53,8 @@ internal class MK6 : MKBaseByte
         /// </summary>
         protected override int BaseTelegram_ChannelStartOffset => 3;
 
-        public MK6(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService)
-          : base(name, address, deviceData, deviceRepository, bleService, 3, MK6.Telegram_Connect, MK6.GetTelegramBase(address))
+        public MK6(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService)
+          : base(name, address, deviceData, deviceRepository, bleService, 3, MK6.Telegram_Connect, MK6.GetTelegramBase(address), mkPlatformService)
         {
         }
 
diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs
index 357ba31b..1d0ff0fe 100644
--- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs
+++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs
@@ -1,6 +1,5 @@
 ﻿using System;
 using BrickController2.PlatformServices.BluetoothLE;
-using BrickController2.Protocols;
 
 namespace BrickController2.DeviceManagement
 {
@@ -9,6 +8,11 @@ namespace BrickController2.DeviceManagement
     /// </summary>
     internal abstract class MKBaseByte : BluetoothAdvertisingDevice
     {
+        /// <summary>
+        /// platform specific MouldKing stuff
+        /// </summary>
+        protected readonly IMKPlatformService _mkPlatformService;
+
         /// <summary>
         /// Telegram to connect to the device
         /// This telegram is sent on init and on reconnect conditions matching
@@ -26,12 +30,13 @@ internal abstract class MKBaseByte : BluetoothAdvertisingDevice
         protected readonly int _channelStartOffset;
 
 
-        protected MKBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, int channelStartOffset, byte[] telegram_Connect, byte[] telegram_Base)
+        protected MKBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, int channelStartOffset, byte[] telegram_Connect, byte[] telegram_Base, IMKPlatformService mkPlatformService)
             : base(name, address, deviceData, deviceRepository, bleService)
         {
             _channelStartOffset = channelStartOffset;
             _telegram_Connect = telegram_Connect;
             _telegram_Base = telegram_Base;
+            _mkPlatformService = mkPlatformService;
         }
 
         /// <summary>
@@ -108,19 +113,14 @@ protected override void InitDevice()
 
         protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload)
         {
-            byte[] rawData;
-
             if (getConnectTelegram)
             {
-                rawData = _telegram_Connect;
+                return _mkPlatformService.TryGetRfPayload(_telegram_Connect, out payload);
             }
             else
             {
-                rawData = _telegram_Base;
+                return _mkPlatformService.TryGetRfPayload(_telegram_Base, out payload);
             }
-
-            MKProtocol.GetRfPayload(MKProtocol.AddressArray, rawData, MKProtocol.CTXValue, out payload);
-            return true;
         }
 
         private bool CheckAllChannelsZero()
diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNible.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNible.cs
index 4bf571c3..c399f7de 100644
--- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNible.cs
+++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNible.cs
@@ -1,6 +1,5 @@
 ﻿using System;
 using BrickController2.PlatformServices.BluetoothLE;
-using BrickController2.Protocols;
 
 namespace BrickController2.DeviceManagement
 {
@@ -9,6 +8,11 @@ namespace BrickController2.DeviceManagement
     /// </summary>
     internal abstract class MKBaseNible : BluetoothAdvertisingDevice
     {
+        /// <summary>
+        /// platform specific MouldKing stuff
+        /// </summary>
+        protected readonly IMKPlatformService _mkPlatformService;
+
         /// <summary>
         /// Telegram to connect to the device
         /// This telegram is sent on init and on reconnect conditions matching
@@ -25,12 +29,13 @@ internal abstract class MKBaseNible : BluetoothAdvertisingDevice
         /// </summary>
         protected readonly int _channelStartOffset;
 
-        protected MKBaseNible(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, int channelStartOffset, byte[] telegram_Connect, byte[] telegram_Base)
+        protected MKBaseNible(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, int channelStartOffset, byte[] telegram_Connect, byte[] telegram_Base, IMKPlatformService mkPlatformService)
             : base(name, address, deviceData, deviceRepository, bleService)
         {
             _channelStartOffset = channelStartOffset;
             _telegram_Connect = telegram_Connect;
             _telegram_Base = telegram_Base;
+            _mkPlatformService = mkPlatformService;
         }
 
         /// <summary>
@@ -120,19 +125,14 @@ protected override void InitDevice()
 
         protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload)
         {
-            byte[] rawData;
-
             if (getConnectTelegram)
             {
-                rawData = _telegram_Connect;
+                return _mkPlatformService.TryGetRfPayload(_telegram_Connect, out payload);
             }
             else
             {
-                rawData = _telegram_Base;
+                return _mkPlatformService.TryGetRfPayload(_telegram_Base, out payload);
             }
-
-            MKProtocol.GetRfPayload(MKProtocol.AddressArray, rawData, MKProtocol.CTXValue, out payload);
-            return true;
         }
 
         private bool CheckAllChannelsZero()
diff --git a/BrickController2/BrickController2/Protocols/MKProtocol.cs b/BrickController2/BrickController2/Protocols/MKProtocol.cs
index 5efd8d2c..ef42528a 100644
--- a/BrickController2/BrickController2/Protocols/MKProtocol.cs
+++ b/BrickController2/BrickController2/Protocols/MKProtocol.cs
@@ -16,84 +16,85 @@ public static class MKProtocol
     /// <summary>
     /// CTXValue for Encryption
     /// </summary>
-    public const byte CTXValue = 0x25;
+    public const byte CTXValue1 = 0x3f;
+
+    /// <summary>
+    /// CTXValue for Encryption
+    /// </summary>
+    public const byte CTXValue2 = 0x25;
 
     /// <summary>
     /// Address array
     /// </summary>
-    public static readonly byte[] AddressArray = new byte[] { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5 };
+    public static readonly byte[] SeedArray = { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5 };
 
     /// <summary>
     /// crypt data-array with addr and ctxvalue
     /// </summary>
-    /// <param name="addr">address array</param>
+    /// <param name="seed">address array</param>
     /// <param name="data">data array to encrypt</param>
-    /// <param name="ctxValue">ctx value for encryption</param>
+    /// <param name="headerOffset">offset for header data: android=0x0f (15) iOS=0xd (13)</param>
+    /// <param name="ctxValue1">ctx value for encryption</param>
+    /// <param name="ctxValue2">ctx value for encryption</param>
     /// <param name="rfPayload">crypted array</param>
     /// <returns>size of crypted array</returns>
-    public static int GetRfPayload(byte[] addr, byte[] data, byte ctxValue, out byte[] rfPayload)
+    public static int GetRfPayload(byte[] seed, byte[] data, int headerOffset, byte ctxValue1, byte ctxValue2, byte[] rfPayload)
     {
-        // resulting advertisment array has a length of constant 24 bytes
-        const int rfPayloadLength = 24;
+        const int initValuesLength = 3;
+        const int checksumLength = 2;
 
-        int addrLength = addr.Length;
+        int seedLength = seed.Length;
         int dataLength = data.Length;
-        int lengthResultArray = addrLength + dataLength + 5;
+        int resultArrayLength = initValuesLength + seedLength + dataLength + checksumLength;
 
-        if (lengthResultArray > rfPayloadLength)
+        if (resultArrayLength > rfPayload.Length)
         {
-            rfPayload = Array.Empty<byte>();
             return 0;
         }
 
-        byte data_offset = 0x12;    // 0x12 (18)
-        byte inverse_offset = 0x0f; // 0x0f (15)
+        //int headerOffset = 0x0f;                         // 0x0f (15)
+        int seedOffset = headerOffset + initValuesLength;  // 0x12 (18) 
+        int dataOffset = seedOffset + seedLength;
+        int checksumOffset = dataOffset + dataLength;
+
+        int resultBufferLength = checksumOffset + checksumLength;
 
-        int result_data_size = data_offset + addrLength + dataLength + 2;
-        byte[] resultbuf = new byte[result_data_size];
+        byte[] resultBuffer = new byte[resultBufferLength];
 
-        resultbuf[15] = 0x71;   // 0x71 (113)
-        resultbuf[16] = 0x0f;   // 0x0f (15)
-        resultbuf[17] = 0x55;   // 0x55 (85)
+        // initValues (offset 15)
+        resultBuffer[headerOffset + 0] = 0x71;   // 0x71 (113)
+        resultBuffer[headerOffset + 1] = 0x0f;   // 0x0f (15)
+        resultBuffer[headerOffset + 2] = 0x55;   // 0x55 (85)
 
-        // copy firstDataArray reverse into targetArray with offset 18
-        for (int index = 0; index < addrLength; index++)
+        // reverse-copy seed-array into resultBuffer after initValues (offset 18)
+        for (int index = 0; index < seedLength; index++)
         {
-            resultbuf[index + data_offset] = addr[addrLength - 1 - index];
+            resultBuffer[seedOffset + index] = seed[seedLength - 1 - index];
         }
 
-        // copy dataArray into resultbuf with offset 18 + addrLength
-        Buffer.BlockCopy(data, 0, resultbuf, data_offset + addrLength, dataLength);
-
-        // crypt Bytes from position 15 to 22
-        for (int index = inverse_offset; index < addrLength + data_offset; index++)
+        // invert bytes of initValues and seed-array in resultBuffer
+        for (int index = 0; index < initValuesLength + seedLength; index++)
         {
-            resultbuf[index] = CryptTools.Invert8(resultbuf[index]);
+            resultBuffer[headerOffset + index] = CryptTools.Invert8(resultBuffer[headerOffset + index]);
         }
 
-        // calc checksum und copy to array
-        ushort checksum = CryptTools.CheckCRC16(addr, data);
-        resultbuf.SetUInt16(checksum, result_data_size - 2);
-
-        byte[] ctx_0x3F = new byte[7]; // int local_58[8];
-        CryptTools.WhiteningInit(0x3f, ctx_0x3F); // 0x3f (63) -> ctx_0x3F = [1111111]
-        CryptTools.WhiteningEncode(resultbuf, 0x12, addrLength + dataLength + 2, ctx_0x3F);
+        // copy dataArray into resultBuffer after initValues and seed-array
+        Buffer.BlockCopy(data, 0, resultBuffer, dataOffset, dataLength);
 
-        byte[] ctx = new byte[7];
-        CryptTools.WhiteningInit(ctxValue, ctx); // ctxValue= 0x25 (37) -> ctx = [1101110]
-        CryptTools.WhiteningEncode(resultbuf, 0, result_data_size, ctx);
+        // calc checksum und copy to array
+        ushort checksum = CryptTools.CheckCRC16(seed, data);
+        resultBuffer.SetUInt16(checksum, checksumOffset);
 
-        // resulting advertisment array has a length of constant 24 bytes
-        rfPayload = new byte[rfPayloadLength];
+        byte[] ctxArray1 = new byte[7];
+        CryptTools.WhiteningInit(ctxValue1, ctxArray1); // 0x3f (63) -> ctx_0x3F = [1111111]
+        CryptTools.WhiteningEncode(resultBuffer, seedOffset, seedLength + dataLength + checksumLength, ctxArray1);
 
-        Buffer.BlockCopy(resultbuf, 15, rfPayload, 0, lengthResultArray);
+        byte[] ctxArray2 = new byte[7];
+        CryptTools.WhiteningInit(ctxValue2, ctxArray2); // ctxValue= 0x25 (37) -> ctx = [1101110]
+        CryptTools.WhiteningEncode(resultBuffer, 0, resultBufferLength, ctxArray2);
 
-        // fill rest of array
-        for (int index = lengthResultArray; index < rfPayloadLength; index++)
-        {
-            rfPayload[index] = (byte)(index + 1);
-        }
+        Buffer.BlockCopy(resultBuffer, headerOffset, rfPayload, 0, resultArrayLength);
 
-        return rfPayloadLength;
+        return resultArrayLength;
     }
 }
diff --git a/README.md b/README.md
index 378d1052..99a20085 100644
--- a/README.md
+++ b/README.md
@@ -23,8 +23,8 @@ Cross platform mobile application for controlling your creations using a bluetoo
 - Lego Duplo Train Hub
 - Circuit Cubes
 - Mould King DIY Module
-- Mould King 4.0 Powered Module (Android only)
-- Mould King 6.0 Powered Module (Android only)
+- Mould King 4.0 Powered Module
+- Mould King 6.0 Powered Module
 - CaDA Race Car (Android only)
 - PFx Brick (lights & Power Functions ports only)
 


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces an iOS advertiser for the MouldKing MK4 and MK6 devices and refactors the related protocol and device management logic to support cross-platform functionality.  
- Updated README to remove Android-only restrictions for Mould King modules.  
- Refactored MKProtocol’s GetRfPayload method to support dynamic payload lengths and renamed constants for clarity.  
- Modified MK device classes and implemented platform-specific MKPlatformService for iOS, WinUI, and Android.

### Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| README.md | Removed Android-only notes for Mould King modules to reflect cross-platform support. |
| BrickController2/BrickController2/Protocols/MKProtocol.cs | Refactored GetRfPayload method with updated parameters and renaming of constants/arrays. |
| MKBaseNible.cs & MKBaseByte.cs | Updated constructors to inject an IMKPlatformService dependency. |
| MK6.cs & MK4.cs | Updated device constructors to accept an IMKPlatformService instance. |
| IMKPlatformService.cs | Added interface for platform-specific MouldKing functionality. |
| MKPlatformService.cs (iOS, WinUI, Android) | Implemented platform-specific methods to generate RF payloads. |
| PlatformServicesModule.cs (iOS, WinUI, Android) | Registered MKPlatformService as a singleton in dependency injection. |
| BluetoothLEAdvertiserDevice.cs | Implemented iOS advertiser device returning a non-null instance. |
</details>



<details>
<summary>Comments suppressed due to low confidence (3)</summary>

**BrickController2/BrickController2/iOS/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs:4**
* The namespace in this iOS-specific file references 'Windows' which appears inconsistent; please update it to reflect the iOS project structure.
```
namespace BrickController2.Windows.DeviceManagement.DI;
```
**BrickController2/BrickController2/Protocols/MKProtocol.cs:41**
* The GetRfPayload method now returns a dynamic payload length instead of a fixed value; ensure that all consumers of this API handle variable lengths appropriately and update corresponding documentation.
```
public static int GetRfPayload(byte[] seed, byte[] data, int headerOffset, byte ctxValue1, byte ctxValue2, byte[] rfPayload)
```
**BrickController2/BrickController2/Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs:9**
* The Android implementation uses a PayloadLength of 24 while other platforms use 26; please confirm that this discrepancy is intentional and document any platform-specific differences.
```
private const int PayloadLength = 24;
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-05-20T15:16:15Z
user: login: vicocz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T15:25:06Z
user: login: vicocz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T15:26:11Z
user: login: vicocz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T15:31:37Z
user: login: vicocz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T16:25:39Z
user: login: J0EK3R
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-20T17:04:30Z
user: login: vicocz
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T18:08:27Z
user: login: J0EK3R
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T11:30:46Z
user: login: J0EK3R
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T11:31:12Z
user: login: J0EK3R
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T17:23:53Z
user: login: J0EK3R
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T17:42:56Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request adds iOS advertiser support for the MouldKing MK4 and MK6 devices by updating the protocol encryption logic, modifying device initialization classes to use platform‐specific services, and implementing the necessary platform service and BLE advertiser components.  
- Updated README to advertise iOS support  
- Modified MKProtocol encryption parameters and refactored related methods  
- Updated device management classes and added platform‐specific MKPlatformService implementations for iOS, WinUI, and Android

### Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| README.md | Removed “Android only” qualifiers to include iOS support for Mould King modules |
| BrickController2/BrickController2/Protocols/MKProtocol.cs | Updated encryption constants, parameter names, and array handling for the new payload structure |
| BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNible.cs | Updated constructor to accept IMKPlatformService and replaced direct protocol calls |
| BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs | Updated constructor to accept IMKPlatformService and replaced direct protocol calls |
| BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs | Updated constructor signature to include MKPlatformService dependency |
| BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs | Updated constructor signature to include MKPlatformService dependency |
| BrickController2/BrickController2/DeviceManagement/MouldKing/IMKPlatformService.cs | Added interface declaration for platform-specific functionality |
| BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs | iOS-specific implementation of MKPlatformService using updated MKProtocol |
| BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs | Implemented BLE advertiser device functionality with new C# array initializer syntax |
| BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs | WinUI-specific implementation of MKPlatformService |
| BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs | Registered MKPlatformService for WinUI in DI |
| BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs | Android-specific implementation of MKPlatformService |
| BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs | Registered MKPlatformService for Android in DI |
</details>





state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T21:02:17Z
user: login: vicocz
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-21T21:06:05Z

Review comments:
diff_hunk: @@ -0,0 +1,24 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.Windows.DeviceManagement.DI;
path: BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
user: login: vicocz
body: Nit pick: wrong namespace
created_at: 2025-05-20T15:25:06Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,25 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.Windows.DeviceManagement.DI;
path: BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
user: login: vicocz
body: Nit pick: Fix namespace please.
created_at: 2025-05-20T15:26:11Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,98 @@
+﻿using System.Threading.Tasks;
+using CoreBluetooth;
+using BrickController2.PlatformServices.BluetoothLE;
+
+namespace BrickController2.iOS.PlatformServices.BluetoothLE;
+
+internal class BluetoothLEAdvertiserDevice : CBPeripheralManagerDelegate, IBluetoothLEAdvertiserDevice
+{
+    private CBPeripheralManager? _peripheralManager;
+    private StartAdvertisingOptions? _advData;
+
+    public BluetoothLEAdvertiserDevice()
+    {
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        StopAdvertiseInternal();
+        _peripheralManager?.Dispose();
+
+        base.Dispose(disposing);
+    }
+
+    public Task StartAdvertiseAsync(AdvertisingInterval advertisingIterval, TxPowerLevel txPowerLevel, ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    public Task StopAdvertiseAsync()
+    {
+        StopAdvertiseInternal();
+
+        return Task.CompletedTask;
+    }
+
+    public Task UpdateAdvertisedDataAsync(ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    private void SetAdvertisingData(ushort manufacturerId, byte[] rawData)
+    {
+        // JK: no check - rawDataLength has to be even!
path: BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs
user: login: vicocz
body: What happens if not?
created_at: 2025-05-20T15:31:37Z
author_association: OWNER
diff_hunk: @@ -16,84 +16,85 @@ public static class MKProtocol
     /// <summary>
     /// CTXValue for Encryption
     /// </summary>
-    public const byte CTXValue = 0x25;
+    public const byte CTXValue1 = 0x3f;
+
+    /// <summary>
+    /// CTXValue for Encryption
+    /// </summary>
+    public const byte CTXValue2 = 0x25;
 
     /// <summary>
     /// Address array
     /// </summary>
-    public static readonly byte[] AddressArray = new byte[] { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5 };
+    public static readonly byte[] SeedArray = { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5 };
 
     /// <summary>
     /// crypt data-array with addr and ctxvalue
     /// </summary>
-    /// <param name="addr">address array</param>
+    /// <param name="seed">address array</param>
     /// <param name="data">data array to encrypt</param>
-    /// <param name="ctxValue">ctx value for encryption</param>
+    /// <param name="headerOffset">offset for header data: android=0x0f (15) iOS=0xd (13)</param>
+    /// <param name="ctxValue1">ctx value for encryption</param>
+    /// <param name="ctxValue2">ctx value for encryption</param>
     /// <param name="rfPayload">crypted array</param>
     /// <returns>size of crypted array</returns>
-    public static int GetRfPayload(byte[] addr, byte[] data, byte ctxValue, out byte[] rfPayload)
+    public static int GetRfPayload(byte[] seed, byte[] data, int headerOffset, byte ctxValue1, byte ctxValue2, byte[] rfPayload)
path: BrickController2/BrickController2/Protocols/MKProtocol.cs
user: login: vicocz
body: Consider adding some unit test please.
created_at: 2025-05-20T16:25:39Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,98 @@
+﻿using System.Threading.Tasks;
+using CoreBluetooth;
+using BrickController2.PlatformServices.BluetoothLE;
+
+namespace BrickController2.iOS.PlatformServices.BluetoothLE;
+
+internal class BluetoothLEAdvertiserDevice : CBPeripheralManagerDelegate, IBluetoothLEAdvertiserDevice
+{
+    private CBPeripheralManager? _peripheralManager;
+    private StartAdvertisingOptions? _advData;
+
+    public BluetoothLEAdvertiserDevice()
+    {
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        StopAdvertiseInternal();
+        _peripheralManager?.Dispose();
+
+        base.Dispose(disposing);
+    }
+
+    public Task StartAdvertiseAsync(AdvertisingInterval advertisingIterval, TxPowerLevel txPowerLevel, ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    public Task StopAdvertiseAsync()
+    {
+        StopAdvertiseInternal();
+
+        return Task.CompletedTask;
+    }
+
+    public Task UpdateAdvertisedDataAsync(ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    private void SetAdvertisingData(ushort manufacturerId, byte[] rawData)
+    {
+        // JK: no check - rawDataLength has to be even!
path: BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs
user: login: J0EK3R
body: hmmm... it must not and also can't happen at the moment.
what do you suggest? throw an exception?
created_at: 2025-05-20T17:04:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,98 @@
+﻿using System.Threading.Tasks;
+using CoreBluetooth;
+using BrickController2.PlatformServices.BluetoothLE;
+
+namespace BrickController2.iOS.PlatformServices.BluetoothLE;
+
+internal class BluetoothLEAdvertiserDevice : CBPeripheralManagerDelegate, IBluetoothLEAdvertiserDevice
+{
+    private CBPeripheralManager? _peripheralManager;
+    private StartAdvertisingOptions? _advData;
+
+    public BluetoothLEAdvertiserDevice()
+    {
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        StopAdvertiseInternal();
+        _peripheralManager?.Dispose();
+
+        base.Dispose(disposing);
+    }
+
+    public Task StartAdvertiseAsync(AdvertisingInterval advertisingIterval, TxPowerLevel txPowerLevel, ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    public Task StopAdvertiseAsync()
+    {
+        StopAdvertiseInternal();
+
+        return Task.CompletedTask;
+    }
+
+    public Task UpdateAdvertisedDataAsync(ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    private void SetAdvertisingData(ushort manufacturerId, byte[] rawData)
+    {
+        // JK: no check - rawDataLength has to be even!
path: BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs
user: login: vicocz
body: May be some exception is the best option right now, just checks what happens next. If it§s not hidden to user that something is wrong.
Any chance to validate the length somewhere else so as e.g. for such types of devices connection is not established?
created_at: 2025-05-20T18:08:27Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,24 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.Windows.DeviceManagement.DI;
path: BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
user: login: J0EK3R
body: OH, OK - I fixed them all

https://github.com/J0EK3R/brickcontroller2/commits/iOSAdvertiser/
created_at: 2025-05-21T11:30:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+﻿using BrickController2.DeviceManagement;
+using BrickController2.Protocols;
+
+namespace BrickController2.Windows.DeviceManagement.DI;
path: BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs
user: login: J0EK3R
body: Fixed

https://github.com/J0EK3R/brickcontroller2/commits/iOSAdvertiser/
created_at: 2025-05-21T11:31:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,98 @@
+﻿using System.Threading.Tasks;
+using CoreBluetooth;
+using BrickController2.PlatformServices.BluetoothLE;
+
+namespace BrickController2.iOS.PlatformServices.BluetoothLE;
+
+internal class BluetoothLEAdvertiserDevice : CBPeripheralManagerDelegate, IBluetoothLEAdvertiserDevice
+{
+    private CBPeripheralManager? _peripheralManager;
+    private StartAdvertisingOptions? _advData;
+
+    public BluetoothLEAdvertiserDevice()
+    {
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        StopAdvertiseInternal();
+        _peripheralManager?.Dispose();
+
+        base.Dispose(disposing);
+    }
+
+    public Task StartAdvertiseAsync(AdvertisingInterval advertisingIterval, TxPowerLevel txPowerLevel, ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    public Task StopAdvertiseAsync()
+    {
+        StopAdvertiseInternal();
+
+        return Task.CompletedTask;
+    }
+
+    public Task UpdateAdvertisedDataAsync(ushort manufacturerId, byte[] rawData)
+    {
+        SetAdvertisingData(manufacturerId, rawData);
+
+        return Task.CompletedTask;
+    }
+
+    private void SetAdvertisingData(ushort manufacturerId, byte[] rawData)
+    {
+        // JK: no check - rawDataLength has to be even!
path: BrickController2/BrickController2.iOS/PlatformServices/BluetoothLE/BluetoothLEAdvertiserDevice.cs
user: login: J0EK3R
body: Currently the payload length is constant [26 bytes](https://github.com/J0EK3R/brickcontroller2/blob/iOSAdvertiser/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/MouldKing/MKPlatformService.cs).
For the moment I would prefer not to do any checks...

If there is a solution for CaDA on iOS and the payload length isn't constant we could do some checks...

created_at: 2025-05-21T17:23:52Z
author_association: NONE
diff_hunk: @@ -16,84 +16,85 @@ public static class MKProtocol
     /// <summary>
     /// CTXValue for Encryption
     /// </summary>
-    public const byte CTXValue = 0x25;
+    public const byte CTXValue1 = 0x3f;
+
+    /// <summary>
+    /// CTXValue for Encryption
+    /// </summary>
+    public const byte CTXValue2 = 0x25;
 
     /// <summary>
     /// Address array
     /// </summary>
-    public static readonly byte[] AddressArray = new byte[] { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5 };
+    public static readonly byte[] SeedArray = { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5 };
 
     /// <summary>
     /// crypt data-array with addr and ctxvalue
     /// </summary>
-    /// <param name="addr">address array</param>
+    /// <param name="seed">address array</param>
     /// <param name="data">data array to encrypt</param>
-    /// <param name="ctxValue">ctx value for encryption</param>
+    /// <param name="headerOffset">offset for header data: android=0x0f (15) iOS=0xd (13)</param>
+    /// <param name="ctxValue1">ctx value for encryption</param>
+    /// <param name="ctxValue2">ctx value for encryption</param>
     /// <param name="rfPayload">crypted array</param>
     /// <returns>size of crypted array</returns>
-    public static int GetRfPayload(byte[] addr, byte[] data, byte ctxValue, out byte[] rfPayload)
+    public static int GetRfPayload(byte[] seed, byte[] data, int headerOffset, byte ctxValue1, byte ctxValue2, byte[] rfPayload)
path: BrickController2/BrickController2/Protocols/MKProtocol.cs
user: login: J0EK3R
body: yes, would make sense.
I've created an [Issue](https://github.com/vicocz/brickcontroller2/issues/135)
created_at: 2025-05-21T17:42:56Z
author_association: NONE

PR comments:
user: login: J0EK3R
created_at: 2025-05-21T17:32:02Z
body: Do you think this pull request is ready to review and merge?
author_association: NONE
user: login: vicocz
created_at: 2025-05-21T18:04:39Z
body: > Do you think this pull request is ready to review and merge?

Yes, I did not see any serious issue at all, so I will have a look tonight.
author_association: OWNER
user: login: vicocz
created_at: 2025-05-21T21:10:25Z
body: Thx for this feature, good job with hacking the protocol.
author_association: OWNER
user: login: J0EK3R
created_at: 2025-05-22T03:47:22Z
body: > Thx for this feature, good job with hacking the protocol.

Thank you, you're welcome :)
author_association: NONE