diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs new file mode 100644 index 00000000..4acb78bc --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDatagramTestsBase.cs @@ -0,0 +1,37 @@ +using BrickController2.DeviceManagement; +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(); + internal readonly Mock _deviceRepository = new(MockBehavior.Strict); + + protected JieStarDatagramTestsBase() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + } +} 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.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs new file mode 100644 index 00000000..8e36c35f --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM4DatagramTests.cs @@ -0,0 +1,186 @@ +using BrickController2.DeviceManagement.JieStar; +using FluentAssertions; +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; + + /// + /// 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(); + } + + /// + /// 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 new file mode 100644 index 00000000..aea33bc8 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarSCM8DatagramTests.cs @@ -0,0 +1,192 @@ +using BrickController2.DeviceManagement.JieStar; +using FluentAssertions; +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; + + /// + /// 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(); + } + + /// + /// 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/MouldKingDatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs new file mode 100644 index 00000000..2bbbecb8 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDatagramTestsBase.cs @@ -0,0 +1,37 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.MouldKing; +using BrickController2.PlatformServices.BluetoothLE; +using Moq; + +namespace BrickController2.Tests.DeviceManagement.MouldKing; + +public abstract class MouldKingDatagramTestsBase +{ + 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. + /// 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(); + internal readonly Mock _deviceRepository = new(MockBehavior.Strict); + + protected MouldKingDatagramTestsBase() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + } +} 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..d7be6656 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK3_8DatagramTests.cs @@ -0,0 +1,119 @@ +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +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; + + /// + /// 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.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs new file mode 100644 index 00000000..4990d730 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK4DatagramTests.cs @@ -0,0 +1,188 @@ +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +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; + + /// + /// 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 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 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 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 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 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(); + } + + /// + /// 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/MouldKingMK5DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs new file mode 100644 index 00000000..105dce4a --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK5DatagramTests.cs @@ -0,0 +1,118 @@ +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +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; + + /// + /// 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(); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs new file mode 100644 index 00000000..5d0bbcd5 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingMK6DatagramTests.cs @@ -0,0 +1,197 @@ +using BrickController2.DeviceManagement.JieStar; +using BrickController2.DeviceManagement.MouldKing; +using FluentAssertions; +using System; +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; + + /// + /// 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(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(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(MK6.Device1)] + [InlineData(MK6.Device2)] + [InlineData(MK6.Device3)] + 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(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(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); + + device.TryGetTelegram(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 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(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(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); + + // 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(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(); + } + + /// + /// 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/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..40efa73f 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; @@ -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(); } /// @@ -227,7 +231,7 @@ protected bool SetChannelOutput(int channelNo, float value) /// When this method returns, contains the RF payload as a byte array if the operation succeeds; otherwise, . /// if the RF payload was successfully retrieved; otherwise, . - protected bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) + protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] payload) { if (getConnectTelegram) { diff --git a/BrickController2/BrickController2/DeviceManagement/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..e93c0bc7 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs @@ -20,17 +20,27 @@ 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 /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public JieStarSCM4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager) - : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, Telegram_Connect_Device, Telegram_Base_Device, GetCTXValue2(address)) + 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, 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 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) { } 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; diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs index fd062dbd..d7597529 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 @@ -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 /// diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs index 2726e213..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(); } /// @@ -120,7 +123,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) { diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs index 2505c8f3..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(); } /// @@ -206,7 +209,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) {