From a31c1c0a1bbf324f4da97d0115c9c99d62a30a6b Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 23 Jul 2026 13:54:17 +0200 Subject: [PATCH 01/14] added new device PowerBox MBattery --- .../DI/PlatformServicesModule.cs | 3 + .../PowerBox/PowerBoxPlatformService.cs | 24 ++ .../DI/PlatformServicesModule.cs | 3 + .../PowerBox/PowerBoxPlatformService.cs | 25 ++ .../DI/PlatformServicesModule.cs | 3 + .../PowerBox/PowerBoxPlatformService.cs | 25 ++ .../DeviceManagement/DeviceType.cs | 1 + .../PowerBox/IPowerBoxPlatformService.cs | 9 + .../DeviceManagement/PowerBox/PowerBox.cs | 25 ++ .../DeviceManagement/PowerBox/PowerBoxBase.cs | 251 ++++++++++++++++++ .../PowerBox/PowerBoxDeviceManager.cs | 21 ++ .../PowerBox/PowerBoxMBattery.cs | 76 ++++++ .../Protocols/PowerBoxProtocol.cs | 44 +++ .../UI/Controls/DeviceChannelLabel.cs | 4 + .../UI/Controls/DeviceChannelSelector.xaml | 17 ++ .../UI/Controls/DeviceChannelSelector.xaml.cs | 3 + .../UI/Images/powerboxmbattery_image.png | Bin 0 -> 16721 bytes .../Images/powerboxmbattery_image_small.png | Bin 0 -> 16721 bytes 18 files changed, 534 insertions(+) create mode 100644 BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs create mode 100644 BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs create mode 100644 BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxPlatformService.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs create mode 100644 BrickController2/BrickController2/Protocols/PowerBoxProtocol.cs create mode 100644 BrickController2/BrickController2/UI/Images/powerboxmbattery_image.png create mode 100644 BrickController2/BrickController2/UI/Images/powerboxmbattery_image_small.png diff --git a/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs b/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs index ef38a5f3..ec254a14 100644 --- a/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs +++ b/BrickController2/BrickController2.Android/PlatformServices/DI/PlatformServicesModule.cs @@ -2,10 +2,12 @@ using BrickController2.DeviceManagement.CaDA; using BrickController2.DeviceManagement.JieStar; using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.PowerBox; using BrickController2.Droid.PlatformServices.BluetoothLE; using BrickController2.Droid.PlatformServices.DeviceManagement.CaDA; using BrickController2.Droid.PlatformServices.DeviceManagement.JieStar; using BrickController2.Droid.PlatformServices.DeviceManagement.MouldKing; +using BrickController2.Droid.PlatformServices.DeviceManagement.PowerBox; using BrickController2.Droid.PlatformServices.GameController; using BrickController2.Droid.PlatformServices.Infrared; using BrickController2.Droid.PlatformServices.Localization; @@ -35,6 +37,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); } } } \ No newline at end of file diff --git a/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs b/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs new file mode 100644 index 00000000..34462c09 --- /dev/null +++ b/BrickController2/BrickController2.Android/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs @@ -0,0 +1,24 @@ +using BrickController2.DeviceManagement.PowerBox; +using BrickController2.Protocols; + +namespace BrickController2.Droid.PlatformServices.DeviceManagement.PowerBox; + +public class PowerBoxPlatformService : IPowerBoxPlatformService +{ + private const int HeaderOffset = 15; + private const int PayloadLength = 18; + + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) + { + rfPayload = new byte[PayloadLength]; + int payloadLength = CryptTools.GetRfPayload(PowerBoxProtocol.SeedArray, PowerBoxProtocol.HeaderArray, rawData, HeaderOffset, PowerBoxProtocol.CTXValue1, PowerBoxProtocol.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 7207d22e..d8b12b77 100644 --- a/BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs +++ b/BrickController2/BrickController2.WinUI/PlatformServices/DI/PlatformServicesModule.cs @@ -2,6 +2,7 @@ using BrickController2.DeviceManagement.CaDA; using BrickController2.DeviceManagement.JieStar; using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.PowerBox; using BrickController2.PlatformServices.BluetoothLE; using BrickController2.PlatformServices.InputDeviceService; using BrickController2.PlatformServices.Infrared; @@ -12,6 +13,7 @@ using BrickController2.Windows.PlatformServices.DeviceManagement.CaDA; using BrickController2.Windows.PlatformServices.DeviceManagement.JieStar; using BrickController2.Windows.PlatformServices.DeviceManagement.MouldKing; +using BrickController2.Windows.PlatformServices.DeviceManagement.PowerBox; using BrickController2.Windows.PlatformServices.GameController; using BrickController2.Windows.PlatformServices.Infrared; using BrickController2.Windows.PlatformServices.Localization; @@ -34,5 +36,6 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); } } \ No newline at end of file diff --git a/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs b/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs new file mode 100644 index 00000000..5e5ac7f2 --- /dev/null +++ b/BrickController2/BrickController2.WinUI/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs @@ -0,0 +1,25 @@ +using BrickController2.DeviceManagement.PowerBox; +using BrickController2.Protocols; + +namespace BrickController2.Windows.PlatformServices.DeviceManagement.PowerBox; + +public class PowerBoxPlatformService : IPowerBoxPlatformService +{ + private const int HeaderOffset = 15; + private const int PayloadOffset = 3; + private const int PayloadLength = 23 + PayloadOffset; + + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) + { + rfPayload = new byte[PayloadLength]; + int payloadLength = CryptTools.GetRfPayload(PowerBoxProtocol.SeedArray, PowerBoxProtocol.HeaderArray, rawData, HeaderOffset, PowerBoxProtocol.CTXValue1, PowerBoxProtocol.CTXValue2, rfPayload, PayloadOffset); + + // fill rest of array + for (int index = payloadLength + PayloadOffset; index < PayloadLength; index++) + { + rfPayload[index] = (byte)(index + 1); + } + + return true; + } +} diff --git a/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs b/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs index 4d675a80..aadad457 100644 --- a/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs +++ b/BrickController2/BrickController2.iOS/PlatformServices/DI/PlatformServicesModule.cs @@ -2,10 +2,12 @@ using BrickController2.DeviceManagement.CaDA; using BrickController2.DeviceManagement.JieStar; using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.PowerBox; using BrickController2.iOS.PlatformServices.BluetoothLE; using BrickController2.iOS.PlatformServices.DeviceManagement.CaDA; using BrickController2.iOS.PlatformServices.DeviceManagement.JieStar; using BrickController2.iOS.PlatformServices.DeviceManagement.MouldKing; +using BrickController2.iOS.PlatformServices.DeviceManagement.PowerBox; using BrickController2.iOS.PlatformServices.GameController; using BrickController2.iOS.PlatformServices.Infrared; using BrickController2.iOS.PlatformServices.Localization; @@ -35,6 +37,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); } } } \ No newline at end of file diff --git a/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs b/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs new file mode 100644 index 00000000..e563d140 --- /dev/null +++ b/BrickController2/BrickController2.iOS/PlatformServices/DeviceManagement/PowerBox/PowerBoxPlatformService.cs @@ -0,0 +1,25 @@ +using BrickController2.DeviceManagement.PowerBox; +using BrickController2.Protocols; + +namespace BrickController2.iOS.PlatformServices.DeviceManagement.PowerBox; + +public class PowerBoxPlatformService : IPowerBoxPlatformService +{ + private const int HeaderOffset = 13; + private const int PayloadLength = 20; + + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) + { + rfPayload = new byte[PayloadLength]; + int payloadLength = CryptTools.GetRfPayload(PowerBoxProtocol.SeedArray, PowerBoxProtocol.HeaderArray, rawData, HeaderOffset, PowerBoxProtocol.CTXValue1, PowerBoxProtocol.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/DeviceType.cs b/BrickController2/BrickController2/DeviceManagement/DeviceType.cs index 13e7c1a8..4388d7fb 100644 --- a/BrickController2/BrickController2/DeviceManagement/DeviceType.cs +++ b/BrickController2/BrickController2/DeviceManagement/DeviceType.cs @@ -26,5 +26,6 @@ public enum DeviceType SBrickLight, JieStarSCM4, JieStarSCM8, + PowerBoxMBattery, } } diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxPlatformService.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxPlatformService.cs new file mode 100644 index 00000000..e73d1706 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxPlatformService.cs @@ -0,0 +1,9 @@ +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// Interface definition for PowerBox specific PlatformService +/// +public interface IPowerBoxPlatformService +{ + bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload); +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs new file mode 100644 index 00000000..50a5cef8 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs @@ -0,0 +1,25 @@ +using Autofac; +using BrickController2.DeviceManagement.DI; +using BrickController2.DeviceManagement.Vendors; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// Vendor: PowerBox and all its devices +/// +internal class PowerBox : Vendor +{ + public override string VendorName => "PowerBox"; + + protected override void Register(VendorBuilder builder) + { + // device manager + builder.ContainerBuilder.RegisterType() + .SingleInstance(); + + // manually added devices + builder.RegisterDevice() + .WithDeviceFactory(PowerBoxMBattery.Device, $"{PowerBoxMBattery.TypeName} Device"); + + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs new file mode 100644 index 00000000..77948d43 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs @@ -0,0 +1,251 @@ +using System; +using BrickController2.PlatformServices.BluetoothLE; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// PowerBox baseclass +/// +internal abstract class PowerBoxBase : BluetoothAdvertisingDevice +{ + /// + /// offset to position of first channel in base telegram + /// + private const int CHANNEL_START_OFFSET = 3; + + /// + /// platform specific PowerBox stuff + /// + protected readonly IPowerBoxPlatformService _powerboxPlatformService; + + /// + /// Telegram to connect to the device + /// This telegram is sent on init and on reconnect conditions matching + /// + protected readonly byte[] _telegram_Connect; + + /// + /// base telegram + /// + protected readonly byte[] _telegram_Base; + + /// + /// array to hold the incoming output values for all channels. + /// + protected readonly float[] _storedValues; + + protected PowerBoxBase(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) + : base(name, address, deviceData, deviceRepository, bleService) + { + _telegram_Connect = telegram_Connect; + _telegram_Base = telegram_Base; + _powerboxPlatformService = powerboxPlatformService; + _storedValues = new float[NumberOfChannels]; // initialize output values for all channels + + // bytes[1] and [2] of both telegrams can be set to a unique appId + ReadOnlySpan appId = powerboxDeviceManager.GetAppId().Span[..2]; + _telegram_Connect[1] = appId[0]; + _telegram_Connect[2] = appId[1]; + + _telegram_Base[1] = appId[0]; + _telegram_Base[2] = appId[1]; + } + + /// + /// No voltage + /// + public override string BatteryVoltageSign => string.Empty; + + /// + /// Sets the output value for the specified channel. + /// + /// This method updates the output value for the specified channel and ensures the value is + /// within the valid range. If the value changes, the method triggers a notification to indicate that data has been + /// updated. + /// The channel number for which the output value is being set. Must be a valid channel index. + /// The output value to set. The value will be adjusted if it exceeds the allowable range. + public override void SetOutput(int channelNo, float value) + { + CheckChannel(channelNo); + value = CutOutputValue(value); + + // store the incoming value in the stored values array + _storedValues[channelNo] = value; + + lock (_outputLock) + { + // call the channel specific set function + bool valueChanged = SetChannelOutput(channelNo, value); + + // check for change + if (valueChanged) + { + _bluetoothAdvertisingDeviceHandler.NotifyDataChanged(); + } + } + } + + /// + /// Processes the specified channel value and returns a transformed result. + /// + /// The exact transformation logic and conditions for success are determined by the implementing + /// class. + /// The channel number to process. Must be a non-negative integer. + /// The input value associated with the channel to be processed. + /// A tuple containing the processed result: value: A byte + /// representing the transformed value for the specified channel. + /// flag: A boolean indicating whether the value is marked as zero () or + /// not (). + protected abstract (byte value, bool flag) ProcessChannelValue(int channelNo, float value); + + /// + /// Updates a specific byte in the telegram buffer and returns whether the value was changed. + /// + /// This method modifies the telegram buffer by updating the specified byte. The operation is + /// thread-safe and ensures exclusive access to the buffer during the + /// update. + /// The zero-based index of the byte in the telegram buffer to modify. + /// The value to set.param> + /// if the byte in the telegram buffer was modified; otherwise, if + /// the value remained unchanged. + protected bool SetChannelValue(int byteOffset, byte setValue_byte) + { + lock (_outputLock) + { + byte originValue_byte = _telegram_Base[byteOffset]; + + _telegram_Base[byteOffset] = setValue_byte; + _telegram_Base[byteOffset + 1] = setValue_byte; // bytes are duplicated in the telegram + return _telegram_Base[byteOffset] != originValue_byte; + } + } + + /// + /// Converts a floating-point value into a byte representation for an analog channel output. + /// + /// The method maps the input value to a byte representation based on predefined ranges for + /// positive, negative, and zero values. The zero value is represented by a specific byte constant. The caller can + /// use the returned boolean to determine if the byte corresponds to the zero value. + /// The floating-point value to be converted. Negative values are mapped to the negative range, positive values are + /// mapped to the positive range, and zero is mapped to a predefined byte. + /// A tuple containing the following: A representing + /// the byte value for the analog channel output. A + /// indicating whether the byte corresponds to the zero value. if the byte represents + /// zero; otherwise, . + protected (byte setValue_Byte, bool zeroSet) SetOutput_AnalogChannel(float value) + { + // value < 0: 7f 6e 5d 4c 3b 2a 19 RANGE_NEG: 0x07 + // value == 0: 00 ZEROVALUE + // value > 0: 91 a2 b3 c4 d5 e6 f7 RANGE_POS: 0x07 + + const int RANGE_POS = 0x07; + const int RANGE_NEG = 0x07; + + const float MIN_NEG_RANGE_THRESHOLD = -1f / RANGE_NEG; // Minimum value for negative range + const float MIN_POS_RANGE_THRESHOLD = 1f / RANGE_POS; // Minimum value for positive range + + const byte ZEROVALUE = 0x00; + + if (value <= MIN_NEG_RANGE_THRESHOLD) + { + byte value_abs = (byte)Math.Min(0x07, -value * RANGE_NEG); + byte setValue_byte = (byte)((value_abs << 4) + value_abs + 8); + + return (setValue_byte, false); + } + else if (value >= MIN_POS_RANGE_THRESHOLD) + { + byte value_abs = (byte)Math.Min(0x07, value * RANGE_POS); + byte setValue_byte = (byte)(((value_abs + 8) << 4) + value_abs); + + return (setValue_byte, false); + } + else + { + return (ZEROVALUE, true); + } + } + + /// + /// Sets the output value for the specified channel. + /// + /// This method handles both virtual and real channels. For virtual channels, the value is + /// processed and the modification status is returned. For real channels, the method calculates the appropriate + /// byte offset and channel-specific parameters, processes the value, and updates the channel state + /// accordingly. + /// The channel number for which the output value is to be set. Must be a valid channel identifier. + /// The output value to set for the specified channel. The value is processed before being applied. + /// if the channel's output value was modified; otherwise, . + protected bool SetChannelOutput(int channelNo, float value) + { + // real channel + int byteOffset = GetTargetPosition(channelNo); + (byte setValue_byte, bool zeroSet) = ProcessChannelValue(channelNo, value); + + _bluetoothAdvertisingDeviceHandler.SetChannelState(channelNo, zeroSet); // set global channel state + return SetChannelValue(byteOffset, setValue_byte); + } + + /// + /// This method sets the device to initial state before advertising starts + /// All channels are initialized with zeroValue. + /// + protected override void InitDevice() + { + const float zeroValue = 0.0f; + + for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) + { + _storedValues[channelNo] = zeroValue; // restore stored values to zero + SetChannelOutput(channelNo, zeroValue); // set all channels to zero using the channel specific function + } + } + + /// + /// Disconnects the device and resets the output state of all channels to zero. + /// + /// This method ensures that all channels are set to a zero output state during the disconnection + /// process. It is intended to be called as part of the device's disconnection workflow. + protected override void DisconnectDevice() + { + const float zeroValue = 0.0f; + + for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) + { + // call _bluetoothAdvertisingDeviceHandler.SetChannelState() to set global channel state to zero + SetChannelOutput(channelNo, zeroValue); + } + } + + /// + /// Attempts to retrieve the RF payload for the specified telegram type. + /// + /// This method delegates the retrieval of the RF payload to the underlying platform + /// service. + /// A boolean value indicating the type of telegram to retrieve. to retrieve the connect + /// telegram; to retrieve the base telegram. + /// When this method returns, contains the RF payload as a byte array if the operation succeeds; otherwise, . + /// if the RF payload was successfully retrieved; otherwise, . + protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) + { + if (getConnectTelegram) + { + return _powerboxPlatformService.TryGetRfPayload(_telegram_Connect, out payload); + } + else + { + return _powerboxPlatformService.TryGetRfPayload(_telegram_Base, out payload); + } + } + + /// + /// Calculates the target position of a channel within the current instance. + /// + /// The channel number for which the position is calculated. Must be a non-negative integer. + /// The byte offset. + protected virtual int GetTargetPosition(int channelNo) + { + return CHANNEL_START_OFFSET + channelNo; + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs new file mode 100644 index 00000000..6820a0a2 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs @@ -0,0 +1,21 @@ +using System; +using BrickController2.UI.Services.AppIdentifier; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// Manager for PowerBox devices +/// +public class PowerBoxDeviceManager +{ + private const int AppIdentifierLength = 2; // PowerBox protocol defines 2 bytes for the app identifier + + private readonly ReadOnlyMemory _appIdentifier; + + public PowerBoxDeviceManager(IAppIdentifierService appIdentifierService) + { + _appIdentifier = appIdentifierService.GetAppId(AppIdentifierLength); + } + + public ReadOnlyMemory GetAppId() => _appIdentifier; +} \ No newline at end of file diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs new file mode 100644 index 00000000..f223e7c8 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs @@ -0,0 +1,76 @@ +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.Protocols; +using System; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// PowerBox 1 Channel +/// +internal class PowerBoxMBattery : PowerBoxBase, IDeviceType +{ + public const string Device = "Device"; + + /// + /// Telegram to connect to the PowerBox devices + /// This telegram is sent on init and on reconnect conditions matching + /// + private static readonly byte[] Telegram_Connect_Device = [0xA4, 0xFE, 0x19, 0x80, 0x80, 0x80, 0x00, 0x5B]; + + /// + /// Base Telegram for PowerBox devices + /// + private static readonly byte[] Telegram_Base_Device = [0x40, 0xFE, 0x19, 0x00, 0x00, 0x00, 0x00, 0xBF]; + + /// + /// after this timespan and all channel's values equal to zero the connect telegram is sent + /// + private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); + + public PowerBoxMBattery(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, powerboxPlatformService, powerboxDeviceManager, Telegram_Connect_Device, Telegram_Base_Device) + { + } + + public static DeviceType Type => DeviceType.PowerBoxMBattery; + + public static string TypeName => "PowerBox M Battery"; + + public override DeviceType DeviceType => Type; + + /// + /// Gets the number of channels supported by the device. + /// + /// Channel 0: real existing channel + /// + /// + public override int NumberOfChannels => 1; + + /// + /// manufacturerId to advertise + /// + protected override ushort ManufacturerId => PowerBoxProtocol.ManufacturerID; + + /// + /// Get or create BluetoothAdvertisingDeviceHandler + /// + /// Instance of BluetoothAdvertisingDeviceHandler + protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDeviceHandler() + { + // PowerBoxMBattery needs a BluetoothAdvertiser per module + return new BluetoothAdvertisingDeviceHandler(_bleService, ManufacturerId, TryGetTelegram, PowerBoxMBattery.ReconnectTimeSpan); + } + + /// + /// Processes the value for the specified analog channel and returns the processed result. + /// + /// The channel number to process. Valid value is 0. + /// The input value to be processed for the specified channel. + /// A tuple containing the processed value and a flag indicating the success of the operation. + /// Thrown if is not the valid channel number 0. + protected override (byte value, bool flag) ProcessChannelValue(int channelNo, float value) => channelNo switch + { + 0 => SetOutput_AnalogChannel(value), + _ => throw new ArgumentException($"Illegal Argument \"{channelNo}\"", nameof(channelNo)) + }; +} diff --git a/BrickController2/BrickController2/Protocols/PowerBoxProtocol.cs b/BrickController2/BrickController2/Protocols/PowerBoxProtocol.cs new file mode 100644 index 00000000..cc9fb217 --- /dev/null +++ b/BrickController2/BrickController2/Protocols/PowerBoxProtocol.cs @@ -0,0 +1,44 @@ +namespace BrickController2.Protocols; + +/// +/// static class which implements the encryption algorithm for the advertising data +/// +public static class PowerBoxProtocol +{ + /// + /// ManufacturerID for PowerBox + /// + public const ushort ManufacturerID = 0xFF00; + + /// + /// CTXValue for Encryption + /// + public const byte CTXValue1 = 0x3f; + + /// + /// CTXValue for Encryption + /// + public const byte CTXValue2 = 0x25; + + /// + /// SeedArray + /// + public static readonly byte[] SeedArray = + { + 0xC1, + 0xC2, + 0xC3, + 0xC4, + 0xC5, + }; + + /// + /// HeaderArray + /// + public static readonly byte[] HeaderArray = + { + 0x71, // 0x71 (113) + 0x0f, // 0x0f (15) + 0x55, // 0x55 (85) + }; +} diff --git a/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs b/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs index 5d0a90d0..a7b0f1a5 100644 --- a/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs +++ b/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs @@ -17,6 +17,7 @@ public class DeviceChannelLabel : Label private readonly static string[] _mk6ChannelLetters = new[] { "A", "B", "C", "D", "E", "F" }; private readonly static string[] _sBrickLightChannelLetters = ["A", "B", "C", "D", "E", "F", "G", "H"]; private readonly static string[] _jieStarChannelLetters = ["A", "B", "C", "D", "E", "F", "G", "H"]; + private readonly static string[] _powerBoxChannelLetters = ["A", "B", "C", "D"]; public static readonly BindableProperty DeviceTypeProperty = BindableProperty.Create(nameof(DeviceType), typeof(DeviceType), typeof(DeviceChannelLabel), default(DeviceType), BindingMode.OneWay, null, OnDeviceChanged); public static readonly BindableProperty ChannelProperty = BindableProperty.Create(nameof(Channel), typeof(int), typeof(DeviceChannelLabel), 0, BindingMode.OneWay, null, OnChannelChanged); @@ -112,6 +113,9 @@ private void SetChannelText() case DeviceType.JieStarSCM8: SetChannelText(_jieStarChannelLetters); break; + case DeviceType.PowerBoxMBattery: + SetChannelText(_powerBoxChannelLetters); + break; default: Text = $"{Channel + 1}"; break; diff --git a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml index dd974e99..f416a459 100644 --- a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml +++ b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml @@ -504,6 +504,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs index 5f1c68c2..6f89614d 100644 --- a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs +++ b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs @@ -195,6 +195,7 @@ private void OnDeviceChanged(Device device) CaDARaceCarSection.IsVisible = deviceType == DeviceType.CaDA_RaceCar; JieStarSCM4Section.IsVisible = deviceType == DeviceType.JieStarSCM4; JieStarSCM8Section.IsVisible = deviceType == DeviceType.JieStarSCM8; + PowerBoxMBatterySection.IsVisible = deviceType == DeviceType.PowerBoxMBattery; } private static void OnSelectedChannelChanged(BindableObject bindable, object oldValue, object newValue) @@ -323,6 +324,8 @@ private void OnSelectedChannelChanged(int selectedChannel) SBrickLightSubchannel1.SelectedChannel = sBrickLightSubchannel; SBrickLightSubchannel2.SelectedChannel = sBrickLightSubchannel; SBrickLightSubchannel3.SelectedChannel = sBrickLightSubchannel; + // PowerBoxMBattery + PowerBoxMBatteryChannel0.SelectedChannel = selectedChannel; } } } \ No newline at end of file diff --git a/BrickController2/BrickController2/UI/Images/powerboxmbattery_image.png b/BrickController2/BrickController2/UI/Images/powerboxmbattery_image.png new file mode 100644 index 0000000000000000000000000000000000000000..023923dfca38b539250ad4244dfe33c04a512d39 GIT binary patch literal 16721 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 literal 0 HcmV?d00001 diff --git a/BrickController2/BrickController2/UI/Images/powerboxmbattery_image_small.png b/BrickController2/BrickController2/UI/Images/powerboxmbattery_image_small.png new file mode 100644 index 0000000000000000000000000000000000000000..023923dfca38b539250ad4244dfe33c04a512d39 GIT binary patch literal 16721 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 literal 0 HcmV?d00001 From 35441f1cc1334000d0065982eb2649139c3c39a6 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 23 Jul 2026 13:59:54 +0200 Subject: [PATCH 02/14] added UnitTest PowerBoxVendorTests --- .../DI/PowerBoxVendorTests.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs new file mode 100644 index 00000000..9ce2ef1b --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs @@ -0,0 +1,51 @@ +using Autofac; +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.PowerBox; +using BrickController2.PlatformServices.BluetoothLE; +using FluentAssertions; +using Moq; +using Xunit; +using PowerBoxVendor = BrickController2.DeviceManagement.PowerBox.PowerBox; + +namespace BrickController2.Tests.DeviceManagement.DI; + +public class PowerBoxVendorTests : VendorTestsBase +{ + private readonly DeviceFactory _deviceFactory; + + public PowerBoxVendorTests() + { + // Act + var container = InitializeContainer().Build(); + + _deviceFactory = container.Resolve(); + } + + protected override ContainerBuilder InitializeContainer() + { + var builder = base.InitializeContainer(); + + // Arrange + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + // execute registration of vendor PowerBox + builder.RegisterModule(); + + return builder; + } + + [Theory] + [InlineData("Device1")] + public void RegisterDevice_PowerBox_MBattery_ReturnedDevice(string address) + { + DeviceType deviceType = DeviceType.PowerBoxMBattery; + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + + var device = _deviceFactory(deviceType, name, address, deviceData, []); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } +} From f0fc4a6e2d125db50ba013b263722f26ffc7c7fa Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sun, 26 Jul 2026 12:54:05 +0200 Subject: [PATCH 03/14] added new device PowerBox A Series --- .../DI/PowerBoxVendorTests.cs | 16 +- .../DeviceManagement/DeviceType.cs | 1 + .../DeviceManagement/PowerBox/PowerBox.cs | 2 + .../PowerBox/PowerBoxASeries.cs | 79 ++++++ .../{PowerBoxBase.cs => PowerBoxBaseByte.cs} | 53 +--- .../PowerBox/PowerBoxBaseNibble.cs | 266 ++++++++++++++++++ .../PowerBox/PowerBoxMBattery.cs | 72 ++++- .../UI/Controls/DeviceChannelLabel.cs | 1 + .../UI/Controls/DeviceChannelSelector.xaml | 21 ++ .../UI/Controls/DeviceChannelSelector.xaml.cs | 13 + .../UI/Images/powerboxaseries_image.png | Bin 0 -> 128072 bytes .../UI/Images/powerboxaseries_image_small.png | Bin 0 -> 16547 bytes 12 files changed, 471 insertions(+), 53 deletions(-) create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs rename BrickController2/BrickController2/DeviceManagement/PowerBox/{PowerBoxBase.cs => PowerBoxBaseByte.cs} (75%) create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs create mode 100644 BrickController2/BrickController2/UI/Images/powerboxaseries_image.png create mode 100644 BrickController2/BrickController2/UI/Images/powerboxaseries_image_small.png diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs index 9ce2ef1b..e9dede30 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs @@ -36,7 +36,7 @@ protected override ContainerBuilder InitializeContainer() } [Theory] - [InlineData("Device1")] + [InlineData("Device")] public void RegisterDevice_PowerBox_MBattery_ReturnedDevice(string address) { DeviceType deviceType = DeviceType.PowerBoxMBattery; @@ -48,4 +48,18 @@ public void RegisterDevice_PowerBox_MBattery_ReturnedDevice(string address) device.Should().NotBeNull(); device.Should().BeOfType(); } + + [Theory] + [InlineData("Device")] + public void RegisterDevice_PowerBox_ASeries_ReturnedDevice(string address) + { + DeviceType deviceType = DeviceType.PowerBoxASeries; + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + + var device = _deviceFactory(deviceType, name, address, deviceData, []); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } } diff --git a/BrickController2/BrickController2/DeviceManagement/DeviceType.cs b/BrickController2/BrickController2/DeviceManagement/DeviceType.cs index 4388d7fb..f19e07dd 100644 --- a/BrickController2/BrickController2/DeviceManagement/DeviceType.cs +++ b/BrickController2/BrickController2/DeviceManagement/DeviceType.cs @@ -27,5 +27,6 @@ public enum DeviceType JieStarSCM4, JieStarSCM8, PowerBoxMBattery, + PowerBoxASeries } } diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs index 50a5cef8..55c15249 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs @@ -21,5 +21,7 @@ protected override void Register(VendorBuilder builder) builder.RegisterDevice() .WithDeviceFactory(PowerBoxMBattery.Device, $"{PowerBoxMBattery.TypeName} Device"); + builder.RegisterDevice() + .WithDeviceFactory(PowerBoxASeries.Device, $"{PowerBoxASeries.TypeName} Device"); } } diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs new file mode 100644 index 00000000..860b895e --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs @@ -0,0 +1,79 @@ +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.Protocols; +using System; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// PowerBox A Series 4 Channels +/// +internal class PowerBoxASeries : PowerBoxBaseNibble, IDeviceType +{ + public const string Device = "Device"; + + /// + /// Telegram to connect to the PowerBox devices + /// This telegram is sent on init and on reconnect conditions matching + /// + private static readonly byte[] Telegram_Connect_Device = [0xA4, 0xFE, 0x19, 0x80, 0x80, 0x80, 0x00, 0x5B]; + + /// + /// Base Telegram for PowerBox devices + /// + private static readonly byte[] Telegram_Base_Device = [0x40, 0xFE, 0x19, 0x00, 0x00, 0x00, 0x00, 0xBF]; + + /// + /// after this timespan and all channel's values equal to zero the connect telegram is sent + /// + private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); + + public PowerBoxASeries(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, powerboxPlatformService, powerboxDeviceManager, Telegram_Connect_Device, Telegram_Base_Device) + { + } + + public static DeviceType Type => DeviceType.PowerBoxASeries; + + public static string TypeName => "PowerBox A Series"; + + public override DeviceType DeviceType => Type; + + /// + /// Gets the number of channels supported by the device. + /// + /// Channel 0..3: real existing channels + /// + /// + public override int NumberOfChannels => 4; + + /// + /// manufacturerId to advertise + /// + protected override ushort ManufacturerId => PowerBoxProtocol.ManufacturerID; + + /// + /// Get or create BluetoothAdvertisingDeviceHandler + /// + /// Instance of BluetoothAdvertisingDeviceHandler + protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDeviceHandler() + { + // PowerBoxASeries needs a BluetoothAdvertiser per module + return new BluetoothAdvertisingDeviceHandler(_bleService, ManufacturerId, TryGetTelegram, PowerBoxASeries.ReconnectTimeSpan); + } + + /// + /// Processes the value for the specified analog channel and returns the processed result. + /// + /// The channel number to process. Valid value is 0. + /// The input value to be processed for the specified channel. + /// A tuple containing the processed value and a flag indicating the success of the operation. + /// Thrown if is not the valid channel number 0. + protected override (byte value, bool flag) ProcessChannelValue(int channelNo, float value) => channelNo switch + { + 0 => SetOutput_AnalogChannel(value), + 1 => SetOutput_AnalogChannel(value), + 2 => SetOutput_AnalogChannel(value), + 3 => SetOutput_AnalogChannel(value), + _ => throw new ArgumentException($"Illegal Argument \"{channelNo}\"", nameof(channelNo)) + }; +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs similarity index 75% rename from BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs rename to BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs index 77948d43..5c3edf34 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBase.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs @@ -6,7 +6,7 @@ namespace BrickController2.DeviceManagement.PowerBox; /// /// PowerBox baseclass /// -internal abstract class PowerBoxBase : BluetoothAdvertisingDevice +internal abstract class PowerBoxBaseByte : BluetoothAdvertisingDevice { /// /// offset to position of first channel in base telegram @@ -34,7 +34,7 @@ internal abstract class PowerBoxBase : BluetoothAdvertisingDevice /// protected readonly float[] _storedValues; - protected PowerBoxBase(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) + protected PowerBoxBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) : base(name, address, deviceData, deviceRepository, bleService) { _telegram_Connect = telegram_Connect; @@ -108,64 +108,17 @@ public override void SetOutput(int channelNo, float value) /// The value to set.param> /// if the byte in the telegram buffer was modified; otherwise, if /// the value remained unchanged. - protected bool SetChannelValue(int byteOffset, byte setValue_byte) + protected virtual bool SetChannelValue(int byteOffset, byte setValue_byte) { lock (_outputLock) { byte originValue_byte = _telegram_Base[byteOffset]; _telegram_Base[byteOffset] = setValue_byte; - _telegram_Base[byteOffset + 1] = setValue_byte; // bytes are duplicated in the telegram return _telegram_Base[byteOffset] != originValue_byte; } } - /// - /// Converts a floating-point value into a byte representation for an analog channel output. - /// - /// The method maps the input value to a byte representation based on predefined ranges for - /// positive, negative, and zero values. The zero value is represented by a specific byte constant. The caller can - /// use the returned boolean to determine if the byte corresponds to the zero value. - /// The floating-point value to be converted. Negative values are mapped to the negative range, positive values are - /// mapped to the positive range, and zero is mapped to a predefined byte. - /// A tuple containing the following: A representing - /// the byte value for the analog channel output. A - /// indicating whether the byte corresponds to the zero value. if the byte represents - /// zero; otherwise, . - protected (byte setValue_Byte, bool zeroSet) SetOutput_AnalogChannel(float value) - { - // value < 0: 7f 6e 5d 4c 3b 2a 19 RANGE_NEG: 0x07 - // value == 0: 00 ZEROVALUE - // value > 0: 91 a2 b3 c4 d5 e6 f7 RANGE_POS: 0x07 - - const int RANGE_POS = 0x07; - const int RANGE_NEG = 0x07; - - const float MIN_NEG_RANGE_THRESHOLD = -1f / RANGE_NEG; // Minimum value for negative range - const float MIN_POS_RANGE_THRESHOLD = 1f / RANGE_POS; // Minimum value for positive range - - const byte ZEROVALUE = 0x00; - - if (value <= MIN_NEG_RANGE_THRESHOLD) - { - byte value_abs = (byte)Math.Min(0x07, -value * RANGE_NEG); - byte setValue_byte = (byte)((value_abs << 4) + value_abs + 8); - - return (setValue_byte, false); - } - else if (value >= MIN_POS_RANGE_THRESHOLD) - { - byte value_abs = (byte)Math.Min(0x07, value * RANGE_POS); - byte setValue_byte = (byte)(((value_abs + 8) << 4) + value_abs); - - return (setValue_byte, false); - } - else - { - return (ZEROVALUE, true); - } - } - /// /// Sets the output value for the specified channel. /// diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs new file mode 100644 index 00000000..f9f7a88a --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs @@ -0,0 +1,266 @@ +using System; +using BrickController2.PlatformServices.BluetoothLE; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// PowerBox baseclass +/// +internal abstract class PowerBoxBaseNibble : BluetoothAdvertisingDevice +{ + /// + /// offset to position of first channel in base telegram + /// + private const int CHANNEL_START_OFFSET = 3; + + /// + /// platform specific PowerBox stuff + /// + protected readonly IPowerBoxPlatformService _powerboxPlatformService; + + /// + /// Telegram to connect to the device + /// This telegram is sent on init and on reconnect conditions matching + /// + protected readonly byte[] _telegram_Connect; + + /// + /// base telegram + /// + protected readonly byte[] _telegram_Base; + + /// + /// array to hold the incoming output values for all channels. + /// + protected readonly float[] _storedValues; + + protected PowerBoxBaseNibble(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) + : base(name, address, deviceData, deviceRepository, bleService) + { + _telegram_Connect = telegram_Connect; + _telegram_Base = telegram_Base; + _powerboxPlatformService = powerboxPlatformService; + _storedValues = new float[NumberOfChannels]; // initialize output values for all channels + + // bytes[1] and [2] of both telegrams can be set to a unique appId + ReadOnlySpan appId = powerboxDeviceManager.GetAppId().Span[..2]; + _telegram_Connect[1] = appId[0]; + _telegram_Connect[2] = appId[1]; + + _telegram_Base[1] = appId[0]; + _telegram_Base[2] = appId[1]; + } + + /// + /// No voltage + /// + public override string BatteryVoltageSign => string.Empty; + + /// + /// Sets the output value for the specified channel. + /// + /// This method updates the output value for the specified channel and ensures the value is + /// within the valid range. If the value changes, the method triggers a notification to indicate that data has been + /// updated. + /// The channel number for which the output value is being set. Must be a valid channel index. + /// The output value to set. The value will be adjusted if it exceeds the allowable range. + public override void SetOutput(int channelNo, float value) + { + CheckChannel(channelNo); + value = CutOutputValue(value); + + // store the incoming value in the stored values array + _storedValues[channelNo] = value; + + lock (_outputLock) + { + // call the channel specific set function + bool valueChanged = SetChannelOutput(channelNo, value); + + // check for change + if (valueChanged) + { + _bluetoothAdvertisingDeviceHandler.NotifyDataChanged(); + } + } + } + + /// + /// Processes the specified channel value and returns a transformed result. + /// + /// The exact transformation logic and conditions for success are determined by the implementing + /// class. + /// The channel number to process. Must be a non-negative integer. + /// The input value associated with the channel to be processed. + /// A tuple containing the processed result: value: A byte + /// representing the transformed value for the specified channel. + /// flag: A boolean indicating whether the value is marked as zero () or + /// not (). + protected abstract (byte value, bool flag) ProcessChannelValue(int channelNo, float value); + + /// + /// Updates a specific nibble of a byte in the telegram buffer and returns whether the value was changed. + /// + /// This method modifies the telegram buffer by updating either the lower or upper nibble of the + /// specified byte. The operation is thread-safe and ensures exclusive access to the buffer during the + /// update. + /// The zero-based index of the byte in the telegram buffer to modify. + /// A value indicating whether the lower nibble of the byte should be updated. to update the + /// lower nibble; to update the upper nibble. + /// The new nibble value to set, represented as a byte (0-15). + /// if the byte in the telegram buffer was modified; otherwise, if + /// the value remained unchanged. + protected bool SetChannelValue(int byteOffset, bool isLowerNibble, byte setValue_nibble) + { + lock (_outputLock) + { + byte originValue_byte = _telegram_Base[byteOffset]; + + byte setValue_byte; + if (isLowerNibble) + { + setValue_byte = (byte)((originValue_byte & 0xF0) + setValue_nibble); + } + else + { + setValue_byte = (byte)((originValue_byte & 0x0F) + (setValue_nibble << 4)); + } + _telegram_Base[byteOffset] = setValue_byte; + return _telegram_Base[byteOffset] != originValue_byte; + } + } + + /// + /// Converts a floating-point value into a nibble representation for an analog channel output. + /// + /// The method maps the input value to a nibble representation based on predefined ranges for + /// positive, negative, and zero values. The zero value is represented by a specific nibble constant. The caller can + /// use the returned boolean to determine if the nibble corresponds to the zero value. + /// The floating-point value to be converted. Negative values are mapped to the negative range, positive values are + /// mapped to the positive range, and zero is mapped to a predefined nibble. + /// A tuple containing the following: A representing + /// the nibble value for the analog channel output. A + /// indicating whether the nibble corresponds to the zero value. if the nibble represents + /// zero; otherwise, . + protected (byte setValue_Nibble, bool zeroSet) SetOutput_AnalogChannel(float value) + { + // MK4: ZEROVALUE_NIBBLE = 0x08, RANGE_POS_OFFSET = 0x08 + // value < 0: 7 6 5 4 3 2 1 RANGE_NEG: 0x07 + // value == 0: 0 8 + // value > 0: 9 A B C D E F RANGE_POS: 0x07 + + const byte RANGE_POS_OFFSET = 0x08; + const int RANGE_POS = 0x07; + const int RANGE_NEG = 0x07; + + const float MIN_NEG_RANGE_THRESHOLD = -1f / RANGE_NEG; // Minimum value for negative range + const float MIN_POS_RANGE_THRESHOLD = 1f / RANGE_POS; // Minimum value for positive range + + const byte ZEROVALUE_NIBBLE = 0x00; + + if (value <= MIN_NEG_RANGE_THRESHOLD) + { + float value_abs = Math.Min(0x07, -value * RANGE_NEG); + byte setValue_nibble = (byte)(0x0F & (byte)value_abs); + + return (setValue_nibble, false); + } + else if (value >= MIN_POS_RANGE_THRESHOLD) + { + float value_abs = Math.Min(0x0F, (value * RANGE_POS) + RANGE_POS_OFFSET); + byte setValue_nibble = (byte)(0x0F & (byte)(value_abs)); + + return (setValue_nibble, false); + } + else + { + return (ZEROVALUE_NIBBLE, true); + } + } + + /// + /// Sets the output value for the specified channel. + /// + /// This method handles both virtual and real channels. For virtual channels, the value is + /// processed and the modification status is returned. For real channels, the method calculates the appropriate + /// byte offset and channel-specific parameters, processes the value, and updates the channel state + /// accordingly. + /// The channel number for which the output value is to be set. Must be a valid channel identifier. + /// The output value to set for the specified channel. The value is processed before being applied. + /// if the channel's output value was modified; otherwise, . + protected bool SetChannelOutput(int channelNo, float value) + { + // real channel + (int byteOffset, bool isLowerNibble) = GetTargetPosition(channelNo); + (byte setValue_nibble, bool zeroSet) = ProcessChannelValue(channelNo, value); + + _bluetoothAdvertisingDeviceHandler.SetChannelState(channelNo, zeroSet); // set global channel state + return SetChannelValue(byteOffset, isLowerNibble, setValue_nibble); + } + + /// + /// This method sets the device to initial state before advertising starts + /// All channels are initialized with zeroValue. + /// + protected override void InitDevice() + { + const float zeroValue = 0.0f; + + for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) + { + _storedValues[channelNo] = zeroValue; // restore stored values to zero + SetChannelOutput(channelNo, zeroValue); // set all channels to zero using the channel specific function + } + } + + /// + /// Disconnects the device and resets the output state of all channels to zero. + /// + /// This method ensures that all channels are set to a zero output state during the disconnection + /// process. It is intended to be called as part of the device's disconnection workflow. + protected override void DisconnectDevice() + { + const float zeroValue = 0.0f; + + for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) + { + // call _bluetoothAdvertisingDeviceHandler.SetChannelState() to set global channel state to zero + SetChannelOutput(channelNo, zeroValue); + } + } + + /// + /// Attempts to retrieve the RF payload for the specified telegram type. + /// + /// This method delegates the retrieval of the RF payload to the underlying platform + /// service. + /// A boolean value indicating the type of telegram to retrieve. to retrieve the connect + /// telegram; to retrieve the base telegram. + /// When this method returns, contains the RF payload as a byte array if the operation succeeds; otherwise, . + /// if the RF payload was successfully retrieved; otherwise, . + protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) + { + if (getConnectTelegram) + { + return _powerboxPlatformService.TryGetRfPayload(_telegram_Connect, out payload); + } + else + { + return _powerboxPlatformService.TryGetRfPayload(_telegram_Base, out payload); + } + } + + /// + /// Calculates the target position of a channel + /// + /// The channel number for which the position is calculated. Must be a non-negative integer. + /// A tuple containing the byte offset and a boolean indicating whether the target position is in the lower nibble. + protected virtual (int byteOffset, bool isLowerNibble) GetTargetPosition(int channelNo) + { + return ( + CHANNEL_START_OFFSET + (channelNo >> 1), // div 2 -> 2 channels per byte + (channelNo & 0x01) == 0x01 + ); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs index f223e7c8..e8ed67ab 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs @@ -5,9 +5,9 @@ namespace BrickController2.DeviceManagement.PowerBox; /// -/// PowerBox 1 Channel +/// PowerBox M Battery with 1 Channel /// -internal class PowerBoxMBattery : PowerBoxBase, IDeviceType +internal class PowerBoxMBattery : PowerBoxBaseByte, IDeviceType { public const string Device = "Device"; @@ -61,6 +61,74 @@ protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDevi return new BluetoothAdvertisingDeviceHandler(_bleService, ManufacturerId, TryGetTelegram, PowerBoxMBattery.ReconnectTimeSpan); } + /// + /// Updates a specific byte in the telegram buffer and returns whether the value was changed. + /// + /// This method modifies the telegram buffer by updating the specified byte. The operation is + /// thread-safe and ensures exclusive access to the buffer during the + /// update. + /// The zero-based index of the byte in the telegram buffer to modify. + /// The value to set.param> + /// if the byte in the telegram buffer was modified; otherwise, if + /// the value remained unchanged. + protected override bool SetChannelValue(int byteOffset, byte setValue_byte) + { + lock (_outputLock) + { + byte originValue_byte = _telegram_Base[byteOffset]; + + _telegram_Base[byteOffset] = setValue_byte; + _telegram_Base[byteOffset + 1] = setValue_byte; // very special: bytes are duplicated in the datagram + return _telegram_Base[byteOffset] != originValue_byte; + } + } + + /// + /// Converts a floating-point value into a byte representation for an analog channel output. + /// + /// The method maps the input value to a byte representation based on predefined ranges for + /// positive, negative, and zero values. The zero value is represented by a specific byte constant. The caller can + /// use the returned boolean to determine if the byte corresponds to the zero value. + /// The floating-point value to be converted. Negative values are mapped to the negative range, positive values are + /// mapped to the positive range, and zero is mapped to a predefined byte. + /// A tuple containing the following: A representing + /// the byte value for the analog channel output. A + /// indicating whether the byte corresponds to the zero value. if the byte represents + /// zero; otherwise, . + protected (byte setValue_Byte, bool zeroSet) SetOutput_AnalogChannel(float value) + { + // value < 0: 7f 6e 5d 4c 3b 2a 19 RANGE_NEG: 0x07 + // value == 0: 00 ZEROVALUE + // value > 0: 91 a2 b3 c4 d5 e6 f7 RANGE_POS: 0x07 + + const int RANGE_POS = 0x07; + const int RANGE_NEG = 0x07; + + const float MIN_NEG_RANGE_THRESHOLD = -1f / RANGE_NEG; // Minimum value for negative range + const float MIN_POS_RANGE_THRESHOLD = 1f / RANGE_POS; // Minimum value for positive range + + const byte ZEROVALUE = 0x00; + + if (value <= MIN_NEG_RANGE_THRESHOLD) + { + byte value_abs = (byte)Math.Min(0x07, -value * RANGE_NEG); + byte setValue_byte = (byte)((value_abs << 4) + value_abs + 8); + + return (setValue_byte, false); + } + else if (value >= MIN_POS_RANGE_THRESHOLD) + { + byte value_abs = (byte)Math.Min(0x07, value * RANGE_POS); + byte setValue_byte = (byte)(((value_abs + 8) << 4) + value_abs); + + return (setValue_byte, false); + } + else + { + return (ZEROVALUE, true); + } + } + /// /// Processes the value for the specified analog channel and returns the processed result. /// diff --git a/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs b/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs index a7b0f1a5..5d9420a8 100644 --- a/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs +++ b/BrickController2/BrickController2/UI/Controls/DeviceChannelLabel.cs @@ -114,6 +114,7 @@ private void SetChannelText() SetChannelText(_jieStarChannelLetters); break; case DeviceType.PowerBoxMBattery: + case DeviceType.PowerBoxASeries: SetChannelText(_powerBoxChannelLetters); break; default: diff --git a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml index f416a459..2d6aaa4e 100644 --- a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml +++ b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml @@ -505,6 +505,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs index 6f89614d..33a5040d 100644 --- a/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs +++ b/BrickController2/BrickController2/UI/Controls/DeviceChannelSelector.xaml.cs @@ -121,6 +121,13 @@ public DeviceChannelSelector() SBrickLightSubchannel1.Command = new SafeCommand(() => UpdateSBrickSubchannel(SBrickProtocol.LIGHT_PORTS_COUNT * 1)); SBrickLightSubchannel2.Command = new SafeCommand(() => UpdateSBrickSubchannel(SBrickProtocol.LIGHT_PORTS_COUNT * 2)); SBrickLightSubchannel3.Command = new SafeCommand(() => UpdateSBrickSubchannel(SBrickProtocol.LIGHT_PORTS_COUNT * 3)); + // PowerBoxMBattery + PowerBoxMBatteryChannel0.Command = new SafeCommand(() => SelectedChannel = 0); + // PowerBoxASeries + PowerBoxASeriesChannel0.Command = new SafeCommand(() => SelectedChannel = 0); + PowerBoxASeriesChannel1.Command = new SafeCommand(() => SelectedChannel = 1); + PowerBoxASeriesChannel2.Command = new SafeCommand(() => SelectedChannel = 2); + PowerBoxASeriesChannel3.Command = new SafeCommand(() => SelectedChannel = 3); void UpdateSBrickPort(int channel) { @@ -195,6 +202,7 @@ private void OnDeviceChanged(Device device) CaDARaceCarSection.IsVisible = deviceType == DeviceType.CaDA_RaceCar; JieStarSCM4Section.IsVisible = deviceType == DeviceType.JieStarSCM4; JieStarSCM8Section.IsVisible = deviceType == DeviceType.JieStarSCM8; + PowerBoxASeriesSection.IsVisible = deviceType == DeviceType.PowerBoxASeries; PowerBoxMBatterySection.IsVisible = deviceType == DeviceType.PowerBoxMBattery; } @@ -326,6 +334,11 @@ private void OnSelectedChannelChanged(int selectedChannel) SBrickLightSubchannel3.SelectedChannel = sBrickLightSubchannel; // PowerBoxMBattery PowerBoxMBatteryChannel0.SelectedChannel = selectedChannel; + // PowerBoxASeries + PowerBoxASeriesChannel0.SelectedChannel = selectedChannel; + PowerBoxASeriesChannel1.SelectedChannel = selectedChannel; + PowerBoxASeriesChannel2.SelectedChannel = selectedChannel; + PowerBoxASeriesChannel3.SelectedChannel = selectedChannel; } } } \ No newline at end of file diff --git a/BrickController2/BrickController2/UI/Images/powerboxaseries_image.png b/BrickController2/BrickController2/UI/Images/powerboxaseries_image.png new file mode 100644 index 0000000000000000000000000000000000000000..80abd5c09650b1c3f6e8f37fd7b2409ee72d3eee GIT binary patch literal 128072 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İ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 literal 0 HcmV?d00001 From c7d7e4aa0ca133fca58c947c247164a7687ed3b2 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sun, 26 Jul 2026 13:13:18 +0200 Subject: [PATCH 04/14] Update README with new supported modules Added "PowerBox MBattery" and "PowerBox A Series" to the list of supported modules in the README.md file for improved documentation accuracy. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4554ccfc..4957abe3 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Cross platform mobile application for controlling your creations using a bluetoo - PFx Brick (lights & Power Functions ports only) - JieStar 4 Channel Smart Creative Module - JieStar 8 Channel Smart Creative Module +- PowerBox MBattery +- PowerBox A Series ## Supported controllers - Generic Bluetooth / USB gamepads From 73f17133068060e517f98ba1d0418a4d9fc8e334 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sat, 1 Aug 2026 13:57:46 +0200 Subject: [PATCH 05/14] Refactor: extract channel reset logic to private method Refactored PowerBoxBaseByte and PowerBoxBaseNibble by moving repeated channel reset logic into a new private method, ResetAllChannelsToZero. Updated InitDevice and DisconnectDevice to use this method, reducing duplication and improving maintainability. --- .../PowerBox/PowerBoxBaseByte.cs | 36 +++++++++---------- .../PowerBox/PowerBoxBaseNibble.cs | 36 +++++++++---------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs index 5c3edf34..d41f2189 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs @@ -143,32 +143,14 @@ protected bool SetChannelOutput(int channelNo, float value) /// This method sets the device to initial state before advertising starts /// All channels are initialized with zeroValue. /// - protected override void InitDevice() - { - const float zeroValue = 0.0f; - - for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) - { - _storedValues[channelNo] = zeroValue; // restore stored values to zero - SetChannelOutput(channelNo, zeroValue); // set all channels to zero using the channel specific function - } - } + protected override void InitDevice() => ResetAllChannelsToZero(); /// /// Disconnects the device and resets the output state of all channels to zero. /// /// This method ensures that all channels are set to a zero output state during the disconnection /// process. It is intended to be called as part of the device's disconnection workflow. - protected override void DisconnectDevice() - { - const float zeroValue = 0.0f; - - for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) - { - // call _bluetoothAdvertisingDeviceHandler.SetChannelState() to set global channel state to zero - SetChannelOutput(channelNo, zeroValue); - } - } + protected override void DisconnectDevice() => ResetAllChannelsToZero(); /// /// Attempts to retrieve the RF payload for the specified telegram type. @@ -201,4 +183,18 @@ protected virtual int GetTargetPosition(int channelNo) { return CHANNEL_START_OFFSET + channelNo; } + + /// + /// Resets the value and the output state of all channels to zero. + /// + private void ResetAllChannelsToZero() + { + const float zeroValue = 0.0f; + + for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) + { + _storedValues[channelNo] = zeroValue; + SetChannelOutput(channelNo, zeroValue); + } + } } diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs index f9f7a88a..0994820f 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs @@ -202,32 +202,14 @@ protected bool SetChannelOutput(int channelNo, float value) /// This method sets the device to initial state before advertising starts /// All channels are initialized with zeroValue. /// - protected override void InitDevice() - { - const float zeroValue = 0.0f; - - for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) - { - _storedValues[channelNo] = zeroValue; // restore stored values to zero - SetChannelOutput(channelNo, zeroValue); // set all channels to zero using the channel specific function - } - } + protected override void InitDevice() => ResetAllChannelsToZero(); /// /// Disconnects the device and resets the output state of all channels to zero. /// /// This method ensures that all channels are set to a zero output state during the disconnection /// process. It is intended to be called as part of the device's disconnection workflow. - protected override void DisconnectDevice() - { - const float zeroValue = 0.0f; - - for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) - { - // call _bluetoothAdvertisingDeviceHandler.SetChannelState() to set global channel state to zero - SetChannelOutput(channelNo, zeroValue); - } - } + protected override void DisconnectDevice() => ResetAllChannelsToZero(); /// /// Attempts to retrieve the RF payload for the specified telegram type. @@ -263,4 +245,18 @@ protected virtual (int byteOffset, bool isLowerNibble) GetTargetPosition(int cha (channelNo & 0x01) == 0x01 ); } + + /// + /// Resets the value and the output state of all channels to zero. + /// + private void ResetAllChannelsToZero() + { + const float zeroValue = 0.0f; + + for (int channelNo = 0; channelNo < NumberOfChannels; channelNo++) + { + _storedValues[channelNo] = zeroValue; + SetChannelOutput(channelNo, zeroValue); + } + } } From 9544ba7b826779628fd277963dbbe21cd0d1832f Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sun, 2 Aug 2026 05:58:12 +0200 Subject: [PATCH 06/14] Added UnitTests PowerBoxDatagramTests for devices MBattery and ASeries --- .../PowerBox/PowerBoxASeriesDatagramTests.cs | 116 ++++++++++++++++++ .../PowerBox/PowerBoxDatagramTestsBase.cs | 37 ++++++ .../PowerBox/PowerBoxDeviceManagerTests.cs | 32 +++++ .../PowerBox/PowerBoxMBatteryDatagramTests.cs | 110 +++++++++++++++++ .../PowerBox/IPowerBoxDeviceManager.cs | 11 ++ .../DeviceManagement/PowerBox/PowerBox.cs | 1 + .../PowerBox/PowerBoxASeries.cs | 7 +- .../PowerBox/PowerBoxBaseByte.cs | 7 +- .../PowerBox/PowerBoxBaseNibble.cs | 7 +- .../PowerBox/PowerBoxDeviceManager.cs | 2 +- .../PowerBox/PowerBoxMBattery.cs | 6 +- 11 files changed, 325 insertions(+), 11 deletions(-) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDatagramTestsBase.cs create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs create mode 100644 BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxDeviceManager.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs new file mode 100644 index 00000000..47fb1289 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs @@ -0,0 +1,116 @@ +using BrickController2.DeviceManagement.PowerBox; +using FluentAssertions; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.PowerBox; + +public sealed class PowerBoxAseriesDatagramTests : PowerBoxDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xa4; + private const byte PayloadIdentifierConnect2 = 0x5b; + private const byte PayloadIdentifierCommand1 = 0x40; + private const byte PayloadIdentifierCommand2 = 0xbf; + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + [Fact] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier() + { + PowerBoxASeries device = new PowerBoxASeries("PowerBoxASeries", PowerBoxASeries.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierConnect1); + payload[7].Should().Be(PayloadIdentifierConnect2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the connect datagram payload for each device address. + /// + [Fact] + public void TryGetTelegram_ConnectDatagram_AppIdentifier() + { + PowerBoxASeries device = new PowerBoxASeries("PowerBoxASeries", PowerBoxASeries.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(AppIdentifier2); + } + + /// + /// This test checks that the payload identifiers are correctly set in the command datagram for each device address. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier() + { + PowerBoxASeries device = new PowerBoxASeries("PowerBoxASeries", PowerBoxASeries.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierCommand1); + payload[7].Should().Be(PayloadIdentifierCommand2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the command datagram payload for each device address. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_AppIdentifier() + { + PowerBoxASeries device = new PowerBoxASeries("PowerBoxASeries", PowerBoxASeries.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(AppIdentifier2); + } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for each device address. + /// + /// The set values for each channel. + /// The expected payload for the command datagram. + [Theory] + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(new float[] { 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf0, 0x00, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 1 maximum, others neutral + [InlineData(new float[] { 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x0f, 0x00, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 2 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0xf0, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 3 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x0f, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 4 maximum, others neutral + [InlineData(new float[] { -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x70, 0x00, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 1 minimum, others neutral + [InlineData(new float[] { 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x07, 0x00, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 2 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x70, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 3 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x07, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 4 minimum, others neutral + [InlineData(new float[] { 9.0f, 9.0f, 9.0f, 9.0f, }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x00, 0x00, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x00, 0x00, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(float[] setValues, byte[] expectedPayload) + { + PowerBoxASeries device = new PowerBoxASeries("PowerBoxASeries", PowerBoxASeries.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + // Set the output values for the device + for (int i = 0; i < setValues.Length; i++) + { + device.SetOutput(i, setValues[i]); + } + + // Get the command datagram payload + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload.Length; i++) + { + payload[i].Should().Be(expectedPayload[i]); + } + } + + /// + /// Tests that setting an illegal channel index throws an ArgumentOutOfRangeException. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel() + { + PowerBoxASeries device = new PowerBoxASeries("PowerBoxASeries", PowerBoxASeries.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDatagramTestsBase.cs new file mode 100644 index 00000000..89d2d6bb --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDatagramTestsBase.cs @@ -0,0 +1,37 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.PowerBox; +using BrickController2.PlatformServices.BluetoothLE; +using Moq; + +namespace BrickController2.Tests.DeviceManagement.PowerBox; + +public abstract class PowerBoxDatagramTestsBase +{ + protected const byte AppIdentifier1 = 0x61; // This is the first byte of an randomly choosen AppIdentifier for UnitTesting + protected const byte AppIdentifier2 = 0x62; // This is the second byte of an randomly choosen AppIdentifier for UnitTesting + + protected static readonly byte[] AppIdentifier = [AppIdentifier1, AppIdentifier2]; + + /// + /// This class is a test implementation of the IPowerBoxPlatformService interface that simulates the behavior of the TryGetRfPayload method for testing purposes. + /// It always returns true and sets the rfPayload to the rawData provided. + /// + protected class TestPowerBoxPlatformService : IPowerBoxPlatformService + { + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) + { + rfPayload = rawData; + return true; // Simulate success + } + } + + protected readonly Mock _bluetoothLEService = new(MockBehavior.Strict); + protected readonly Mock _manager = new(MockBehavior.Strict); + protected readonly TestPowerBoxPlatformService _powerBoxPlatformService = new(); + internal readonly Mock _deviceRepository = new(MockBehavior.Strict); + + protected PowerBoxDatagramTestsBase() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs new file mode 100644 index 00000000..487ae518 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs @@ -0,0 +1,32 @@ +using BrickController2.DeviceManagement.PowerBox; +using BrickController2.UI.Services.AppIdentifier; +using BrickController2.UI.Services.Preferences; +using FluentAssertions; +using Moq; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.PowerBox; + +public class PowerBoxDeviceManagerTests +{ + private readonly IPowerBoxDeviceManager _manager; + private readonly Mock _preferencesService = new(MockBehavior.Strict); + + public PowerBoxDeviceManagerTests() + { + _preferencesService.Setup(x => x.ContainsKey("Identifier", "App")).Returns(true); + _preferencesService.Setup(x => x.Get("Identifier", "", "App")).Returns("YWJj"); + + IAppIdentifierService appIdentifierService = new AppIdentifierService(_preferencesService.Object); + _manager = new PowerBoxDeviceManager(appIdentifierService); + } + + [Fact] + public void AppId_TwoBytesInPreferences_AllBytes() + { + var appId = _manager.GetAppId(); + appId.Length.Should().Be(2); + appId.Span[0].Should().Be(0x61); // 'a' = 0x61 + appId.Span[1].Should().Be(0x62); // 'b' = 0x62 + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs new file mode 100644 index 00000000..d7292db5 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs @@ -0,0 +1,110 @@ +using BrickController2.DeviceManagement.PowerBox; +using FluentAssertions; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.PowerBox; + +public sealed class PowerBoxMBatteryDatagramTests : PowerBoxDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xa4; + private const byte PayloadIdentifierConnect2 = 0x5b; + private const byte PayloadIdentifierCommand1 = 0x40; + private const byte PayloadIdentifierCommand2 = 0xbf; + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + [Fact] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier() + { + PowerBoxMBattery device = new PowerBoxMBattery("PowerBoxMBattery", PowerBoxMBattery.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierConnect1); + payload[7].Should().Be(PayloadIdentifierConnect2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the connect datagram payload for each device address. + /// + /// The address of the device to test. + [Fact] + public void TryGetTelegram_ConnectDatagram_AppIdentifier() + { + PowerBoxMBattery device = new PowerBoxMBattery("PowerBoxMBattery", PowerBoxMBattery.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(AppIdentifier2); + } + + /// + /// This test checks that the payload identifiers are correctly set in the command datagram for each device address. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier() + { + PowerBoxMBattery device = new PowerBoxMBattery("PowerBoxMBattery", PowerBoxMBattery.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierCommand1); + payload[7].Should().Be(PayloadIdentifierCommand2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the command datagram payload for each device address. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_AppIdentifier() + { + PowerBoxMBattery device = new PowerBoxMBattery("PowerBoxMBattery", PowerBoxMBattery.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(AppIdentifier2); + } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for each device address. + /// + /// The set values for each channel. + /// The expected payload for the command datagram. + [Theory] + [InlineData(new float[] { 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(new float[] { 1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf7, 0xf7, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 1 maximum, others neutral + [InlineData(new float[] { -1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x7f, 0x7f, 0x00, 0x00, PayloadIdentifierCommand2 })] // channel 1 minimum, others neutral + [InlineData(new float[] { 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf7, 0xf7, 0x00, 0x00, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + public void TryGetTelegram_CommandDatagram_Payload(float[] setValues, byte[] expectedPayload) + { + PowerBoxMBattery device = new PowerBoxMBattery("PowerBoxMBattery", PowerBoxMBattery.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + // Set the output values for the device + for (int i = 0; i < setValues.Length; i++) + { + device.SetOutput(i, setValues[i]); + } + + // Get the command datagram payload + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload.Length; i++) + { + payload[i].Should().Be(expectedPayload[i]); + } + } + + /// + /// Tests that setting an illegal channel index throws an ArgumentOutOfRangeException. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel() + { + PowerBoxMBattery device = new PowerBoxMBattery("PowerBoxMBattery", PowerBoxMBattery.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _powerBoxPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxDeviceManager.cs new file mode 100644 index 00000000..6970f559 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/IPowerBoxDeviceManager.cs @@ -0,0 +1,11 @@ +using System; + +namespace BrickController2.DeviceManagement.PowerBox; + +/// +/// Interface for PowerBoxDeviceManager. +/// +public interface IPowerBoxDeviceManager +{ + ReadOnlyMemory GetAppId(); +} diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs index 55c15249..9a39eb1e 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs @@ -15,6 +15,7 @@ protected override void Register(VendorBuilder builder) { // device manager builder.ContainerBuilder.RegisterType() + .As() .SingleInstance(); // manually added devices diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs index 860b895e..eac9960a 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs @@ -15,21 +15,22 @@ internal class PowerBoxASeries : PowerBoxBaseNibble, IDeviceType - private static readonly byte[] Telegram_Connect_Device = [0xA4, 0xFE, 0x19, 0x80, 0x80, 0x80, 0x00, 0x5B]; + private static readonly byte[] Telegram_Connect_Device = [0xa4, 0xfe, 0x19, 0x80, 0x80, 0x80, 0x00, 0x5b]; /// /// Base Telegram for PowerBox devices /// - private static readonly byte[] Telegram_Base_Device = [0x40, 0xFE, 0x19, 0x00, 0x00, 0x00, 0x00, 0xBF]; + private static readonly byte[] Telegram_Base_Device = [0x40, 0xfe, 0x19, 0x00, 0x00, 0x00, 0x00, 0xbf]; /// /// after this timespan and all channel's values equal to zero the connect telegram is sent /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public PowerBoxASeries(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager) + public PowerBoxASeries(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, IPowerBoxDeviceManager powerboxDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, powerboxPlatformService, powerboxDeviceManager, Telegram_Connect_Device, Telegram_Base_Device) { + tozero } public static DeviceType Type => DeviceType.PowerBoxASeries; diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs index d41f2189..959e3f05 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseByte.cs @@ -34,7 +34,7 @@ internal abstract class PowerBoxBaseByte : BluetoothAdvertisingDevice /// protected readonly float[] _storedValues; - protected PowerBoxBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) + protected PowerBoxBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, IPowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) : base(name, address, deviceData, deviceRepository, bleService) { _telegram_Connect = telegram_Connect; @@ -49,6 +49,9 @@ protected PowerBoxBaseByte(string name, string address, byte[] deviceData, IDevi _telegram_Base[1] = appId[0]; _telegram_Base[2] = appId[1]; + + // initialize all channels in _telegram_Base and _storedValues to zero value + InitDevice(); } /// @@ -162,7 +165,7 @@ protected bool SetChannelOutput(int channelNo, float value) /// When this method returns, contains the RF payload as a byte array if the operation succeeds; otherwise, . /// if the RF payload was successfully retrieved; otherwise, . - protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) + protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) { if (getConnectTelegram) { diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs index 0994820f..db4eb850 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxBaseNibble.cs @@ -34,7 +34,7 @@ internal abstract class PowerBoxBaseNibble : BluetoothAdvertisingDevice /// protected readonly float[] _storedValues; - protected PowerBoxBaseNibble(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) + protected PowerBoxBaseNibble(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, IPowerBoxDeviceManager powerboxDeviceManager, byte[] telegram_Connect, byte[] telegram_Base) : base(name, address, deviceData, deviceRepository, bleService) { _telegram_Connect = telegram_Connect; @@ -49,6 +49,9 @@ protected PowerBoxBaseNibble(string name, string address, byte[] deviceData, IDe _telegram_Base[1] = appId[0]; _telegram_Base[2] = appId[1]; + + // initialize all channels in _telegram_Base and _storedValues to zero value + InitDevice(); } /// @@ -221,7 +224,7 @@ protected bool SetChannelOutput(int channelNo, float value) /// When this method returns, contains the RF payload as a byte array if the operation succeeds; otherwise, . /// if the RF payload was successfully retrieved; otherwise, . - protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) + protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) { if (getConnectTelegram) { diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs index 6820a0a2..87d71d65 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxDeviceManager.cs @@ -6,7 +6,7 @@ namespace BrickController2.DeviceManagement.PowerBox; /// /// Manager for PowerBox devices /// -public class PowerBoxDeviceManager +public class PowerBoxDeviceManager : IPowerBoxDeviceManager { private const int AppIdentifierLength = 2; // PowerBox protocol defines 2 bytes for the app identifier diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs index e8ed67ab..3502ff03 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs @@ -15,19 +15,19 @@ internal class PowerBoxMBattery : PowerBoxBaseByte, IDeviceType - private static readonly byte[] Telegram_Connect_Device = [0xA4, 0xFE, 0x19, 0x80, 0x80, 0x80, 0x00, 0x5B]; + private static readonly byte[] Telegram_Connect_Device = [0xa4, 0xfe, 0x19, 0x80, 0x80, 0x80, 0x00, 0x5b]; /// /// Base Telegram for PowerBox devices /// - private static readonly byte[] Telegram_Base_Device = [0x40, 0xFE, 0x19, 0x00, 0x00, 0x00, 0x00, 0xBF]; + private static readonly byte[] Telegram_Base_Device = [0x40, 0xfe, 0x19, 0x00, 0x00, 0x00, 0x00, 0xbf]; /// /// after this timespan and all channel's values equal to zero the connect telegram is sent /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public PowerBoxMBattery(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, PowerBoxDeviceManager powerboxDeviceManager) + public PowerBoxMBattery(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IPowerBoxPlatformService powerboxPlatformService, IPowerBoxDeviceManager powerboxDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, powerboxPlatformService, powerboxDeviceManager, Telegram_Connect_Device, Telegram_Base_Device) { } From da7d5d78de351226a6b873c493c48bf92d1f01d9 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sun, 2 Aug 2026 12:59:04 +0200 Subject: [PATCH 07/14] Buildfix --- .../DeviceManagement/PowerBox/PowerBoxASeries.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs index eac9960a..a027b8dc 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs @@ -30,7 +30,6 @@ internal class PowerBoxASeries : PowerBoxBaseNibble, IDeviceType DeviceType.PowerBoxASeries; From cc84bee0044ada6e567ec1123ccaca453b2b308a Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sun, 2 Aug 2026 13:02:15 +0200 Subject: [PATCH 08/14] Improve change tracking for duplicated bytes in array Updated logic to compare both bytes at byteOffset and byteOffset + 1 in the _telegram_Base array. The method now returns true if either byte changes, ensuring accurate detection of modifications to duplicated bytes. --- .../DeviceManagement/PowerBox/PowerBoxMBattery.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs index 3502ff03..f26978a7 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs @@ -75,11 +75,14 @@ protected override bool SetChannelValue(int byteOffset, byte setValue_byte) { lock (_outputLock) { - byte originValue_byte = _telegram_Base[byteOffset]; + byte originValue1 = _telegram_Base[byteOffset]; + byte originValue2 = _telegram_Base[byteOffset + 1]; _telegram_Base[byteOffset] = setValue_byte; _telegram_Base[byteOffset + 1] = setValue_byte; // very special: bytes are duplicated in the datagram - return _telegram_Base[byteOffset] != originValue_byte; + + return _telegram_Base[byteOffset] != originValue1 || + _telegram_Base[byteOffset + 1] != originValue2; } } From 2e00cfe30956f88e58606b903516b0152c58df36 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sun, 2 Aug 2026 13:06:27 +0200 Subject: [PATCH 09/14] Update XML docs for ProcessChannelValue parameter/exception Updated to specify valid values 0..3. Clarified to cover any invalid channel number, not just when not 0. --- .../DeviceManagement/PowerBox/PowerBoxASeries.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs index a027b8dc..aa65dcf0 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxASeries.cs @@ -64,10 +64,10 @@ protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDevi /// /// Processes the value for the specified analog channel and returns the processed result. /// - /// The channel number to process. Valid value is 0. + /// The channel number to process. Valid values are 0..3. /// The input value to be processed for the specified channel. /// A tuple containing the processed value and a flag indicating the success of the operation. - /// Thrown if is not the valid channel number 0. + /// Thrown if is not a valid channel number. protected override (byte value, bool flag) ProcessChannelValue(int channelNo, float value) => channelNo switch { 0 => SetOutput_AnalogChannel(value), From 253ca0c87abeb679f19ab394a9e82b56e083b3f3 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 4 Aug 2026 05:30:15 +0200 Subject: [PATCH 10/14] Fix XML doc typo and improve tag formatting Corrected the tag typo and enhanced the tag formatting for clarity and consistency in documentation comments. --- .../DeviceManagement/PowerBox/PowerBoxMBattery.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs index f26978a7..98ffec42 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBoxMBattery.cs @@ -68,8 +68,8 @@ protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDevi /// thread-safe and ensures exclusive access to the buffer during the /// update. /// The zero-based index of the byte in the telegram buffer to modify. - /// The value to set.param> - /// if the byte in the telegram buffer was modified; otherwise, if + /// The value to set. + /// if the byte in the telegram buffer was modified; otherwise, if /// the value remained unchanged. protected override bool SetChannelValue(int byteOffset, byte setValue_byte) { From a307705b176e2775e337b68a8d8c76b98dd4b6f9 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 4 Aug 2026 05:58:48 +0200 Subject: [PATCH 11/14] Associate image with PowerBoxMBattery; remove old image Updated device registration to use "powerboxmbattery_image.png" via .WithImage method. Removed obsolete "powerboxmbattery_image_small.png" from the project. --- .../DeviceManagement/PowerBox/PowerBox.cs | 3 ++- .../UI/Images/powerboxmbattery_image_small.png | Bin 16721 -> 0 bytes 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 BrickController2/BrickController2/UI/Images/powerboxmbattery_image_small.png diff --git a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs index 9a39eb1e..f97522d9 100644 --- a/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs +++ b/BrickController2/BrickController2/DeviceManagement/PowerBox/PowerBox.cs @@ -20,7 +20,8 @@ protected override void Register(VendorBuilder builder) // manually added devices builder.RegisterDevice() - .WithDeviceFactory(PowerBoxMBattery.Device, $"{PowerBoxMBattery.TypeName} Device"); + .WithDeviceFactory(PowerBoxMBattery.Device, $"{PowerBoxMBattery.TypeName} Device") + .WithImage("powerboxmbattery_image.png"); builder.RegisterDevice() .WithDeviceFactory(PowerBoxASeries.Device, $"{PowerBoxASeries.TypeName} Device"); diff --git a/BrickController2/BrickController2/UI/Images/powerboxmbattery_image_small.png b/BrickController2/BrickController2/UI/Images/powerboxmbattery_image_small.png deleted file mode 100644 index 023923dfca38b539250ad4244dfe33c04a512d39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16721 zcmY+s1ymbd*fok1JV+t9TX75S6evYXN-6H{?(XhVpg5G5Qrz9$X>qp##i3Y({L}A# z>%aGAt&mAFnK^Ujoafok-un>!UR41HlME990RiW&q8to(4FaCdAT;1F zI@)s@n>m`8b9&i30ed4Lh)H@m8JpUgyV01KTUtAaGoQD2Gt*d`i8Fr^P~lc_k~O!o zR`hW(|KOvlVd`URDs09qDS?A2<|P6g!QR}>n8wTA&cRj0OPub14lDv}|NEGWPD0GZ z%t8bvC;z{D0Pn=Zfz!FVQe8_VrC}5VPec@#=*m5X3Sx1 z#>c~9#>ZzSD8M7cYr$he_rIriv$ptu&+p*+zuf?I2-m-FxOh0Z|26sFwuqXGwK>p= zf2Wk-5&NI-|9_u}as6w^|EFOR|Ia7DwE|oJw`cx!)Bo*Va|fVHJ%PSsPy398fFSwd zt(>%mm%(YL_ZNKy_j7pVx6ZpPwdqO+oXXLRJ@ux`0jsYadP59J=rpHJY= zje{=|zcUzh(U6~y>gWAc&pRtTDnHtOYqaU-^Fo{6Dhs`Pd_I4XuGADLolDWy(c+Gc z#j>!ro=sDF_f^k;_uT)S`qBAZKutjIoMd|IahLMkYqfpjuIuIbu2G4U79|0uI4pe( ziqrO}g2j}=js|WiGb?p(Bg;V3+&v${Hvd*Gc5W9_!wf9E!7?-@HE}yaad8e_O4fhUHXbwa@76sx`^lkpEhWo#;3I@m232LZXn6&E zbqFFF$H@g2E%y*jm=q==odpVQkD}_-6jjI0m@TK)#@s{j$eESwgSB-uH+C|t$ z1us^)xIfODwpYXep`^674i;w`I`0o{e3*Jw(eMY`$NBM|XBS;1O+%4U9fYznbs?um z4kwk08eXiD$(v%SAdrmcSpk7iWz==p)y`V><|aE2ayh2fR=9){C`F;Raily$1!-1< z6ISuuLDAf8vVR=3WIUq(ri0%F-465RwiASff-#3LA=-Q%2_(8=g|Pw2wGG_l3*niH60b9b~{2 zhTuNahl5!@cUaNM$;ppvYkG|v|7F{hDi<#!n4#LG#oW(bPY zb&$(|z=_PLn%&QKvmC)Fn57gY8i!gTIJmew7FQJ>JC_{khBR>r302;MYzCM4771!u z56d6;byZ5zTB<^imkOgA*tSCF!125JxO3`T^3v3FF(bN%$UFUp) zg2Uy@q}QS6f#+2lw%eJqNG**zgVma2a>i0%_(=l&B}F7R2vj^0b6+(ZwUd>ubt?m6 zAea@XXoQY@l8v^VU+B< zmmB!=rup6J^F?eBtlVsDpI7>jK5WQcpJKQdzo0)#v*JxjCHyPOLRscQ%2^&>B%T_q zdhu=94`pRL1lpvzm>1j=_WBtVX~HmR5D6>8f6S8IVKr1TSM9K|D&_%^uFQut7j;;& zYS}KgTUnxvA|3LPU6udrv^cV!|IPd&QS9LJwEvmwrbDAn5YA`T(j)>pO8htYhoITh zdh$>h@4^8R{Kn;7*8ba?XV)0Jq$7_h&uq`*x2e~hmRDoQ*}=RfSNZd@ib_bmT8vyRw&$QA53 z8tQ(yC{z9Nxln7Qn1Vc|ktv%JxG8;0k$eMqtlu}z~9rPVjsx^6#42auE+0l1ApA~~b z_$W^3sanmoW1qv(8xEJe_>})m<25!8PV%IAGHhs}nhDf*+d9hq2X{C%{JeQ4*+AOo zPk@A|BI~7IX4(O5DFaA0gvJVfxjv8yo7>!+9vJ~;DWe@KhB(NJkcib9yrf-Jd3GMZ zwEg{EQ60F?X81sTeDfNny&EK6a?ddH$je2!5KE*1`JjEQ zdHnN(G3F!cWwj+OVj2uVyfy+i<=Hz=K_cH#_etKCJnu|=vFFIO?M{ph@%b?E<*<|b zIj0+b@w*ewx~ul`N9~2WQsBlXH?#Y3Kae%ds6PV7tX^C6y$mu_QxJVzZ5yxicuBIN zF}-^<=3&&{Mn3Vl{{56xx)LK3Gczd}nTe56k{;)(yFp0U{cRafgrb5_LQKw)7A0{; za99o9DnWa{-Z~g5O~b5CyJEGFGFNVaf0XLpWm*`pA0-lKBTS@s&O>-bq0fB1hV2RjnYgFgg0&0B!xXHs# zGAu^p^_$n}b>Hk!Wp3LFJHbV(_F)cR7j=yItxV~^c&>FGw_B1Gl~MTKo?uXWZ6SWF zUPKf{cpFr#$4Q8SSan?Iur301_(GF8PMaR@$9P6=MMp>zKY*`Ug2YzU$kk5@p&=az zQ>-X1MvCJBVL;%bK}iIQ&bu=wE@-M}Y`d1KWj|UHOyBq^H>A5@X7B|?;rVu-1t2T3 za?7s@#Iw+03mBWt7^5JeEqoW>{UOeHzlAR8_2a8b&T}Gc)M8wT^-?U+O+=+pr&O~d zIgB$kQfaMnc2>Q~el_C2TPVRitjWDv+DeuH$-fSJCN_P*hAS*ecE~v+X*Qb%)b_kV zn*k%3ewRFD2JX{&TCbnaYww^gdg=_9rrB|I`x4GjO37u4txhzgAP0%C!hs1{nUY== z=eHbHs|C)LAqcL!{dievGKD*ytvBNp3#MDOi0ibqorY5=p$avNQ@p8Aof6L~MK0l3R|pv%Da@n8MxoO zE>D^&iM|d?B_CQG08{RG1h@_LLUue-rzx#pp-7*+mHRBjg)c=O3=QGNOqSto3iz-{ zD1q8?+O(RWtg5PNWy+1D6RbA&Zrj^B}z&=NMevfYe3KF#xdK*ifu zDp9WXFcZTKC1j!n^cu~^v@hNq&+HI{3_=N7A%bt>#g0Y7a;A+SQX! z=W|GSc=&A)>mqIHw0gz-e~aW0WSB72p)zg$Lz{xan3+W`uCWwGD1Y{LvUHPUcTz)U zZ+e}7JopzYkUuLKbHNhAgU_A*BIOz}ZX)q8T&1^hwP9+1{wQJ3JWa6SOX8oeFT%SL z&*2#urbA#Eqo^o4YTB^I$~7WbtDL$V811qD<#FIa9no{s|L!SXe)pR{{MI_(P6`6wNd3 z_p^vir$P{YFiO%wd7v_ua0g}^7Xy>_rG7Mz9)eC}#LrdvdJa2!z8u#=&UeIMuq!O>=nPbva8bEo^ z&dE8anw3#!4?%>mldMVyZR{y(Nf2s=aFdbp#d9y9DVELYdGDv2@sf6=neusNiWfNu zK6qg#>19s%C|dZHRdO7J7}zYeN9c&0$C}M z(sEYPVVOZPN!g+}t)gi(ZqST#RbCB>KuCNJ(-J>V@bPQC#i8R;Zu=EvOEZLWq6CjCyaF8*HRZ3TT-mCMx1G?NP9XM|v^Gv5tm+WIY2)F5cXbIlfrz!SB zbVi3T93L&TXX`&9O7*V4`n zx(rRA@G69{?h4`dBg+EmD9Kqvg}kRh|HpjN9++w1oboj6P$x&L+7pBe&aScF!!WK7 zKC#QBL1Ef3l(NMcN@7RV6#=23wQJJNWD5xq8;6A+@3G(}{PDn$fGP3&8~2gX;gd!01#hLN^Ur&wDSF4)NIyAw%tpM>D}#y@m(&0 zjHWM+Jlxy}XiD!Gf2mFa=OwA;Wg_SsI;A9wM@v@<|4?7Q=5Z`YdU#Hssx-T}7R>zRU9an>=quuWBS(WwM;#6V^-E zE*EVPu@&`jFgG_5rfkPcDD`tJ6f1C#p8BCIk~#oF`#nEvU?)}Hwvnt97HvA5pkA)w zWHzy_Kgpyej_p`8H22%*8bdm$mw@r%NC?+<;X5q|N%f16bSaEBb+V{&Yh+KI%4cEkdU2+P04A!d2`gWpwZ{@(7(ALlMtxIKkXL;m2P_hUhHCw$rDp;bo#MOv!U zplsQW;El0Z6Imr;qe+&!QoGL~OE_ZR($ld8SS$XXCXR^>`T1d}o57Z>gh7!K|L5x# zCYeZyN=@XbQbyqS3Lxo<`67er)X5dM9XFBnv$V7nmYC1Ex%&grgki=@dG|=~ypD^O zcilj2WThg=sbUhE?a=fqh;Ds^Orcb(j=75q0Z=sUy9njIBRXClfrPLW&D!uvN^a(< z%YZVJ%sIObVFrJSGHb5Cu|2d0-h;}B53+J{4L3d~SBROSduS@~=coWTOnSzKP@L9^ zmy|+bFcMiSUoz_HDIL_!dx;YTeml|~xc!3&hLrqY%#wUvlA`shHD3>o;Mzr?O=bDK zjb=B<)@|?Fn>R~Y7aPMkzEh{Z_h&SrhK)cPn8jaYd$q0gK=E-m%y{Ys;XMf64#mE` z`C-ToWBv45%p+~^ilEgm*sd!`p1>w)j5c?iPoyd1h8)Tx#4l4%dXXAQT^pUIgOE~@ z?=FChEE^_bvipmJDCUVm#4oJW1%nb%J|y`xfMME{4o8I4E-EBRr&y*^yFXE9p@0uR z8DlmKQYamqd8ueF9%kAZqiHoAf7lX;QTvHmOe?xL7_|Qymr6*gQE$ju4{6f}Jz*D2 zDqSLNbhK;{6uRdFI1 zuS6}?@H=&O+?xFmd%qLHd^3lDWWpamf^wImr&Rw42F-rukoMn96py(7a<8~@vEz;u z*L(M5?j59toh$uvpte5n^8|aBT(AJ94UC2sZ0y8fH|Gwq^0Y}-JH8-&t_2FofG@*T zzHu(C6Z)R(gL``nJy?(FPjkr?HHzpo=`Rv)+s#|+X{ zTBN2js}Z0ejyYe5;Z5Isf1Q$|$2s2SZmZqy!BKTQYu5}X@ce-%{ znyC{-rkjXtpdod-O}b0DT>Wx`bdu0tMX{bSbt0@c5s{Ij2Uk{h%IyN!-4lR~RvwV@#B9v12&H-af?*|JSjahOb$*Yu?iSl7vA_(*B6>e8&@}OFXzr{u+1FaVe#M22Aq`%}{>8Vg9A@}d zz^%V+d%Svje~z8wOP}LBSbo>rrcl^|KEBLWso3AYf3^6UA}(urV&05@ULO!#85)>oTrk2`q!n`60~v!&Jkg_D zZdWXm*?vJFXT^>+iqIVCZ$hrD2_Q^kSF-vn_GM)>@I420xB18ZEGMQNN%i{xMF~SA zBdf5T9`M}gR%FQYv-)53A$%{Cwhz+vbjToMck`la(TLn9*05I(^ER(c8z@m0OOv>g zCij!<9eay3D|q~OP>h$}Cb{X`&fMJGsJ~7Z^4S9cyDruvvM}1GO}SHCwTjWx(*t-* zorUOPhjD`?=7b5?*{NJSk1g)l#+5b8d<7B{PWR&nGbkj;qCy%RvFAdn$82CNYZ}Hx zCsUl9Ak|91fTAwd@37wYqzuDC3R_~s5&Nyu*_D8cvYUznVDOg zJtbT_hOeo@%4iKGLkUQ2v>je<@D_(b;+kdy2v>?6GTV_4S?{1p9n~6d zu83W+BD+BwR--a(g`)Wm9+v#DHj4PLfke&6&xaLup3g>9hft9Fo}mR;Yqa zW46FoO|OvzBf?YEc)mAPeM_P56X+aiEmeWRqz7De)4wH#Yzrl@c-U zM6DD+de6Ij^Ls7I(Kw4KnZ4?G&g!RdfL}~j7U}C(?S0m9INTU^wek{%a!TmKwW6_Z z&ACew>Mbr8zmAY9u0#~z@G4gV#h^~#JJtIxyro#4h?@O+<5Lj|R_l3kd+!~%1VA3Q zIP;F!_>A58>*`wCWjN6q(iqkx%PlsB>ar03_tAEOYhv{qmHm5=hF7@1o z4A8d(e%g9^dozWh6O*|%3ctf@u(*dHI+aY)R#}MOOG5XNEeQH-RUFOFeh@zn=~#*<+%fRE64{nMuL_RXlW&@R3;ojtwzqy=H93L)1X z7Lb~*9>mgJ!d*%y{`~o4O}p2$BBIbzJK_K(|03iZQml|z*U%tWSDYC?w=BUF{$II9 zwoI3l2{$H#(e-^Xx7={D#Fn+%k4cj{f*+|iYJoeR#W}dAuT5u@9Wcx) zHEjW+(@OVz^x&-j&Od=#Qnh}W0o%Sy$Hgr&g^=A8r4pk!NwYqj2W`>0@`jq+li5<0 zbFuk!a|)XlC1A^SXMi6FHCFsNl$q+7I$dgwI{gsc^3>51R$}-F$<=*8tWs&+L~c5g zoSCj%geD{8^7>l|77I2swrQ!56qnf@=~!W)*=gOov;%L0S}nh>1rMo|L&MkR7?3xYU*BCOKZ?hE55Nm4 z=tMK9UR<5)S?G5kP;v3Uvt(tMw5YGd{5JpEcCk`L^{|aIK@qe4<4XC%hY2oDm?Jbg z4$V00ovGD>gfY?fS20)?c9tITu(Q=)j-18++PMPWqYA97RPp<>93Qh9jZ4?!|52zJ7#9e8LH)XcGsqzk-r^WA_F1DnL zSax=)Uxe@^?r8`K3Ej^1Jzaj8eJ9r8{~h1-Td~GYDm)!zOf(y#-)KWojbF^@ zS#R??_wA3(O*M&p1>5%e&nk@xK1V*d++J1sGfJ5HZI^Buh?b#y+WcQn*WHUJ5uzoz z!t_ZbowlS_*$_k#U^>MdtfanwW)+0JuNay)eApzPFB6xW?8%i_Lud+S8&HWkHiz*} z(9oi@wXLCyn^>$X&L?t=sKMq)Ii5-VuHqp*XrCWxmN6NLuPfGGCb%J{ce}b6lGOu= zC=(o<7c+U0_A&W-RLDV87)esLlR%PN>hu-F!ooT|FHK7uHwQ$(L6hVop?Abv?+pJE zRBFa@<*6dBvc} z5C?%9i+*aRR|3GrK zs7sC+0qICbz2|J+xD`LUpXK~&p~@>HGGtOdF>(m8=IXER{N-MK*g45tA!VDh|Dk#j zDD=Y$E3rWRNkHrBT3QqdDzW-&C_zdVCiTlDI+eWK+%r?NcVanJm1%WD#*KA5L72w7 zNJC3{0p$z zt83nOiQt4->No6Zq6gl=BYneWj*7RUYi8 zjR0miQo`<*(1QI3rAvTz%K&cJTsgmzI~xBb0}$%~a;WFLB$6@8O|E#B#_rf(bk*1M z!0R;NDUjE>cMQkJ!}4Mti?`*Kes^N)QOAe|MXP*8C_S|roC$da-97i8=9OC zYYS4_v=imv-%FmlWjpqGl(!^%EgkdB=&gGFO<*^_#i%i{!H`slt6}C zP%sj<5JN#qYV7Qstj9T=EnvGck$ht87)P5quKhL2->-~~|775d_=`k+3KDKHJvj+C z4L*yp;o?fI4hNz+-laey{qv{rc<=A>hZ_OU`~|@yLC}y9kjJ_&b#vfLSgM-G!0S;A%0yw?SU_n-Hmt82){q%|NXfw?tAMrOzFuj zA~Htlxz_vq1ou93&3{+;zCh#kGGThrdzPf>13<{!S*kT{(Ba_hesm!wAz9rw1+ww! zYIAX#Y+zvEDm|DNw>PFGNP6V9&S|Hg`IHDAme{S zdiimRXDMi!`3@m6`ewIehb+PXBWcU-%-6t(fvmp|7VQ`a7oC{q+G_Ue>v_+*Xe}U% zjakTmj2sG)^CexCAEkuauk_(LbU&$e{n_X8zs;At$;$D`L3lsyyK(*exE)E&gne3f z23(48z@rd4F2xpLZZHSpJWI@fniT-Y&>SkhY?o19&I$l~R^r&x&%wJwcI$1gC`nzG zzu3zyUsT5Qw?LAVQYOuMXPohOUEzQK&a9k4n|$vaBp$c%T!DDK)yfH{_KoX=-#T9= z#Ac93gqZi{)YEF*k+M#k_pe;u(k zSf#A(;9|R)Hais(#(SKP1P}<*dwuX^Av>9~=1*i6l(ivx#sd@j6yIbR{j&)IqLf|INgI}_I=i64;&>0Jz zb4d~GTb+|laKS4Y?8o)Zm&xM{0UxxH;9$fKp*|?L(TBPZtnEOxpK5$}wr(#Yuijvn zfkE2>SQVDLOv&TrL+KR~0MPx4C(tHuls>hL2XP5=!R+!axyrcXtQEf#WoC3V##!qf z)$REvp4L}wl|3Dp__L>F7|$&ktrGbt*M|g9qL1s{&r9@BGSfl#gopE4Xce ztQ7g*2s)+HHmYvRuayd~Dc34lVFFQ0dH`^?FLwt@s{hQ@Fo~C9w5Kv5TIuLh7U^H8 z)Q#?o3Va++G>)reP`W)rKBi^Bz;)2$`_JCt(iSy5>LKNg?4E1}ONQ8%D;)$%BK8Av6rRW86@vsz^EzLh-xzbupM3 z8+~$@Q|sB;*$YBJgrsS1UJD;TU1tS8O<4eq{6{n^6(K^01BxB--qsNPXItc$6jVCt0LX{+=__8k(b6et2B(h{7jd z&)sd5*@#Gs3d}KO%KRb?9h-`AWv21!XUCUdwZPl2ws|i_2hpNuExT~>PA@%B5V>Q= z?3%}_y**mX%QM`xV($0GKflU%?Rqpp8+9Qc$zzppft$As=D%)eZf-tZjEtCrXr-5M z3eMW^5Cg7KBg-{y_1tC|;}YU{yF9)K6x*(Bn-%1;{Ywf`fs&5*r<+YX%FFha@lEY+ zUtz2sXKSNtCltW-Z~F9;pR9D&zzQ61L1rZhx7mhd&=q z5kQB)Z^&3n%#}^~ipVR2&R;HYhlYke8)`B9T&imHetr`G)8(Ba zSrc7jUGtOSu!+aJ=f^%s7(D&{RDaZmlX#t!h=*|zLede0#nqAI(yAg8zwnVmNNPLVMyCug^( z7x}(xy`SoP!n7H$2OCHv03j9D0cuffnfnU)}6o-MDQ(2)_sO_57nBx+y6r%)GolD^UBV z<%)XpN;vqNEV_#tudtZReE6?m46oZa4c?{O9~<)>!-8ev%v5(!=M*7e>&KO@{W%@B ztY~q(fB(WI;qYoxP|B^nHV{=!3;}N=KH6V&2pN@`-6Vv>(2r-j>)8jFT4dtlf;&^s zWz>dhg)jsRm3stSVe{uKYcsIWc=&bY3 zpoql;2;Try17w9(z|!EXPg2%3VjkJmzcNIWxW=dEjyME$+)jBX@WUj^=LLf2&_za1 zsham?ch)~~&Z+?{lcZ$MLL9qSCpL_@`L0W{gy`@F7K4D(8;;W*-qv4&g$+wZ)3 zvGOv}Oz{1lXQ@gh;q{VyO3RPkfM4T>9h8)mwD3JkL#mjWc>)`=qp2x(|7<6YMPlSn zGKo?}z6ul1w1%)>Rv*+Qb|dmJM>D zjN=YlS-_DSGK!-aXmcTJXW5O67%gUQxoKK$y{_^dC5Zs>|ICp{k5H?8wQc*>+32&( z3r*}kX1r#sCd7!FMZxK0iO0gi0@M#K)f3~2-}U)t5N&sOUOT96vs(4IE0gv+Oh$mM z#C-v{NteM_pWat5UW!#yPK>@0SlIOB6J2`^r_$Q$G3!w`LQ`bbq9^LU%qF=y%s8F6 zjhFIOP?3g^0UkhYo!5mWvLdO!#@MP#=51qbKQfC*YKmV7gvLUXtvK;{!-Y*D3~TmZfpxOV&ipr&1Ga(5SF5?`YVEa zu8mU$vyQa=Ix;QBrZMKA^rXvg$blcD=3lZ((29JH@RPdWmoi6wJ63>@`hc@?RG3KuUcFvs~0?x zgC&24Kf6ezL zRIacP_HE9}tu&up`n7pQej1E_H=NX$E7GQMY`Z^F`?DcUt`hxDQ&sn~PglVBi|2k$ z;6d}?)Hvtr#%g{(bOqb#>1MH+w8xpEZkZh@LoDh?1-mZeZ&4>tK&gd$MW=*uJ{+7B z<1Ulg6rs56CtTYosJxZ(z8r$o>HMGSrcSf`AXFi`m`&j4k7^U zYB0%~@dqb>+ztkDV`F0hdmyY*Q=NsoAZ&+`%ZD}a&xYhL)M$fD!&iV2PEJknvgmi_m!;&C+p5o6bMH-kkyEZOD3K#VRK;Q+CMCwNyxtj&=c zPx29znyP>i0*Ws&s8%ok36hOZ(VXzjQ-G|r$XCqQ>307`OpRpD$`+A_MA(}Or;Y;i z>o)TFH)V zQ$PE)a+l7?c!w?^sWtA6C&Dfn=hDV-`ZO6TAueNU=WP$wyv3;`u?c8-6z^x^daGd18Ne6lIQm@e3et0yi71Klln% zX|a3UKD;veG{smLZ6y-&J6yCyv^|2AdD5*ZG1MWC`Vc!lnvHwt4OaYtP#SUOi1`O0 zBos2%o)0nIotLugx!DIR71gvC{$Yp;iebT^iu2T+M1uw_@%%&l&Ed}_kgsnbWS9?h ztkb{vfkhO3EHcB&VxpqsK#l-LPzl9iLMl|xfB)mjdi{ZlW5AE|sOFcm^RDtA<7JYn zY{bC$6xC!Q3Q%EfN2zG6e1!m^rbai-LN=X2`!fP6%-r8dO<85GQ0_Q1XD^AsP`6?j zR{K(*QOVupz@bo`1Pm^d;?BMxr zA%8VE-&<2rQ~OY^(Q;oD8`T$;m9^WDt-`rf_lgk}7c;EZbsoivo(C4JPLU@zkm&jH z*|sVWinsQH08Gh4mbxG0_kiO+8NiCLat$G2zW)1rp~)Wf={H=dYHGyJ62*wa?f~X( zn)&%|)}~RhDIAp?!3LT$k(=F!m=Bo?B1*}8iZs;kI9r}>a+sJF2HG_{J4+4u5gi3g z@WQmoDc1CimZ#fuLiudnuD>6Xduhtu**7p-TkvD*fslwFn54u?fB0oZCSkVy4Nhd7op-%K$wZpf3LPE;*vpa{$ ztW+DPz@Y0q@n2)-IR2 z@F|R&tvsK`j<&-8RmH2DQB`JXa6@z9yiTf8sg{=B)>bg`_=eA!H!J5-TfAxNhrkPk z!23v(^)~knII#Mx`_TUAfRv3l2nIErhOj{!7l4W{oF&E%KkA^i!R$k72#wJuy|1v> z1$S+9Bu~VuV>jirJ4Rm+hc1+lo4e<1y?xnZ8w$uF%7(RyOt}Ubly(U5@t2+?-e1VE zjkwV&Y-$MD-`ZV1A+*yr;Oyp!xW^#F?+EWDTJ<;sq{H{xj;r>E@B;t4AKDD)Y&rU> zw$}1J+K}e^2*PNLE)WE}J7%qdctFN%-VHqW#xqmb@HG;N#N(1AdAhKodArLawZQBT zuC67WM4wJOQW_2}m%9AzmRq97JI3LvLbw`cp>p zMQr3NQW}h5CT-SeB5pLj;nEGOrJE4DCXO~0$R*%0EbzxO#a@A&bsTd7B#4?};mE5` zdcg;!pTCI7035yBB-ui>VImmthofeoj!daPGFw~7%E|&Uv8Zuf1n^*fT^L$=Q}%C4 zii*ba40WA@gXtNnUn&=`FJB(;Os7}8TU!A8OBay84K;vHs`i<$a5%IGF+*rffRIu$ zVj}~}7%Op%SDu1MI#cGP%Vw1?exEdE_<3Z|z@6;xMl|@XxKdzYzHN0^MrBS@%D9=f zAav?*1V~J&|H7pn6j^3q+c3ToyGem4eB9$m74uCtVDtk6vXUG$XRxb~Z|dtSkN853 z*T9N8XVqMhSM4=4@rB*piJs3Js}FGUm*Evk61!JG)L<4Zz8DXFMT%eqJRIqsQ>7P& zY671A-fD6@J^BB+xtRsD9zX#vuOO5$;fC1rS%uZ076Guy;>8rM-R{6aVz(Z1(0bzg zM;#zu|2fLu(c-Xa-YT+6N8Lue2UP1)BH4afue|jvc3Lql41Qpd+0UOuH*NmHfciP_ zH=Z%saUg!ajr{WDzq{LCyz{gCLvE)-z-Y~DZk>dj!bHfh*A!Y66w` znERrkLgD+KYcT-Q0Ode=d3lrjy4A{hFQBE(>!i=Y{;f?RyNST5M)Sy*Tx`sF=T+r@ zly+i_P>Cb0CskBZ`I-s>ky3hFYLdzqhA>^9Ba;sZ=fEAEwex7|5*TtiZCrGjpUO21 zJSN{{kw-%tSX*10xXK84xM=yh05H9i%o3N;O@~B1z@?z$QEh!=t2XiY#Sn={jdt?A z{h|*GVB!q^9XVG07TX0(>+dJLXl3@Haen}{U(H02tfus z($c}8DH(hYOxU2_u2fKutG?f+1Mx>keikl{<)8!rtKTxm&>1|9eY&j6>@w2=8ISE> z$-uYsdUyE{dS+(cZ&y0{+X|~=RJnNFGfNg~a9*F-R_{KepOtA=5=7vW3p$tq@zL+5 z!qD8wDG4ag^%l*IjDn)0apctXv`7ySG;9TsHWe@jd$wo>Wr7g^kcJP1q)#7M@n?g+ zlPRrf#2J?ZjsPW-{n)b}7=u}V;;GB%pYh{eOj-to?>MB?=bMyE|u0*r7twE9Q9 zVq$iStMN<&?+>{E8$_~mOtX~MYF`ha-HvMB&1scMBP_c`0L!!PAct(Pd(dw3d%p>h zpcTy90IS|ge;Gc_8ge zCKcnHibh1h4DXv!l*osxIr{xbR%V6#wKY4$b>(&{#Jy_qM%d^qoZuy^#`{ZlBb z0GKk~m%_3PH;yf9$fmLo+nmpCli?eS80ZE)+RDW(>=AXw&c1t2F=9 zsGtddb>qxcnznY)NB-6D@$q2TG2IJIjaV%kk>Ig^^D!4NYxO}?P!xxm(imoi6qcHk z7`Qlz`hGpwnksrzCF}xRwAJ8|F>gR=&QO(V*I~FIMfcf??LsrTAKC%xK|CTt*Uh=% zs^@X#=JHvI*rC!5u}qSS9>rp*-fP;ho*OX=fT-s+!tUC{5JnD@uRraxC;E%I32*$= z{d-VcMLhI6?0O6V;pH}*E8u1;_^JAfp-uy8B8;ZA&z)i%VV?jgc(-luTKI(X6TZAI z^6Z_ip8Nbqz02AE&U#-)DQy&(}ES*I1=6bu}3GKH5fSqImSZJ90HubIN21^Ri+hYK!A0 zto*qyvC#C|-zGB;J@xr_Ut$xcmAj8Zu(%jNC$>wyWIEFLHwn!SG=Z_^gsXD;O!e#xhff>kpBbfi}TF@ From e597ebbc5e7d8c2550194395e48ca8ef3ecffe7a Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 4 Aug 2026 06:14:39 +0200 Subject: [PATCH 12/14] Fixed UnitTests --- .../DeviceManagement/DI/PowerBoxVendorTests.cs | 2 ++ .../UI/Images/DeviceImagesResourceTests.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs index e9dede30..a805dda3 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/PowerBoxVendorTests.cs @@ -2,6 +2,7 @@ using BrickController2.DeviceManagement; using BrickController2.DeviceManagement.PowerBox; using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.UI.Images; using FluentAssertions; using Moq; using Xunit; @@ -28,6 +29,7 @@ protected override ContainerBuilder InitializeContainer() // Arrange builder.RegisterInstance(Mock.Of()); builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); // needed because RegisterDevice().WithImages("powerfunctions_image.png", "powerfunctions_image_small.png") // execute registration of vendor PowerBox builder.RegisterModule(); diff --git a/BrickController2/BrickController2.Tests/UI/Images/DeviceImagesResourceTests.cs b/BrickController2/BrickController2.Tests/UI/Images/DeviceImagesResourceTests.cs index 1d691a22..dabe7564 100644 --- a/BrickController2/BrickController2.Tests/UI/Images/DeviceImagesResourceTests.cs +++ b/BrickController2/BrickController2.Tests/UI/Images/DeviceImagesResourceTests.cs @@ -77,6 +77,7 @@ private static DeviceImageRegistry CreateRegistry() registry.Register(DeviceType.BuWizz2, "buwizz_image.png", "buwizz_image_small.png"); registry.Register(DeviceType.RemoteControl, "remotecontrol_image_small.png", "remotecontrol_image_small.png"); registry.Register(DeviceType.Infrared, "powerfunctions_image.png", "powerfunctions_image_small.png"); + registry.Register(DeviceType.PowerBoxMBattery, "powerboxmbattery_image.png", "powerboxmbattery_image.png"); return registry; } } From ddb97a42ea9cc285ce6b977070d706e5f7451b00 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 4 Aug 2026 06:15:33 +0200 Subject: [PATCH 13/14] Refactor payload validation in unit tests Simplify PowerBoxAseriesDatagramTests and PowerBoxMBatteryDatagramTests by replacing byte-by-byte payload comparisons with payload.Should().Equal(expectedPayload) assertions for improved readability and maintainability. --- .../PowerBox/PowerBoxASeriesDatagramTests.cs | 5 +---- .../PowerBox/PowerBoxMBatteryDatagramTests.cs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs index 47fb1289..949cc483 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxASeriesDatagramTests.cs @@ -95,10 +95,7 @@ public void TryGetTelegram_CommandDatagram_Payload(float[] setValues, byte[] exp device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); // Check that the payload matches the expected values - for (int i = 0; i < expectedPayload.Length; i++) - { - payload[i].Should().Be(expectedPayload[i]); - } + payload.Should().Equal(expectedPayload); } /// diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs index d7292db5..b73484d2 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxMBatteryDatagramTests.cs @@ -89,10 +89,7 @@ public void TryGetTelegram_CommandDatagram_Payload(float[] setValues, byte[] exp device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); // Check that the payload matches the expected values - for (int i = 0; i < expectedPayload.Length; i++) - { - payload[i].Should().Be(expectedPayload[i]); - } + payload.Should().Equal(expectedPayload); } /// From d80f90685d0421eaaa10687651aa5a50898e3582 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 4 Aug 2026 06:23:50 +0200 Subject: [PATCH 14/14] Refactor tests to mock IAppIdentifierService usage Refactored PowerBoxDeviceManagerTests to use a mock of IAppIdentifierService instead of IPreferencesService. Updated the constructor and tests to use AppIdentifier1 and AppIdentifier2 constants, decoupling tests from preferences storage and focusing on integration with IAppIdentifierService. --- .../PowerBox/PowerBoxDeviceManagerTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs index 487ae518..335fac92 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/PowerBox/PowerBoxDeviceManagerTests.cs @@ -9,16 +9,16 @@ namespace BrickController2.Tests.DeviceManagement.PowerBox; public class PowerBoxDeviceManagerTests { + private const byte AppIdentifier1 = 0x61; // 'a' = 0x61 + private const byte AppIdentifier2 = 0x62; // 'b' = 0x62 + private readonly IPowerBoxDeviceManager _manager; - private readonly Mock _preferencesService = new(MockBehavior.Strict); + private readonly Mock _appIdentifierService = new(MockBehavior.Strict); public PowerBoxDeviceManagerTests() { - _preferencesService.Setup(x => x.ContainsKey("Identifier", "App")).Returns(true); - _preferencesService.Setup(x => x.Get("Identifier", "", "App")).Returns("YWJj"); - - IAppIdentifierService appIdentifierService = new AppIdentifierService(_preferencesService.Object); - _manager = new PowerBoxDeviceManager(appIdentifierService); + _appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { AppIdentifier1, AppIdentifier2 }); + _manager = new PowerBoxDeviceManager(_appIdentifierService.Object); } [Fact] @@ -26,7 +26,7 @@ public void AppId_TwoBytesInPreferences_AllBytes() { var appId = _manager.GetAppId(); appId.Length.Should().Be(2); - appId.Span[0].Should().Be(0x61); // 'a' = 0x61 - appId.Span[1].Should().Be(0x62); // 'b' = 0x62 + appId.Span[0].Should().Be(AppIdentifier1); + appId.Span[1].Should().Be(AppIdentifier2); } }