From a6899efc70eff193167a8160b167f77d88ac2194 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Mon, 27 Jul 2026 10:13:00 +0200 Subject: [PATCH 01/16] Add MK6 datagram payload tests with mocks and helpers Added MouldKing_MK6_DatagramTests to verify datagram payload construction for MK6 devices. Introduced TestableMK6 to expose TryGetTelegram for testing and TestMKPlatformService to simulate RF payload extraction. Used Moq for dependency mocking and added tests for payload identifiers, AppIdentifier inclusion, and output value encoding across channels and addresses. Utilized xUnit and FluentAssertions for test structure and assertions. --- .../MouldKing/MouldKing_MK6_DatagramTests.cs | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs new file mode 100644 index 00000000..557eccf5 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs @@ -0,0 +1,176 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.MouldKing; +using BrickController2.PlatformServices.BluetoothLE; +using FluentAssertions; +using Moq; +using System.Threading.Tasks; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.MouldKing; + +public class MouldKing_MK6_DatagramTests +{ + private static readonly byte[] AppIdentifier = [0x61, 0x62, 0x63]; + + /// + /// This class is a testable subclass of MK6 that exposes the protected TryGetTelegram method for testing purposes. + /// + private class TestableMK6 : MK6 + { + public TestableMK6(string name, string address, byte[] deviceData, + IDeviceRepository deviceRepository, IBluetoothLEService bleService, + IMKPlatformService mkPlatformService, IMouldKingDeviceManager mkDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager) + { + } + + // Expose the protected method for testing + public bool TestTryGetTelegram(bool getConnectTelegram, out byte[] payload) + => TryGetTelegram(getConnectTelegram, out payload); + } + + /// + /// This class is a test implementation of the IMKPlatformService interface that simulates the behavior of the TryGetRfPayload method for testing purposes. + /// It always returns true and sets the rfPayload to the rawData provided. + /// + private class TestMKPlatformService : IMKPlatformService + { + public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) + { + rfPayload = rawData; + return true; // Simulate success + } + } + + private readonly Mock _deviceRepository = new(MockBehavior.Strict); + private readonly Mock _bluetoothLEService = new(MockBehavior.Strict); + private readonly Mock _manager = new(MockBehavior.Strict); + private readonly TestMKPlatformService _mkPlatformService = new(); + + public MouldKing_MK6_DatagramTests() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + } + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + /// The address of the device to test. + /// The expected first payload identifier. + /// The expected second payload identifier. + [Theory] + [InlineData(MK6.Device1, 0x6d, 0x92)] + [InlineData(MK6.Device2, 0x6d, 0x92)] + [InlineData(MK6.Device3, 0x6d, 0x92)] + public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) + { + TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TestTryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(expectedPayloadIdentifier1); + payload[9].Should().Be(expectedPayloadIdentifier2); + } + + /// + /// 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. + [Theory] + [InlineData(MK6.Device1)] + [InlineData(MK6.Device2)] + [InlineData(MK6.Device3)] + public async Task MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) + { + TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TestTryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier[0]); + payload[2].Should().Be(AppIdentifier[1]); + } + + /// + /// This test checks that the payload identifiers are correctly set in the command datagram for each device address. + /// + /// The address of the device to test. + /// The expected first payload identifier. + /// The expected second payload identifier. + [Theory] + [InlineData(MK6.Device1, 0x61, 0x9e)] + [InlineData(MK6.Device2, 0x62, 0x9d)] + [InlineData(MK6.Device3, 0x63, 0x9c)] + public async Task MK6_TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) + { + TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TestTryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(expectedPayloadIdentifier1); + payload[9].Should().Be(expectedPayloadIdentifier2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the command datagram payload for each device address. + /// + /// The address of the device to test. + [Theory] + [InlineData(MK6.Device1)] + [InlineData(MK6.Device2)] + [InlineData(MK6.Device3)] + public async Task MK6_TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) + { + TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TestTryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier[0]); + payload[2].Should().Be(AppIdentifier[1]); + } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for each device address. + /// + /// The address of the device to test. + /// The set values for each channel. + /// The expected payload for the command datagram. + [Theory] + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral + [InlineData(MK6.Device1, new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0xff, 0x80, 0x80, 0x80, 0x80, 0x80 })] // channel 1 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0xff, 0x80, 0x80, 0x80, 0x80 })] // channel 2 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0xff, 0x80, 0x80, 0x80 })] // channel 3 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0xff, 0x80, 0x80 })] // channel 4 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0xff, 0x80 })] // channel 5 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0xff })] // channel 6 maximum, others neutral + [InlineData(MK6.Device1, new float[] { -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x00, 0x80, 0x80, 0x80, 0x80, 0x80 })] // channel 1 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x00, 0x80, 0x80, 0x80, 0x80 })] // channel 2 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x00, 0x80, 0x80, 0x80 })] // channel 3 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x00, 0x80, 0x80 })] // channel 4 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x00, 0x80 })] // channel 5 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x00 })] // channel 6 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum + [InlineData(MK6.Device1, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum + + [InlineData(MK6.Device2, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral + [InlineData(MK6.Device2, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum + [InlineData(MK6.Device2, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum + + [InlineData(MK6.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral + [InlineData(MK6.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum + [InlineData(MK6.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum + public async Task MK6_Check_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) + { + TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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.TestTryGetTelegram(false, out byte[] payload).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload.Length; i++) + { + payload[i + 3].Should().Be(expectedPayload[i]); + } + } +} From e8986b828a942d191d86d19d2ca2ad209e1700ac Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Mon, 27 Jul 2026 20:16:31 +0200 Subject: [PATCH 02/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs index 557eccf5..0632da14 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs @@ -79,7 +79,7 @@ public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAd [InlineData(MK6.Device1)] [InlineData(MK6.Device2)] [InlineData(MK6.Device3)] - public async Task MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) + public void MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) { TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); From 75fc848d8193ac43f2368ca29dabbff39baff574 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 28 Jul 2026 15:10:57 +0200 Subject: [PATCH 03/16] Fix payload index in assertion for expected identifier The test now checks payload[7] instead of payload[9] for expectedPayloadIdentifier2, correcting the expected position of the value within the payload array. --- .../DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs index 0632da14..4f788a1b 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs @@ -68,7 +68,7 @@ public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAd device.TestTryGetTelegram(true, out byte[] payload).Should().BeTrue(); payload[0].Should().Be(expectedPayloadIdentifier1); - payload[9].Should().Be(expectedPayloadIdentifier2); + payload[7].Should().Be(expectedPayloadIdentifier2); } /// From cf23423fccef92b2d6abe6affd58f880817ad005 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 28 Jul 2026 15:13:26 +0200 Subject: [PATCH 04/16] Shorten AppIdentifier to 2 bytes in tests Reduced the AppIdentifier byte array in MouldKing_MK6_DatagramTests.cs from three bytes ([0x61, 0x62, 0x63]) to two bytes ([0x61, 0x62]) to match updated protocol requirements. --- .../DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs index 4f788a1b..04f31512 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs @@ -10,7 +10,7 @@ namespace BrickController2.Tests.DeviceManagement.MouldKing; public class MouldKing_MK6_DatagramTests { - private static readonly byte[] AppIdentifier = [0x61, 0x62, 0x63]; + private static readonly byte[] AppIdentifier = [0x61, 0x62]; /// /// This class is a testable subclass of MK6 that exposes the protected TryGetTelegram method for testing purposes. From eab933f9ef545966492bba35f4d6d42936ee05a2 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 28 Jul 2026 15:16:07 +0200 Subject: [PATCH 05/16] Refactor tests to access TryGetTelegram directly Refactored MouldKing_MK6_DatagramTests to remove the inner TestableMK6 subclass. Changed TryGetTelegram in MKBaseByte from protected to protected internal to allow direct access in tests. Updated all test cases to instantiate MK6 and call TryGetTelegram directly. --- .../MouldKing/MouldKing_MK6_DatagramTests.cs | 37 +++++-------------- .../DeviceManagement/MouldKing/MKBaseByte.cs | 2 +- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs index 04f31512..15b74d67 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs @@ -12,23 +12,6 @@ public class MouldKing_MK6_DatagramTests { private static readonly byte[] AppIdentifier = [0x61, 0x62]; - /// - /// This class is a testable subclass of MK6 that exposes the protected TryGetTelegram method for testing purposes. - /// - private class TestableMK6 : MK6 - { - public TestableMK6(string name, string address, byte[] deviceData, - IDeviceRepository deviceRepository, IBluetoothLEService bleService, - IMKPlatformService mkPlatformService, IMouldKingDeviceManager mkDeviceManager) - : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager) - { - } - - // Expose the protected method for testing - public bool TestTryGetTelegram(bool getConnectTelegram, out byte[] payload) - => TryGetTelegram(getConnectTelegram, out payload); - } - /// /// This class is a test implementation of the IMKPlatformService interface that simulates the behavior of the TryGetRfPayload method for testing purposes. /// It always returns true and sets the rfPayload to the rawData provided. @@ -64,9 +47,9 @@ public MouldKing_MK6_DatagramTests() [InlineData(MK6.Device3, 0x6d, 0x92)] public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) { - TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); - device.TestTryGetTelegram(true, out byte[] payload).Should().BeTrue(); + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); payload[0].Should().Be(expectedPayloadIdentifier1); payload[7].Should().Be(expectedPayloadIdentifier2); } @@ -81,9 +64,9 @@ public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAd [InlineData(MK6.Device3)] public void MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) { - TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); - device.TestTryGetTelegram(true, out byte[] payload).Should().BeTrue(); + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); payload[1].Should().Be(AppIdentifier[0]); payload[2].Should().Be(AppIdentifier[1]); } @@ -100,9 +83,9 @@ public void MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddres [InlineData(MK6.Device3, 0x63, 0x9c)] public async Task MK6_TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) { - TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); - device.TestTryGetTelegram(false, out byte[] payload).Should().BeTrue(); + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); payload[0].Should().Be(expectedPayloadIdentifier1); payload[9].Should().Be(expectedPayloadIdentifier2); } @@ -117,9 +100,9 @@ public async Task MK6_TryGetTelegram_CommandDatagram_PayloadIdentifier(string de [InlineData(MK6.Device3)] public async Task MK6_TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) { - TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); - device.TestTryGetTelegram(false, out byte[] payload).Should().BeTrue(); + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); payload[1].Should().Be(AppIdentifier[0]); payload[2].Should().Be(AppIdentifier[1]); } @@ -156,7 +139,7 @@ public async Task MK6_TryGetTelegram_CommandDatagram_AppIdentifier(string device [InlineData(MK6.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum public async Task MK6_Check_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) { - TestableMK6 device = new TestableMK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); // Set the output values for the device for (int i = 0; i < setValues.Length; i++) @@ -165,7 +148,7 @@ public async Task MK6_Check_CommandDatagram_Payload(string deviceAddress, float[ } // Get the command datagram payload - device.TestTryGetTelegram(false, out byte[] payload).Should().BeTrue(); + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); // Check that the payload matches the expected values for (int i = 0; i < expectedPayload.Length; i++) diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs index 2726e213..60fd4801 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs @@ -120,7 +120,7 @@ public override void SetOutput(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) { From c4eddfc686912d5bfb170cc16fe7e808a5195f19 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Tue, 28 Jul 2026 15:43:39 +0200 Subject: [PATCH 06/16] Refactor MK6 datagram tests into base and derived classes Split MK6 datagram tests into MouldKingDatagramTestsBase and MouldKingMK6DatagramTests for better code reuse. Shared test infrastructure moved to the base class; MK6-specific logic remains in the derived class. Test method names clarified. Changed TryGetTelegram visibility to protected internal for test access. No changes to test logic. --- .../MouldKing/MouldKingDatagramTestsBase.cs | 32 +++++++++++++++ ...mTests.cs => MouldKingMK6DatagramTests.cs} | 39 ++++--------------- .../MouldKing/MKBaseNibble.cs | 2 +- 3 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs rename BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/{MouldKing_MK6_DatagramTests.cs => MouldKingMK6DatagramTests.cs} (82%) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs new file mode 100644 index 00000000..edba708f --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs @@ -0,0 +1,32 @@ +using BrickController2.DeviceManagement.MouldKing; +using BrickController2.PlatformServices.BluetoothLE; +using Moq; + +namespace BrickController2.Tests.DeviceManagement.MouldKing; + +public abstract class MouldKingDatagramTestsBase +{ + protected static readonly byte[] AppIdentifier = [0x61, 0x62]; + + /// + /// This class is a test implementation of the IMKPlatformService 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 TestMKPlatformService : IMKPlatformService + { + 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 TestMKPlatformService _mkPlatformService = new(); + + protected MouldKingDatagramTestsBase() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs similarity index 82% rename from BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs rename to BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs index 15b74d67..8ff18b80 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKing_MK6_DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs @@ -1,39 +1,14 @@ using BrickController2.DeviceManagement; using BrickController2.DeviceManagement.MouldKing; -using BrickController2.PlatformServices.BluetoothLE; using FluentAssertions; using Moq; -using System.Threading.Tasks; using Xunit; namespace BrickController2.Tests.DeviceManagement.MouldKing; -public class MouldKing_MK6_DatagramTests +public sealed class MouldKingMK6DatagramTests : MouldKingDatagramTestsBase { - private static readonly byte[] AppIdentifier = [0x61, 0x62]; - - /// - /// This class is a test implementation of the IMKPlatformService interface that simulates the behavior of the TryGetRfPayload method for testing purposes. - /// It always returns true and sets the rfPayload to the rawData provided. - /// - private class TestMKPlatformService : IMKPlatformService - { - public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) - { - rfPayload = rawData; - return true; // Simulate success - } - } - - private readonly Mock _deviceRepository = new(MockBehavior.Strict); - private readonly Mock _bluetoothLEService = new(MockBehavior.Strict); - private readonly Mock _manager = new(MockBehavior.Strict); - private readonly TestMKPlatformService _mkPlatformService = new(); - - public MouldKing_MK6_DatagramTests() - { - _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); - } + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. @@ -45,7 +20,7 @@ public MouldKing_MK6_DatagramTests() [InlineData(MK6.Device1, 0x6d, 0x92)] [InlineData(MK6.Device2, 0x6d, 0x92)] [InlineData(MK6.Device3, 0x6d, 0x92)] - public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); @@ -62,7 +37,7 @@ public void MK6_TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAd [InlineData(MK6.Device1)] [InlineData(MK6.Device2)] [InlineData(MK6.Device3)] - public void MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) + public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); @@ -81,7 +56,7 @@ public void MK6_TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddres [InlineData(MK6.Device1, 0x61, 0x9e)] [InlineData(MK6.Device2, 0x62, 0x9d)] [InlineData(MK6.Device3, 0x63, 0x9c)] - public async Task MK6_TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) + public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); @@ -98,7 +73,7 @@ public async Task MK6_TryGetTelegram_CommandDatagram_PayloadIdentifier(string de [InlineData(MK6.Device1)] [InlineData(MK6.Device2)] [InlineData(MK6.Device3)] - public async Task MK6_TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) + public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); @@ -137,7 +112,7 @@ public async Task MK6_TryGetTelegram_CommandDatagram_AppIdentifier(string device [InlineData(MK6.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral [InlineData(MK6.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum [InlineData(MK6.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum - public async Task MK6_Check_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) + public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs index 2505c8f3..8d0ea0b7 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs @@ -206,7 +206,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) { From 18be5bcc5eb9904b9cb689a3b775f56eabafeb20 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 07:09:04 +0200 Subject: [PATCH 07/16] Refactor tests to use named constants and improve coverage Refactored MouldKingMK6DatagramTests to replace hardcoded byte values with named constants for payload and app identifiers. Updated command datagram payload tests to include full expected payloads. Fixed payload comparison logic in TryGetTelegram_CommandDatagram_Payload. Added a test to ensure ArgumentOutOfRangeException is thrown for illegal channel indices. --- .../MouldKing/MouldKingDatagramTestsBase.cs | 5 +- .../MouldKing/MouldKingMK6DatagramTests.cs | 104 +++++++++++------- 2 files changed, 69 insertions(+), 40 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs index edba708f..d7900f7e 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs @@ -6,7 +6,10 @@ namespace BrickController2.Tests.DeviceManagement.MouldKing; public abstract class MouldKingDatagramTestsBase { - protected static readonly byte[] AppIdentifier = [0x61, 0x62]; + 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 IMKPlatformService interface that simulates the behavior of the TryGetRfPayload method for testing purposes. diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs index 8ff18b80..b557204f 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs @@ -2,31 +2,40 @@ using BrickController2.DeviceManagement.MouldKing; using FluentAssertions; using Moq; +using System; +using System.Net; using Xunit; namespace BrickController2.Tests.DeviceManagement.MouldKing; public sealed class MouldKingMK6DatagramTests : MouldKingDatagramTestsBase { + private const byte PayloadIdentifierConnect1 = 0x6d; + private const byte PayloadIdentifierConnect2 = 0x92; + private const byte PayloadIdentifierCommand1_1 = 0x61; + private const byte PayloadIdentifierCommand1_2 = 0x9e; + private const byte PayloadIdentifierCommand2_1 = 0x62; + private const byte PayloadIdentifierCommand2_2 = 0x9d; + private const byte PayloadIdentifierCommand3_1 = 0x63; + private const byte PayloadIdentifierCommand3_2 = 0x9c; + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// /// The address of the device to test. - /// The expected first payload identifier. - /// The expected second payload identifier. [Theory] - [InlineData(MK6.Device1, 0x6d, 0x92)] - [InlineData(MK6.Device2, 0x6d, 0x92)] - [InlineData(MK6.Device3, 0x6d, 0x92)] - public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) + [InlineData(MK6.Device1)] + [InlineData(MK6.Device2)] + [InlineData(MK6.Device3)] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); - payload[0].Should().Be(expectedPayloadIdentifier1); - payload[7].Should().Be(expectedPayloadIdentifier2); + payload[0].Should().Be(PayloadIdentifierConnect1); + payload[7].Should().Be(PayloadIdentifierConnect2); } /// @@ -42,8 +51,8 @@ public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); - payload[1].Should().Be(AppIdentifier[0]); - payload[2].Should().Be(AppIdentifier[1]); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(AppIdentifier2); } /// @@ -53,9 +62,9 @@ public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) /// The expected first payload identifier. /// The expected second payload identifier. [Theory] - [InlineData(MK6.Device1, 0x61, 0x9e)] - [InlineData(MK6.Device2, 0x62, 0x9d)] - [InlineData(MK6.Device3, 0x63, 0x9c)] + [InlineData(MK6.Device1, PayloadIdentifierCommand1_1, PayloadIdentifierCommand1_2)] + [InlineData(MK6.Device2, PayloadIdentifierCommand2_1, PayloadIdentifierCommand2_2)] + [InlineData(MK6.Device3, PayloadIdentifierCommand3_1, PayloadIdentifierCommand3_2)] public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); @@ -78,8 +87,8 @@ public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); - payload[1].Should().Be(AppIdentifier[0]); - payload[2].Should().Be(AppIdentifier[1]); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(AppIdentifier2); } /// @@ -89,29 +98,29 @@ public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) /// The set values for each channel. /// The expected payload for the command datagram. [Theory] - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral - [InlineData(MK6.Device1, new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0xff, 0x80, 0x80, 0x80, 0x80, 0x80 })] // channel 1 maximum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0xff, 0x80, 0x80, 0x80, 0x80 })] // channel 2 maximum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0xff, 0x80, 0x80, 0x80 })] // channel 3 maximum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0xff, 0x80, 0x80 })] // channel 4 maximum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0xff, 0x80 })] // channel 5 maximum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0xff })] // channel 6 maximum, others neutral - [InlineData(MK6.Device1, new float[] { -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x00, 0x80, 0x80, 0x80, 0x80, 0x80 })] // channel 1 minimum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x00, 0x80, 0x80, 0x80, 0x80 })] // channel 2 minimum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x00, 0x80, 0x80, 0x80 })] // channel 3 minimum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x00, 0x80, 0x80 })] // channel 4 minimum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x00, 0x80 })] // channel 5 minimum, others neutral - [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x00 })] // channel 6 minimum, others neutral - [InlineData(MK6.Device1, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum - [InlineData(MK6.Device1, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum - - [InlineData(MK6.Device2, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral - [InlineData(MK6.Device2, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum - [InlineData(MK6.Device2, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum - - [InlineData(MK6.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] // all channels neutral - [InlineData(MK6.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] // all channels above maximum, should be clamped to maximum - [InlineData(MK6.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // all channels neutral + [InlineData(MK6.Device1, new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 1 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0xff, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 2 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 3 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0xff, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 4 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0xff, 0x80, PayloadIdentifierCommand1_2 })] // channel 5 maximum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff, PayloadIdentifierCommand1_2 })] // channel 6 maximum, others neutral + [InlineData(MK6.Device1, new float[] { -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 1 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 2 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 3 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, PayloadIdentifierCommand1_2 })] // channel 4 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, PayloadIdentifierCommand1_2 })] // channel 5 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, PayloadIdentifierCommand1_2 })] // channel 6 minimum, others neutral + [InlineData(MK6.Device1, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, PayloadIdentifierCommand1_2 })] // all channels above maximum, should be clamped to maximum + [InlineData(MK6.Device1, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(MK6.Device2, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2_2 })] // all channels neutral + [InlineData(MK6.Device2, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, PayloadIdentifierCommand2_2 })] // all channels above maximum, should be clamped to maximum + [InlineData(MK6.Device2, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand2_2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(MK6.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand3_2 })] // all channels neutral + [InlineData(MK6.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, PayloadIdentifierCommand3_2 })] // all channels above maximum, should be clamped to maximum + [InlineData(MK6.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand3_2 })] // all channels below minimum, should be clamped to minimum public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) { MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); @@ -128,7 +137,24 @@ public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] // Check that the payload matches the expected values for (int i = 0; i < expectedPayload.Length; i++) { - payload[i + 3].Should().Be(expectedPayload[i]); + payload[i].Should().Be(expectedPayload[i]); } } + + /// + /// Tests that setting an illegal channel index throws an ArgumentOutOfRangeException. + /// + /// The address of the device to test. + [Theory] + [InlineData(MK6.Device1)] + [InlineData(MK6.Device2)] + [InlineData(MK6.Device3)] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddress) + { + MK6 device = new MK6("MK6", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } } From 78276941cd26d9218b92731279c2ac243490f8c3 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 07:09:22 +0200 Subject: [PATCH 08/16] Add MK5 datagram unit tests with xUnit and FluentAssertions Added MouldKingMK5DatagramTests class inheriting from MouldKingDatagramTestsBase. Implemented unit tests for connect and command datagram construction, payload and app identifier validation, channel value clamping, and exception handling for invalid channel indices. Used strict mocks for dependencies. --- .../MouldKing/MouldKingMK5DatagramTests.cs | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs new file mode 100644 index 00000000..9a1d49ab --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs @@ -0,0 +1,122 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +using Moq; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.MouldKing; + +public sealed class MouldKingMK5DatagramTests : MouldKingDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xad; + private const byte PayloadIdentifierConnect2 = 0x52; + private const byte PayloadIdentifierCommand1 = 0x7d; + private const byte PayloadIdentifierCommand2 = 0x82; + + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + [Fact] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier() + { + MK5 device = new MK5("MK5", MK5.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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() + { + MK5 device = new MK5("MK5", MK5.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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() + { + MK5 device = new MK5("MK5", MK5.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierCommand1); + payload[9].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() + { + MK5 device = new MK5("MK5", MK5.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf0, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 1 maximum, others neutral + [InlineData(new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x07, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 2 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0xf0, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 3 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x0f, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 4 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x02, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 5 maximum, others neutral + [InlineData(new float[] { -1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x70, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 1 minimum, others neutral + [InlineData(new float[] { 0.0f, -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x0f, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 2 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0xf0, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 3 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x07, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 4 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x02, 0x00, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 5 minimum, others neutral + [InlineData(new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf7, 0xff, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x7f, 0xf7, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(float[] setValues, byte[] expectedPayload) + { + MK5 device = new MK5("MK5", MK5.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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() + { + MK5 device = new MK5("MK5", MK5.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} From 348dfc9f4483bce48055d9781c97048f80b005f4 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 07:10:10 +0200 Subject: [PATCH 09/16] Add MK4 datagram tests and refactor array initializers Introduce MouldKingMK4DatagramTests for comprehensive unit testing of MK4 datagram logic, including payload and exception cases. Add ResetBaseTelegram for test isolation. Update MK4 array initializations to C# 9.0 syntax. --- .../MouldKing/MouldKingMK4DatagramTests.cs | 154 ++++++++++++++++++ .../DeviceManagement/MouldKing/MK4.cs | 16 +- 2 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs new file mode 100644 index 00000000..61143ad1 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs @@ -0,0 +1,154 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +using Moq; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.MouldKing; + +public sealed class MouldKingMK4DatagramTests : MouldKingDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xad; + private const byte PayloadIdentifierConnect2 = 0x52; + private const byte PayloadIdentifierCommand1 = 0x7d; + private const byte PayloadIdentifierCommand2 = 0x82; + + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + /// The address of the device to test. + [Theory] + [InlineData(MK4.Device1)] + [InlineData(MK4.Device2)] + [InlineData(MK4.Device3)] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress) + { + MK4.ResetBaseTelegram(); // Resets the state of the base telegram. + MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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. + [Theory] + [InlineData(MK4.Device1)] + [InlineData(MK4.Device2)] + [InlineData(MK4.Device3)] + public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) + { + MK4.ResetBaseTelegram(); // Resets the state of the base telegram. + MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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. + /// + /// The address of the device to test. + [Theory] + [InlineData(MK4.Device1)] + [InlineData(MK4.Device2)] + [InlineData(MK4.Device3)] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress) + { + MK4.ResetBaseTelegram(); // Resets the state of the base telegram. + MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierCommand1); + payload[9].Should().Be(PayloadIdentifierCommand2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the command datagram payload for each device address. + /// + /// The address of the device to test. + [Theory] + [InlineData(MK4.Device1)] + [InlineData(MK4.Device2)] + [InlineData(MK4.Device3)] + public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) + { + MK4.ResetBaseTelegram(); // Resets the state of the base telegram. + MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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 address of the device to test. + /// The set values for each channel. + /// The expected payload for the command datagram. + [Theory] + [InlineData(MK4.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(MK4.Device1, new float[] { 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf8, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 1 maximum, others neutral + [InlineData(MK4.Device1, new float[] { 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x8f, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 2 maximum, others neutral + [InlineData(MK4.Device1, new float[] { 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0xf8, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 3 maximum, others neutral + [InlineData(MK4.Device1, new float[] { 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x8f, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 4 maximum, others neutral + [InlineData(MK4.Device1, new float[] { -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x78, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 1 minimum, others neutral + [InlineData(MK4.Device1, new float[] { 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x87, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 2 minimum, others neutral + [InlineData(MK4.Device1, new float[] { 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x78, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 3 minimum, others neutral + [InlineData(MK4.Device1, new float[] { 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x87, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // channel 4 minimum, others neutral + [InlineData(MK4.Device1, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(MK4.Device1, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(MK4.Device2, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(MK4.Device2, new float[] { 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0xff, 0xff, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(MK4.Device2, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x77, 0x77, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(MK4.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(MK4.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x88, 0x88, 0xff, 0xff, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(MK4.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x88, 0x88, 0x77, 0x77, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) + { + MK4.ResetBaseTelegram(); // Resets the state of the base telegram. + MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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. + /// + /// The address of the device to test. + [Theory] + [InlineData(MK4.Device1)] + [InlineData(MK4.Device2)] + [InlineData(MK4.Device3)] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddress) + { + MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs index fd062dbd..fa603809 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs @@ -17,7 +17,7 @@ internal class MK4 : MKBaseNibble, IDeviceType /// Telegram to connect to the MK4.0 device(s) /// This telegram is sent on init and on reconnect conditions matching /// - private static readonly byte[] Telegram_Connect = new byte[] { 0xAD, 0x7B, 0xA7, 0x80, 0x80, 0x80, 0x4F, 0x52 }; + private static readonly byte[] Telegram_Connect = [0xAD, 0x7B, 0xA7, 0x80, 0x80, 0x80, 0x4F, 0x52]; /// /// Base Telegram for MK4.0 @@ -26,7 +26,7 @@ internal class MK4 : MKBaseNibble, IDeviceType /// * channels 0..3 for Device2 start at offset 5 and are analog channels /// * channels 0..3 for Device3 start at offset 7 and are analog channels /// - private static readonly byte[] Telegram_Base = new byte[] { 0x7D, 0x7B, 0xA7, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x82 }; + private static readonly byte[] Telegram_Base = [0x7D, 0x7B, 0xA7, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x82]; /// /// after this timespan and all channel's values equal to zero the connect telegram is sent @@ -156,4 +156,16 @@ protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDevi Device3 => 2, _ => throw new ArgumentException($"Illegal Argument: \"{address}\"", nameof(address)) }; + + /// + /// Resets the state of the base telegram. + /// This is needed for testing purposes to ensure that the base telegram is in a known state before each test is executed. + /// + internal static void ResetBaseTelegram() + { + for (int index = 3; index <= 8; index++) + { + Telegram_Base[index] = 0x88; + } + } } From 88488ef01bdf51531e0f4f80f5bcd11a437c4b88 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 07:11:19 +0200 Subject: [PATCH 10/16] Add unit tests for MK3_8 datagram logic; update constructor Added MouldKingMK3_8DatagramTests for comprehensive unit testing of MK3_8 datagram construction, including payload and exception handling. Modified MK3_8 constructor to set the third byte of _telegram_Connect and _telegram_Base arrays to 0x00. --- .../MouldKing/MouldKingMK3_8DatagramTests.cs | 123 ++++++++++++++++++ .../DeviceManagement/MouldKing/MK3_8.cs | 3 + 2 files changed, 126 insertions(+) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs new file mode 100644 index 00000000..ef7d7420 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs @@ -0,0 +1,123 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +using Moq; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.MouldKing; + +public sealed class MouldKingMK3_8DatagramTests : MouldKingDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xb1; + private const byte PayloadIdentifierConnect2 = 0xc1; + private const byte PayloadIdentifierCommand1 = 0x81; + private const byte PayloadIdentifierCommand2 = 0xc2; + + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + [Fact] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier() + { + MK3_8 device = new MK3_8("MK3_8", MK3_8.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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() + { + MK3_8 device = new MK3_8("MK3_8", MK3_8.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(0x00); // This is an exception - the second byte of the AppIdentifier is used as setvalue for channel 5 + } + + /// + /// This test checks that the payload identifiers are correctly set in the command datagram for each device address. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier() + { + MK3_8 device = new MK3_8("MK3_8", MK3_8.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifierCommand1); + payload[9].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() + { + MK3_8 device = new MK3_8("MK3_8", MK3_8.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[1].Should().Be(AppIdentifier1); + payload[2].Should().Be(0x00); // This is an exception - the second byte of the AppIdentifier is used as setvalue for channel 5 + } + + /// + /// 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, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0xf9, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 1 maximum, others neutral + [InlineData(new float[] { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x9f, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 2 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x99, 0xf9, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 3 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x99, 0x9f, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 4 maximum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x2, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 5 maximum, others neutral + [InlineData(new float[] { -1.0f, 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x79, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 1 minimum, others neutral + [InlineData(new float[] { 0.0f, -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x97, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 2 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x99, 0x79, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 3 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x0, 0x99, 0x97, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 4 minimum, others neutral + [InlineData(new float[] { 0.0f, 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // channel 5 minimum, others neutral + [InlineData(new float[] { 9.0f, 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, 0x2, 0xff, 0xff, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(new float[] { -9.0f, -9.0f, -9.0f, -9.0f, -9.0f }, new byte[]{ PayloadIdentifierCommand1, AppIdentifier1, 0x1, 0x77, 0x77, 0x99, 0x99, 0x99, 0x99, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(float[] setValues, byte[] expectedPayload) + { + MK3_8 device = new MK3_8("MK3_8", MK3_8.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _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() + { + MK3_8 device = new MK3_8("MK3_8", MK3_8.Device, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs index cdd5dc72..2354bd4e 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs @@ -36,6 +36,9 @@ internal class MK3_8 : MKBaseNibble, IDeviceType public MK3_8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, IMouldKingDeviceManager mkDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager, 0, Telegram_Connect, Telegram_Base) { + // This is an exception - the second byte of the AppIdentifier is used as setvalue for channel 5 + _telegram_Connect[2] = 0x00; + _telegram_Base[2] = 0x00; } public override DeviceType DeviceType => Type; From 0950ab231e2ca240614fa4e6a0760eb5a5ba5041 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 07:33:23 +0200 Subject: [PATCH 11/16] Refactor to use IJieStarDeviceManager interface in DI Introduce IJieStarDeviceManager interface with GetAppId method. Update JieStarDeviceManager to implement the interface. Refactor JieStarSCM4, JieStarSCM8, and JieStarBase to depend on the interface. Update DI registration and unit tests to use IJieStarDeviceManager. --- .../JieStar/JieStarDeviceManagerTests.cs | 2 +- .../DeviceManagement/JieStar/IJieStarDeviceManager.cs | 11 +++++++++++ .../DeviceManagement/JieStar/JieStar.cs | 1 + .../DeviceManagement/JieStar/JieStarBase.cs | 2 +- .../DeviceManagement/JieStar/JieStarDeviceManager.cs | 2 +- .../DeviceManagement/JieStar/JieStarSCM4.cs | 2 +- .../DeviceManagement/JieStar/JieStarSCM8.cs | 2 +- 7 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs index abc90ace..5ceee076 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs @@ -9,7 +9,7 @@ namespace BrickController2.Tests.DeviceManagement.JieStar; public class JieStarDeviceManagerTests { - private readonly JieStarDeviceManager _manager; + private readonly IJieStarDeviceManager _manager; private readonly Mock _preferencesService = new(MockBehavior.Strict); public JieStarDeviceManagerTests() diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs new file mode 100644 index 00000000..5068e256 --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs @@ -0,0 +1,11 @@ +using System; + +namespace BrickController2.DeviceManagement.JieStar; + +/// +/// Interface for JieStarDeviceManager. +/// +public interface IJieStarDeviceManager +{ + ReadOnlyMemory GetAppId(); +} diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs index e1722700..fc35b371 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.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/JieStar/JieStarBase.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs index f9b359b7..ff598fbb 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs @@ -39,7 +39,7 @@ internal abstract class JieStarBase : BluetoothAdvertisingDevice /// protected readonly float[] _storedValues; - protected JieStarBase(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager, byte[] telegram_Connect, byte[] telegram_Base, byte ctxValue2) + protected JieStarBase(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager, byte[] telegram_Connect, byte[] telegram_Base, byte ctxValue2) : base(name, address, deviceData, deviceRepository, bleService) { _telegram_Connect = telegram_Connect; diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs index 26ce1701..95d11559 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs @@ -6,7 +6,7 @@ namespace BrickController2.DeviceManagement.JieStar; /// /// Manager for JIESTAR devices /// -public class JieStarDeviceManager +public class JieStarDeviceManager : IJieStarDeviceManager { private const int AppIdentifierLength = 2; // JIESTAR protocol defines 2 bytes for the app identifier diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs index 7727a37b..149ee3ec 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs @@ -29,7 +29,7 @@ internal class JieStarSCM4 : JieStarBase, IDeviceType /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public JieStarSCM4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager) + public JieStarSCM4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, Telegram_Connect_Device, Telegram_Base_Device, GetCTXValue2(address)) { } diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs index 9b0fe6ac..8a71cbca 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs @@ -39,7 +39,7 @@ internal class JieStarSCM8 : JieStarBase, IDeviceType /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public JieStarSCM8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager) + public JieStarSCM8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, JieStarSCM8.Telegram_Connect, GetTelegramBase(address), JieStarProtocol.CTXValue2) { } From 003e4d58ba476f7c17b7cc35aba4b8c9121df5d4 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 08:13:57 +0200 Subject: [PATCH 12/16] Expand test access, add SCM4 datagram unit tests - Changed TryGetTelegram visibility to protected internal for test access. - Added abstract test base JieStarDatagramTestsBase with shared mocks and constants. - Introduced JieStarSCM4DatagramTests with comprehensive unit tests for payload construction, AppIdentifier inclusion, output settings, and exception handling. --- .../JieStar/JieStarDatagramTestsBase.cs | 35 ++++ .../JieStar/JieStarSCM4DatagramTests.cs | 149 ++++++++++++++++++ .../DeviceManagement/JieStar/JieStarBase.cs | 2 +- 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs new file mode 100644 index 00000000..39a6f1a8 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs @@ -0,0 +1,35 @@ +using BrickController2.DeviceManagement.JieStar; +using BrickController2.PlatformServices.BluetoothLE; +using Moq; + +namespace BrickController2.Tests.DeviceManagement.JieStar; + +public abstract class JieStarDatagramTestsBase +{ + 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 IJieStarPlatformService 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 TestJieStarPlatformService : IJieStarPlatformService + { + public bool TryGetRfPayload(byte ctxValue2, 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 TestJieStarPlatformService _jieStarPlatformService = new(); + + protected JieStarDatagramTestsBase() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs new file mode 100644 index 00000000..22e4573b --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs @@ -0,0 +1,149 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.JieStar; +using FluentAssertions; +using Moq; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.JieStar; + +public sealed class JieStarSCM4DatagramTests : JieStarDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xa4; + private const byte PayloadIdentifierConnect2 = 0x5b; + private const byte PayloadIdentifierCommand1 = 0x40; + private const byte PayloadIdentifierCommand2 = 0xbf; + + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM4.Device1)] + [InlineData(JieStarSCM4.Device2)] + [InlineData(JieStarSCM4.Device3)] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress) + { + JieStarSCM4 device = new JieStarSCM4("JieStarSCM4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + [Theory] + [InlineData(JieStarSCM4.Device1)] + [InlineData(JieStarSCM4.Device2)] + [InlineData(JieStarSCM4.Device3)] + public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) + { + JieStarSCM4 device = new JieStarSCM4("JieStarSCM4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM4.Device1)] + [InlineData(JieStarSCM4.Device2)] + [InlineData(JieStarSCM4.Device3)] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress) + { + JieStarSCM4 device = new JieStarSCM4("JieStarSCM4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM4.Device1)] + [InlineData(JieStarSCM4.Device2)] + [InlineData(JieStarSCM4.Device3)] + public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) + { + JieStarSCM4 device = new JieStarSCM4("JieStarSCM4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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 address of the device to test. + /// The set values for each channel. + /// The expected payload for the command datagram. + [Theory] + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(JieStarSCM4.Device1, new float[] { 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xf0, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 1 maximum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x0f, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 2 maximum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0xf0, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 3 maximum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x0f, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 4 maximum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x70, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 1 minimum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x07, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 2 minimum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x70, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 3 minimum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x07, 0x80, 0x80, PayloadIdentifierCommand2 })] // channel 4 minimum, others neutral + [InlineData(JieStarSCM4.Device1, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(JieStarSCM4.Device1, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(JieStarSCM4.Device2, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(JieStarSCM4.Device2, new float[] { 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(JieStarSCM4.Device2, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(JieStarSCM4.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels neutral + [InlineData(JieStarSCM4.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels above maximum, should be clamped to maximum + [InlineData(JieStarSCM4.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x80, 0x80, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) + { + JieStarSCM4 device = new JieStarSCM4("JieStarSCM4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM4.Device1)] + [InlineData(JieStarSCM4.Device2)] + [InlineData(JieStarSCM4.Device3)] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddress) + { + JieStarSCM4 device = new JieStarSCM4("JieStarSCM4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs index ff598fbb..0bc99499 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs @@ -227,7 +227,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) { From 64a9180b9197c4badac46ecd8e35cd97a8591ddc Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 08:50:57 +0200 Subject: [PATCH 13/16] Add JieStarSCM8Datagram unit tests with edge cases Added JieStarSCM8DatagramTests covering payload construction and validation for connect and command datagrams. Introduced parameterized tests for channel values, including edge cases and clamping. Verified payload identifiers and AppIdentifier bytes for various device addresses. Ensured ArgumentOutOfRangeException is thrown for illegal channel indices. Used Moq and FluentAssertions for testing. --- .../JieStar/JieStarSCM8DatagramTests.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs new file mode 100644 index 00000000..62bd2575 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs @@ -0,0 +1,155 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.JieStar; +using FluentAssertions; +using Moq; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.JieStar; + +public sealed class JieStarSCM8DatagramTests : JieStarDatagramTestsBase +{ + private const byte PayloadIdentifierConnect1 = 0xa4; + private const byte PayloadIdentifierConnect2 = 0x5b; + private const byte PayloadIdentifierCommand1_1 = 0x41; + private const byte PayloadIdentifierCommand1_2 = 0xbf; + private const byte PayloadIdentifierCommand2_1 = 0x42; + private const byte PayloadIdentifierCommand2_2 = 0xbe; + private const byte PayloadIdentifierCommand3_1 = 0x43; + private const byte PayloadIdentifierCommand3_2 = 0xbd; + + private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. + + /// + /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM8.Device1)] + [InlineData(JieStarSCM8.Device2)] + [InlineData(JieStarSCM8.Device3)] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress) + { + JieStarSCM8 device = new JieStarSCM8("JieStarSCM8", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + [Theory] + [InlineData(JieStarSCM8.Device1)] + [InlineData(JieStarSCM8.Device2)] + [InlineData(JieStarSCM8.Device3)] + public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) + { + JieStarSCM8 device = new JieStarSCM8("JieStarSCM8", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + /// + /// The address of the device to test. + /// The expected first payload identifier. + /// The expected second payload identifier. + [Theory] + [InlineData(JieStarSCM8.Device1, PayloadIdentifierCommand1_1, PayloadIdentifierCommand1_2)] + [InlineData(JieStarSCM8.Device2, PayloadIdentifierCommand2_1, PayloadIdentifierCommand2_2)] + [InlineData(JieStarSCM8.Device3, PayloadIdentifierCommand3_1, PayloadIdentifierCommand3_2)] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress, byte expectedPayloadIdentifier1, byte expectedPayloadIdentifier2) + { + JieStarSCM8 device = new JieStarSCM8("JieStarSCM8", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(expectedPayloadIdentifier1); + payload[7].Should().Be(expectedPayloadIdentifier2); + } + + /// + /// This test checks that the AppIdentifier is correctly included in the command datagram payload for each device address. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM8.Device1)] + [InlineData(JieStarSCM8.Device2)] + [InlineData(JieStarSCM8.Device3)] + public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) + { + JieStarSCM8 device = new JieStarSCM8("JieStarSCM8", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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 address of the device to test. + /// The set values for each channel. + /// The expected payload for the command datagram. + [Theory] + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // all channels neutral + [InlineData(JieStarSCM8.Device1, new float[] { 1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0xf0, 0x00, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 1 maximum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x0f, 0x00, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 2 maximum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0xf0, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 3 maximum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0x0f, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 4 maximum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { -1.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x70, 0x00, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 1 minimum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x07, 0x00, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 2 minimum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0x70, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 3 minimum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 0.0f, 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x00, 0x07, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // channel 4 minimum, others neutral + [InlineData(JieStarSCM8.Device1, new float[] { 9.0f, 9.0f, 9.0f, 9.0f, }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // all channels above maximum, should be clamped to maximum + [InlineData(JieStarSCM8.Device1, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x00, 0x00, PayloadIdentifierCommand1_2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(JieStarSCM8.Device2, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand2_2 })] // all channels neutral + [InlineData(JieStarSCM8.Device2, new float[] { 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x00, 0x00, PayloadIdentifierCommand2_2 })] // all channels above maximum, should be clamped to maximum + [InlineData(JieStarSCM8.Device2, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x00, 0x00, PayloadIdentifierCommand2_2 })] // all channels below minimum, should be clamped to minimum + + [InlineData(JieStarSCM8.Device3, new float[] { 0.0f, 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand3_2 })] // all channels neutral + [InlineData(JieStarSCM8.Device3, new float[] { 9.0f, 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x00, 0x00, PayloadIdentifierCommand3_2 })] // all channels above maximum, should be clamped to maximum + [InlineData(JieStarSCM8.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0x77, 0x77, 0x00, 0x00, PayloadIdentifierCommand3_2 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) + { + JieStarSCM8 device = new JieStarSCM8("JieStarSCM8", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _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. + /// + /// The address of the device to test. + [Theory] + [InlineData(JieStarSCM8.Device1)] + [InlineData(JieStarSCM8.Device2)] + [InlineData(JieStarSCM8.Device3)] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddress) + { + JieStarSCM8 device = new JieStarSCM8("JieStarSCM8", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + + Action action = () => device.SetOutput(device.NumberOfChannels, 0); + + action.Should().Throw(); + } +} From 5b527651f410f9c1985dc5678ca133a3ef1d1655 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 30 Jul 2026 09:24:24 +0200 Subject: [PATCH 14/16] Centralize telegram reset logic in device initialization Removed MK4.ResetBaseTelegram() and its test calls. Device initialization now resets the base telegram via InitDevice() in constructors of MKBaseByte, MKBaseNibble, and JieStarBase. MK4 overrides InitDevice() to set default telegram bytes for all channels, improving test reliability and reducing manual setup. --- .../MouldKing/MouldKingMK4DatagramTests.cs | 5 ---- .../DeviceManagement/JieStar/JieStarBase.cs | 4 +++ .../DeviceManagement/MouldKing/MK4.cs | 28 +++++++++++-------- .../DeviceManagement/MouldKing/MKBaseByte.cs | 3 ++ .../MouldKing/MKBaseNibble.cs | 3 ++ 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs index 61143ad1..745304ef 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs @@ -26,7 +26,6 @@ public sealed class MouldKingMK4DatagramTests : MouldKingDatagramTestsBase [InlineData(MK4.Device3)] public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddress) { - MK4.ResetBaseTelegram(); // Resets the state of the base telegram. MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); @@ -44,7 +43,6 @@ public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(string deviceAddres [InlineData(MK4.Device3)] public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) { - MK4.ResetBaseTelegram(); // Resets the state of the base telegram. MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); @@ -62,7 +60,6 @@ public void TryGetTelegram_ConnectDatagram_AppIdentifier(string deviceAddress) [InlineData(MK4.Device3)] public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddress) { - MK4.ResetBaseTelegram(); // Resets the state of the base telegram. MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); @@ -80,7 +77,6 @@ public void TryGetTelegram_CommandDatagram_PayloadIdentifier(string deviceAddres [InlineData(MK4.Device3)] public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) { - MK4.ResetBaseTelegram(); // Resets the state of the base telegram. MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); @@ -116,7 +112,6 @@ public void TryGetTelegram_CommandDatagram_AppIdentifier(string deviceAddress) [InlineData(MK4.Device3, new float[] { -9.0f, -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x88, 0x88, 0x88, 0x88, 0x77, 0x77, PayloadIdentifierCommand2 })] // all channels below minimum, should be clamped to minimum public void TryGetTelegram_CommandDatagram_Payload(string deviceAddress, float[] setValues, byte[] expectedPayload) { - MK4.ResetBaseTelegram(); // Resets the state of the base telegram. MK4 device = new MK4("MK4", deviceAddress, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); // Set the output values for the device diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs index 0bc99499..40efa73f 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs @@ -55,6 +55,10 @@ protected JieStarBase(string name, string address, byte[] deviceData, IDeviceRep _telegram_Base[1] = appId[0]; _telegram_Base[2] = appId[1]; + + + // initialize all channels in _telegram_Base and _storedValues to zero value + InitDevice(); } /// diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs index fa603809..d7597529 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs @@ -62,6 +62,22 @@ public MK4(string name, string address, byte[] deviceData, IDeviceRepository dev /// protected override ushort ManufacturerId => MKProtocol.ManufacturerID; + /// + /// This method sets the device to initial state before advertising starts + /// All channels are initialized with zeroValue. + /// + protected override void InitDevice() + { + base.InitDevice(); + + // Reset the base telegram to its initial state for all channels and all instances to the default value of 0x88. + // Because the 3 instances of the MK4.0 device are using the same static Telegram_Base, we need to reset the values for all channels of all instances. + for (int index = 3; index <= 8; index++) + { + Telegram_Base[index] = 0x88; + } + } + /// /// Get or create BluetoothAdvertisingDeviceHandler /// @@ -156,16 +172,4 @@ protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDevi Device3 => 2, _ => throw new ArgumentException($"Illegal Argument: \"{address}\"", nameof(address)) }; - - /// - /// Resets the state of the base telegram. - /// This is needed for testing purposes to ensure that the base telegram is in a known state before each test is executed. - /// - internal static void ResetBaseTelegram() - { - for (int index = 3; index <= 8; index++) - { - Telegram_Base[index] = 0x88; - } - } } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs index 60fd4801..221879a8 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs @@ -45,6 +45,9 @@ protected MKBaseByte(string name, string address, byte[] deviceData, IDeviceRepo _telegram_Base[1] = appId[0]; _telegram_Base[2] = appId[1]; + + // initialize all channels in _telegram_Base and _storedValues to zero value + InitDevice(); } /// diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs index 8d0ea0b7..eed6f755 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs @@ -62,6 +62,9 @@ protected MKBaseNibble(string name, string address, byte[] deviceData, IDeviceRe _telegram_Base[2] = appId[1]; _storedValues = new float[NumberOfChannels]; // initialize output values for all channels + + // initialize all channels in _telegram_Base and _storedValues to zero value + InitDevice(); } /// From 36c5539988b3fdefd2cb3124e053791db8bdc7fd Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sat, 1 Aug 2026 05:39:27 +0200 Subject: [PATCH 15/16] Refactor test mocks to base classes, clean up usings Move Mock to base test classes to remove duplication and centralize setup. Remove redundant Moq usings and reorder using directives for clarity. --- .../DeviceManagement/JieStar/JieStarDatagramTestsBase.cs | 4 +++- .../DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs | 6 +----- .../DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs | 6 +----- .../MouldKing/MouldKingDatagramTestsBase.cs | 4 +++- .../MouldKing/MouldKingMK3_8DatagramTests.cs | 6 +----- .../MouldKing/MouldKingMK4DatagramTests.cs | 6 +----- .../MouldKing/MouldKingMK5DatagramTests.cs | 6 +----- .../MouldKing/MouldKingMK6DatagramTests.cs | 7 +------ 8 files changed, 12 insertions(+), 33 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs index 39a6f1a8..4acb78bc 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs @@ -1,4 +1,5 @@ -using BrickController2.DeviceManagement.JieStar; +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.JieStar; using BrickController2.PlatformServices.BluetoothLE; using Moq; @@ -27,6 +28,7 @@ public bool TryGetRfPayload(byte ctxValue2, byte[] rawData, out byte[] rfPayload protected readonly Mock _bluetoothLEService = new(MockBehavior.Strict); protected readonly Mock _manager = new(MockBehavior.Strict); protected readonly TestJieStarPlatformService _jieStarPlatformService = new(); + internal readonly Mock _deviceRepository = new(MockBehavior.Strict); protected JieStarDatagramTestsBase() { diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs index 22e4573b..1f7a7b44 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs @@ -1,7 +1,5 @@ -using BrickController2.DeviceManagement; -using BrickController2.DeviceManagement.JieStar; +using BrickController2.DeviceManagement.JieStar; using FluentAssertions; -using Moq; using System; using Xunit; @@ -14,8 +12,6 @@ public sealed class JieStarSCM4DatagramTests : JieStarDatagramTestsBase private const byte PayloadIdentifierCommand1 = 0x40; private const byte PayloadIdentifierCommand2 = 0xbf; - private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. - /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs index 62bd2575..76025ee2 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs @@ -1,7 +1,5 @@ -using BrickController2.DeviceManagement; -using BrickController2.DeviceManagement.JieStar; +using BrickController2.DeviceManagement.JieStar; using FluentAssertions; -using Moq; using System; using Xunit; @@ -18,8 +16,6 @@ public sealed class JieStarSCM8DatagramTests : JieStarDatagramTestsBase private const byte PayloadIdentifierCommand3_1 = 0x43; private const byte PayloadIdentifierCommand3_2 = 0xbd; - private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. - /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs index d7900f7e..2bbbecb8 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs @@ -1,4 +1,5 @@ -using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.MouldKing; using BrickController2.PlatformServices.BluetoothLE; using Moq; @@ -27,6 +28,7 @@ public bool TryGetRfPayload(byte[] rawData, out byte[] rfPayload) protected readonly Mock _bluetoothLEService = new(MockBehavior.Strict); protected readonly Mock _manager = new(MockBehavior.Strict); protected readonly TestMKPlatformService _mkPlatformService = new(); + internal readonly Mock _deviceRepository = new(MockBehavior.Strict); protected MouldKingDatagramTestsBase() { diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs index ef7d7420..d7be6656 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs @@ -1,7 +1,5 @@ -using BrickController2.DeviceManagement; -using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.MouldKing; using FluentAssertions; -using Moq; using System; using Xunit; @@ -14,8 +12,6 @@ public sealed class MouldKingMK3_8DatagramTests : MouldKingDatagramTestsBase private const byte PayloadIdentifierCommand1 = 0x81; private const byte PayloadIdentifierCommand2 = 0xc2; - private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. - /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs index 745304ef..1733096e 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs @@ -1,7 +1,5 @@ -using BrickController2.DeviceManagement; -using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.MouldKing; using FluentAssertions; -using Moq; using System; using Xunit; @@ -14,8 +12,6 @@ public sealed class MouldKingMK4DatagramTests : MouldKingDatagramTestsBase private const byte PayloadIdentifierCommand1 = 0x7d; private const byte PayloadIdentifierCommand2 = 0x82; - private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. - /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs index 9a1d49ab..105dce4a 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs @@ -1,7 +1,5 @@ -using BrickController2.DeviceManagement; -using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.MouldKing; using FluentAssertions; -using Moq; using System; using Xunit; @@ -14,8 +12,6 @@ public sealed class MouldKingMK5DatagramTests : MouldKingDatagramTestsBase private const byte PayloadIdentifierCommand1 = 0x7d; private const byte PayloadIdentifierCommand2 = 0x82; - private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. - /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs index b557204f..71915871 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs @@ -1,9 +1,6 @@ -using BrickController2.DeviceManagement; -using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.MouldKing; using FluentAssertions; -using Moq; using System; -using System.Net; using Xunit; namespace BrickController2.Tests.DeviceManagement.MouldKing; @@ -19,8 +16,6 @@ public sealed class MouldKingMK6DatagramTests : MouldKingDatagramTestsBase private const byte PayloadIdentifierCommand3_1 = 0x63; private const byte PayloadIdentifierCommand3_2 = 0x9c; - private readonly Mock _deviceRepository = new(MockBehavior.Strict); // IDeviceRepository is defined as internal, so we can't declare it as a field in the base class. We need to declare it here in the derived class. - /// /// This test checks that the payload identifiers are correctly set in the connect datagram for each device address. /// From 951b7fc7bd1b5e26efaf1fce4b513c981deaceb0 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sat, 1 Aug 2026 06:09:26 +0200 Subject: [PATCH 16/16] Add unit tests for command datagram instance interaction Added TryGetTelegram_CommandDatagram_InstanceInteraction tests to JieStarSCM4, JieStarSCM8, MouldKingMK4, and MouldKingMK6 to verify payload construction for multiple instances. Fixed namespace import in MouldKingMK6DatagramTests.cs. --- .../JieStar/JieStarSCM4DatagramTests.cs | 41 +++++++++++++++++ .../JieStar/JieStarSCM8DatagramTests.cs | 41 +++++++++++++++++ .../MouldKing/MouldKingMK4DatagramTests.cs | 43 ++++++++++++++++++ .../MouldKing/MouldKingMK6DatagramTests.cs | 44 ++++++++++++++++++- .../DeviceManagement/JieStar/JieStarSCM4.cs | 32 ++++++++++++-- 5 files changed, 197 insertions(+), 4 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs index 1f7a7b44..8e36c35f 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs @@ -142,4 +142,45 @@ public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddres action.Should().Throw(); } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for multiple devices, ensuring that each device's payload is independent of the others. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_InstanceInteraction() + { + float[] setValues_1 = [1.0f, 1.0f, 1.0f, 1.0f]; + byte[] expectedPayload_1 = [PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x80, 0x80, PayloadIdentifierCommand2]; + + float[] setValues_2 = [0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_2 = [PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x80, 0x80, PayloadIdentifierCommand2]; + + float[] setValues_3 = [0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_3 = [PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x80, 0x80, PayloadIdentifierCommand2]; + + JieStarSCM4 device1 = new JieStarSCM4("JieStarSCM4", JieStarSCM4.Device1, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + JieStarSCM4 device2 = new JieStarSCM4("JieStarSCM4", JieStarSCM4.Device2, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + JieStarSCM4 device3 = new JieStarSCM4("JieStarSCM4", JieStarSCM4.Device3, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + + // Set the output values for the device + for (int i = 0; i < setValues_1.Length; i++) + { + device1.SetOutput(i, setValues_1[i]); + device2.SetOutput(i, setValues_2[i]); + device3.SetOutput(i, setValues_3[i]); + } + + // Get the command datagram payload + device1.TryGetTelegram(false, out byte[] payload1).Should().BeTrue(); + device2.TryGetTelegram(false, out byte[] payload2).Should().BeTrue(); + device3.TryGetTelegram(false, out byte[] payload3).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload_1.Length; i++) + { + payload1[i].Should().Be(expectedPayload_1[i]); + payload2[i].Should().Be(expectedPayload_2[i]); + payload3[i].Should().Be(expectedPayload_3[i]); + } + } } diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs index 76025ee2..aea33bc8 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs @@ -148,4 +148,45 @@ public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddres action.Should().Throw(); } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for multiple devices, ensuring that each device's payload is independent of the others. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_InstanceInteraction() + { + float[] setValues_1 = [1.0f, 1.0f, 1.0f, 1.0f]; + byte[] expectedPayload_1 = [PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x00, 0x00, PayloadIdentifierCommand1_2]; + + float[] setValues_2 = [0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_2 = [PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand2_2]; + + float[] setValues_3 = [0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_3 = [PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0x00, 0x00, 0x00, 0x00, PayloadIdentifierCommand3_2]; + + JieStarSCM8 device1 = new JieStarSCM8("JieStarSCM8", JieStarSCM8.Device1, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + JieStarSCM8 device2 = new JieStarSCM8("JieStarSCM8", JieStarSCM8.Device2, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + JieStarSCM8 device3 = new JieStarSCM8("JieStarSCM8", JieStarSCM8.Device3, [], _deviceRepository.Object, _bluetoothLEService.Object, _jieStarPlatformService, _manager.Object); + + // Set the output values for the device + for (int i = 0; i < setValues_1.Length; i++) + { + device1.SetOutput(i, setValues_1[i]); + device2.SetOutput(i, setValues_2[i]); + device3.SetOutput(i, setValues_3[i]); + } + + // Get the command datagram payload + device1.TryGetTelegram(false, out byte[] payload1).Should().BeTrue(); + device2.TryGetTelegram(false, out byte[] payload2).Should().BeTrue(); + device3.TryGetTelegram(false, out byte[] payload3).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload_1.Length; i++) + { + payload1[i].Should().Be(expectedPayload_1[i]); + payload2[i].Should().Be(expectedPayload_2[i]); + payload3[i].Should().Be(expectedPayload_3[i]); + } + } } diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs index 1733096e..4990d730 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs @@ -142,4 +142,47 @@ public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddres action.Should().Throw(); } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for multiple devices, ensuring that each device's payload is independent of the others. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_InstanceInteraction() + { + // Attention! - the MK4 devices static base telegram is interacted by all instances! + + float[] setValues_1 = [1.0f, 1.0f, 1.0f, 1.0f]; + byte[] expectedPayload_1 = [PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2]; + + float[] setValues_2 = [0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_2 = [PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2]; + + float[] setValues_3 = [0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_3 = [PayloadIdentifierCommand1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0x88, 0x88, 0x88, 0x88, PayloadIdentifierCommand2]; + + MK4 device1 = new MK4("MK4", MK4.Device1, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK4 device2 = new MK4("MK4", MK4.Device2, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK4 device3 = new MK4("MK4", MK4.Device3, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + // Set the output values for the device + for (int i = 0; i < setValues_1.Length; i++) + { + device1.SetOutput(i, setValues_1[i]); + device2.SetOutput(i, setValues_2[i]); + device3.SetOutput(i, setValues_3[i]); + } + + // Get the command datagram payload + device1.TryGetTelegram(false, out byte[] payload1).Should().BeTrue(); + device2.TryGetTelegram(false, out byte[] payload2).Should().BeTrue(); + device3.TryGetTelegram(false, out byte[] payload3).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload_1.Length; i++) + { + payload1[i].Should().Be(expectedPayload_1[i]); + payload2[i].Should().Be(expectedPayload_2[i]); + payload3[i].Should().Be(expectedPayload_3[i]); + } + } } diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs index 71915871..5d0bbcd5 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs @@ -1,4 +1,5 @@ -using BrickController2.DeviceManagement.MouldKing; +using BrickController2.DeviceManagement.JieStar; +using BrickController2.DeviceManagement.MouldKing; using FluentAssertions; using System; using Xunit; @@ -152,4 +153,45 @@ public void TryGetTelegram_CommandDatagram_SetIllegalChannel(string deviceAddres action.Should().Throw(); } + + /// + /// This test checks that the command datagram payload is correctly constructed based on the set output values for multiple devices, ensuring that each device's payload is independent of the others. + /// + [Fact] + public void TryGetTelegram_CommandDatagram_InstanceInteraction() + { + float[] setValues_1 = [1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f]; + byte[] expectedPayload_1 = [PayloadIdentifierCommand1_1, AppIdentifier1, AppIdentifier2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, PayloadIdentifierCommand1_2]; + + float[] setValues_2 = [0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_2 = [PayloadIdentifierCommand2_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand2_2]; + + float[] setValues_3 = [0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f]; + byte[] expectedPayload_3 = [PayloadIdentifierCommand3_1, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, PayloadIdentifierCommand3_2]; + + MK6 device1 = new MK6("MK6", MK6.Device1, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device2 = new MK6("MK6", MK6.Device2, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + MK6 device3 = new MK6("MK6", MK6.Device3, [], _deviceRepository.Object, _bluetoothLEService.Object, _mkPlatformService, _manager.Object); + + // Set the output values for the device + for (int i = 0; i < setValues_1.Length; i++) + { + device1.SetOutput(i, setValues_1[i]); + device2.SetOutput(i, setValues_2[i]); + device3.SetOutput(i, setValues_3[i]); + } + + // Get the command datagram payload + device1.TryGetTelegram(false, out byte[] payload1).Should().BeTrue(); + device2.TryGetTelegram(false, out byte[] payload2).Should().BeTrue(); + device3.TryGetTelegram(false, out byte[] payload3).Should().BeTrue(); + + // Check that the payload matches the expected values + for (int i = 0; i < expectedPayload_1.Length; i++) + { + payload1[i].Should().Be(expectedPayload_1[i]); + payload2[i].Should().Be(expectedPayload_2[i]); + payload3[i].Should().Be(expectedPayload_3[i]); + } + } } diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs index 149ee3ec..e93c0bc7 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs @@ -20,9 +20,19 @@ internal class JieStarSCM4 : JieStarBase, IDeviceType private static readonly byte[] Telegram_Connect_Device = [0xa4, 0x1d, 0x74, 0x80, 0x80, 0x80, 0x80, 0x5b]; /// - /// Base Telegram for SCM4 devices + /// Base Telegram for SCM4 device 1 /// - private static readonly byte[] Telegram_Base_Device = [0x40, 0x1d, 0x74, 0x80, 0x80, 0x80, 0x80, 0xbf]; + private static readonly byte[] Telegram_Base_Device_1 = [0x40, 0x1d, 0x74, 0x80, 0x80, 0x80, 0x80, 0xbf]; + + /// + /// Base Telegram for SCM4 device 2 + /// + private static readonly byte[] Telegram_Base_Device_2 = [0x40, 0x1d, 0x74, 0x80, 0x80, 0x80, 0x80, 0xbf]; + + /// + /// Base Telegram for SCM4 device 3 + /// + private static readonly byte[] Telegram_Base_Device_3 = [0x40, 0x1d, 0x74, 0x80, 0x80, 0x80, 0x80, 0xbf]; /// /// after this timespan and all channel's values equal to zero the connect telegram is sent @@ -30,7 +40,7 @@ internal class JieStarSCM4 : JieStarBase, IDeviceType private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); public JieStarSCM4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager) - : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, Telegram_Connect_Device, Telegram_Base_Device, GetCTXValue2(address)) + : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, Telegram_Connect_Device, GetTelegramBase(address), GetCTXValue2(address)) { } @@ -81,6 +91,22 @@ protected override BluetoothAdvertisingDeviceHandler GetBluetoothAdvertisingDevi /// /// address /// reference to Base-Telegram + private static byte[] GetTelegramBase(string address) + { + return address switch + { + JieStarSCM4.Device1 => Telegram_Base_Device_1, + JieStarSCM4.Device2 => Telegram_Base_Device_2, + JieStarSCM4.Device3 => Telegram_Base_Device_3, + _ => throw new ArgumentException("Illegal Argument", nameof(address)) + }; + } + + /// + /// Get CTXValue2 for the given address + /// + /// address + /// CTXValue2 private static byte GetCTXValue2(string address) { return address switch