diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADatagramTestsBase.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADatagramTestsBase.cs new file mode 100644 index 00000000..b1cd971d --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADatagramTestsBase.cs @@ -0,0 +1,43 @@ +using BrickController2.DeviceManagement; +using BrickController2.DeviceManagement.CaDA; +using BrickController2.PlatformServices.BluetoothLE; +using Moq; +using System; + +namespace BrickController2.Tests.DeviceManagement.CaDA; + +public abstract class CaDADatagramTestsBase +{ + protected const byte AppIdentifier1 = 0x61; // This is the first byte of a randomly chosen AppIdentifier for unit testing + protected const byte AppIdentifier2 = 0x62; // This is the second byte of a randomly chosen AppIdentifier for unit testing + protected const byte AppIdentifier3 = 0x63; // This is the third byte of a randomly chosen AppIdentifier for unit testing + + protected static readonly byte[] AppIdentifier = [AppIdentifier1, AppIdentifier2, AppIdentifier3]; + + /// + /// This class is a test implementation of the ICaDAPlatformService 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 TestCaDAPlatformService : ICaDAPlatformService + { + 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 TestCaDAPlatformService _cadaPlatformService = new(); + internal readonly Mock _deviceRepository = new(MockBehavior.Strict); + protected readonly MessageEncoderFactory _messageEncoderFactory; + protected readonly Mock _random = new(MockBehavior.Strict); + + protected CaDADatagramTestsBase() + { + _manager.Setup(x => x.GetAppId()).Returns(AppIdentifier); + _random.Setup(x => x.Next(ushort.MinValue, ushort.MaxValue)).Returns(0); // mock random number generation to always return 0 for testing + _messageEncoderFactory = new MessageEncoderFactory(_manager.Object, _cadaPlatformService, _random.Object); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev1DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev1DatagramTests.cs new file mode 100644 index 00000000..c4099a9b --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev1DatagramTests.cs @@ -0,0 +1,205 @@ +using BrickController2.DeviceManagement.CaDA; +using FluentAssertions; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.CaDA; + +public sealed class CaDARaceCarRev1DatagramTests : CaDADatagramTestsBase +{ + private const byte PayloadIdentifier1 = 0x75; + private const byte PayloadIdentifier2 = 0x13; + private const byte MockedRandomValue1 = 0xf4; + private const byte MockedRandomValue2 = 0xf4; + + /// + /// 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(new byte[] { 0xab, 0xcd, 0xef })] + [InlineData(new byte[] { 0x12, 0x34, 0x56 })] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], deviceAddress[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifier1); + payload[1].Should().Be(PayloadIdentifier2); + } + + /// + /// 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(new byte[] { 0xab, 0xcd, 0xef })] + [InlineData(new byte[] { 0x12, 0x34, 0x35 })] + public void TryGetTelegram_ConnectDatagram_AppIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], deviceAddress[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + payload[5].Should().Be(AppIdentifier1); + payload[6].Should().Be(AppIdentifier2); + payload[7].Should().Be(AppIdentifier3); + } + + /// + /// 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(new byte[] { 0xab, 0xcd, 0xef })] + [InlineData(new byte[] { 0x12, 0x34, 0x35 })] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], deviceAddress[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[0].Should().Be(PayloadIdentifier1); + payload[1].Should().Be(PayloadIdentifier2); + } + + /// + /// 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(new byte[] { 0xab, 0xcd, 0xef })] + [InlineData(new byte[] { 0x12, 0x34, 0x35 })] + public void TryGetTelegram_CommandDatagram_AppIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], deviceAddress[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + payload[5].Should().Be(AppIdentifier1); + payload[6].Should().Be(AppIdentifier2); + payload[7].Should().Be(AppIdentifier3); + } + + /// + /// Tests that setting an illegal channel index throws an ArgumentOutOfRangeException. + /// + /// The address of the device to test. + [Theory] + [InlineData(new byte[] { 0xab, 0xcd, 0xef })] + [InlineData(new byte[] { 0x12, 0x34, 0x35 })] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], deviceAddress[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + 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() + { + byte[] deviceAddress1 = [0xab, 0xcd, 0xef]; + byte[] scanData1 = [0x00, 0x00, 0x00, 0x00, deviceAddress1[0], deviceAddress1[1], deviceAddress1[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device1 = new CaDARaceCar("CaDARaceCar1", BitConverter.ToString(deviceAddress1).ToLower(), scanData1, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + float[] setValues1 = [1.0f, 1.0f, 0.0f]; + byte[] expectedPayload1 = [ + PayloadIdentifier1, PayloadIdentifier2, + deviceAddress1[0], deviceAddress1[1], deviceAddress1[2], + AppIdentifier1, AppIdentifier2, AppIdentifier3, + MockedRandomValue1, MockedRandomValue2, // mocked random values + 0xc9, 0x1a, 0x21, 0xc9, 0xc9, 0xc9]; + + byte[] deviceAddress2 = [0x12, 0x34, 0x56]; + byte[] scanData2 = [0x00, 0x00, 0x00, 0x00, deviceAddress2[0], deviceAddress2[1], deviceAddress2[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device2 = new CaDARaceCar("CaDARaceCar2", BitConverter.ToString(deviceAddress2).ToLower(), scanData2, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + float[] setValues2 = [-1.0f, -1.0f, 0.0f]; + byte[] expectedPayload2 = [ + PayloadIdentifier1, PayloadIdentifier2, + deviceAddress2[0], deviceAddress2[1], deviceAddress2[2], + AppIdentifier1, AppIdentifier2, AppIdentifier3, + MockedRandomValue1, MockedRandomValue2, // mocked random values + 0x1a, 0xc9, 0x21, 0xc9, 0xc9, 0xc9]; + + byte[] deviceAddress3 = [0x78, 0x90, 0xab]; + byte[] scanData3 = [0x00, 0x00, 0x00, 0x00, deviceAddress3[0], deviceAddress3[1], deviceAddress3[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device3 = new CaDARaceCar("CaDARaceCar3", BitConverter.ToString(deviceAddress3).ToLower(), scanData3, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + float[] setValues3 = [1.0f, -1.0f, 1.0f]; + byte[] expectedPayload3 = [ + PayloadIdentifier1, PayloadIdentifier2, + deviceAddress3[0], deviceAddress3[1], deviceAddress3[2], + AppIdentifier1, AppIdentifier2, AppIdentifier3, + MockedRandomValue1, MockedRandomValue2, // mocked random values + 0xc9, 0xc9, 0x1a, 0xc9, 0xc9, 0xc9]; + + // Set the output values for the device + for (int i = 0; i < device1.NumberOfChannels; i++) + { + device1.SetOutput(i, setValues1[i]); + device2.SetOutput(i, setValues2[i]); + device3.SetOutput(i, setValues3[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(); + + payload1.Should().BeEquivalentTo(expectedPayload1); + payload2.Should().BeEquivalentTo(expectedPayload2); + payload3.Should().BeEquivalentTo(expectedPayload3); + } + + /// + /// 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(new byte[] { 0xab, 0xcd, 0xef }, new float[] { 0.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x21, 0x21, 0x21, 0xc9, 0xc9, 0xc9 })] // all channels neutral + [InlineData(new byte[] { 0xcd, 0xef, 0xab }, new float[] { 1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0xc9, 0x21, 0x21, 0xc9, 0xc9, 0xc9 })] // channel 1 maximum, others neutral + [InlineData(new byte[] { 0xef, 0xab, 0xcd }, new float[] { 0.0f, 1.0f, 0.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x21, 0x1a, 0x21, 0xc9, 0xc9, 0xc9 })] // channel 2 maximum, others neutral + [InlineData(new byte[] { 0x11, 0x22, 0x33 }, new float[] { 0.0f, 0.0f, 1.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x21, 0x21, 0x1a, 0xc9, 0xc9, 0xc9 })] // channel 3 maximum, others neutral + [InlineData(new byte[] { 0x22, 0x33, 0x44 }, new float[] { -1.0f, 0.0f, 0.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x1a, 0x21, 0x21, 0xc9, 0xc9, 0xc9 })] // channel 1 minimum, others neutral + [InlineData(new byte[] { 0x33, 0x44, 0x55 }, new float[] { 0.0f, -1.0f, 0.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x21, 0xc9, 0x21, 0xc9, 0xc9, 0xc9 })] // channel 2 minimum, others neutral + [InlineData(new byte[] { 0x44, 0x55, 0x66 }, new float[] { 0.0f, 0.0f, -1.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x21, 0x21, 0xc9, 0xc9, 0xc9, 0xc9 })] // channel 3 minimum, others neutral + [InlineData(new byte[] { 0x55, 0x66, 0x77 }, new float[] { 9.0f, 9.0f, 9.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0xc9, 0x1a, 0x1a, 0xc9, 0xc9, 0xc9 })] // all channels above maximum, should be clamped to maximum + [InlineData(new byte[] { 0x66, 0x77, 0x88 }, new float[] { -9.0f, -9.0f, -9.0f }, new byte[] { PayloadIdentifier1, PayloadIdentifier2, 0x00, 0x00, 0x00, AppIdentifier1, AppIdentifier2, AppIdentifier3, MockedRandomValue1, MockedRandomValue2, 0x1a, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(byte[] deviceAddress, float[] setValues, byte[] expectedPayload) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], deviceAddress[2], AppIdentifier1, AppIdentifier2, AppIdentifier3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + deviceAddress.CopyTo(expectedPayload, 2); // Copy device address to expectedPayload at index 2 + + // Set the output values for the device + for (int i = 0; i < device.NumberOfChannels; 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 + payload.Should().BeEquivalentTo(expectedPayload); + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2DatagramTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2DatagramTests.cs new file mode 100644 index 00000000..f9ce59c5 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDARaceCarRev2DatagramTests.cs @@ -0,0 +1,218 @@ +using BrickController2.DeviceManagement.CaDA; +using FluentAssertions; +using System; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.CaDA; + +public sealed class CaDARaceCarRev2DatagramTests : CaDADatagramTestsBase +{ + private const byte PayloadPairingIdentifier1 = 0xaa; + private const byte PayloadCommandIdentifier1 = 0xbb; + private const byte PayloadIdentifier2 = 0x11; + private static readonly byte[] PayloadPairingFooter = [0xcc, 0xb8, 0x92, 0xa0]; + private static readonly byte[] PayloadCommandFooter = [0xcc, 0xb8, 0x92, 0xb0]; + + /// + /// 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(new byte[] { 0xab, 0xcd })] + [InlineData(new byte[] { 0x12, 0x34 })] + public void TryGetTelegram_ConnectDatagram_PayloadIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + + payload[0].Should().Be(PayloadPairingIdentifier1); + payload[1].Should().Be(PayloadIdentifier2); + payload[12].Should().Be(PayloadPairingFooter[0]); + payload[13].Should().Be(PayloadPairingFooter[1]); + payload[14].Should().Be(PayloadPairingFooter[2]); + payload[15].Should().Be(PayloadPairingFooter[3]); + } + + /// + /// 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(new byte[] { 0xab, 0xcd })] + [InlineData(new byte[] { 0x12, 0x34 })] + public void TryGetTelegram_ConnectDatagram_AppIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(true, out byte[] payload).Should().BeTrue(); + + payload[5].Should().Be(AppIdentifier1); + payload[6].Should().Be(AppIdentifier2); + } + + /// + /// 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(new byte[] { 0xab, 0xcd })] + [InlineData(new byte[] { 0x12, 0x34 })] + public void TryGetTelegram_CommandDatagram_PayloadIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + + payload[0].Should().Be(PayloadCommandIdentifier1); + payload[1].Should().Be(PayloadIdentifier2); + payload[12].Should().Be(PayloadCommandFooter[0]); + payload[13].Should().Be(PayloadCommandFooter[1]); + payload[14].Should().Be(PayloadCommandFooter[2]); + payload[15].Should().Be(PayloadCommandFooter[3]); + } + + /// + /// 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(new byte[] { 0xab, 0xcd })] + [InlineData(new byte[] { 0x12, 0x34 })] + public void TryGetTelegram_CommandDatagram_AppIdentifier(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + device.TryGetTelegram(false, out byte[] payload).Should().BeTrue(); + + payload[5].Should().Be(AppIdentifier1); + payload[6].Should().Be(AppIdentifier2); + } + + /// + /// Tests that setting an illegal channel index throws an ArgumentOutOfRangeException. + /// + /// The address of the device to test. + [Theory] + [InlineData(new byte[] { 0xab, 0xcd })] + [InlineData(new byte[] { 0x12, 0x34 })] + public void TryGetTelegram_CommandDatagram_SetIllegalChannel(byte[] deviceAddress) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + 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() + { + byte[] deviceAddress1 = [0xab, 0xcd]; + byte[] scanData1 = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress1[0], deviceAddress1[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device1 = new CaDARaceCar("CaDARaceCar1", BitConverter.ToString(deviceAddress1).ToLower(), scanData1, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + float[] setValues1 = [1.0f, 1.0f, 0.0f]; + byte[] expectedPayload1 = [ + PayloadCommandIdentifier1, PayloadIdentifier2, + 0x11, // product/model identifier (CaDA RaceCar) + deviceAddress1[0], deviceAddress1[1], + AppIdentifier1, AppIdentifier2, + 0xde, 0x21, 0x00, // throttle, steering, lights + 0xde, 0x01, // checksum, sequence + PayloadCommandFooter[0], PayloadCommandFooter[1], PayloadCommandFooter[2], PayloadCommandFooter[3]]; // 4 bytes footer + + byte[] deviceAddress2 = [0x12, 0x34]; + byte[] scanData2 = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress2[0], deviceAddress2[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device2 = new CaDARaceCar("CaDARaceCar2", BitConverter.ToString(deviceAddress2).ToLower(), scanData2, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + float[] setValues2 = [-1.0f, -1.0f, 0.0f]; + byte[] expectedPayload2 = [ + PayloadCommandIdentifier1, PayloadIdentifier2, + 0x11, // product/model identifier (CaDA RaceCar) + deviceAddress2[0], deviceAddress2[1], + AppIdentifier1, AppIdentifier2, + 0x53, 0xac, 0x00, // throttle, steering, lights + 0xac, 0x01, // checksum, sequence + PayloadCommandFooter[0], PayloadCommandFooter[1], PayloadCommandFooter[2], PayloadCommandFooter[3]]; // 4 bytes footer + + byte[] deviceAddress3 = [0x78, 0x90]; + byte[] scanData3 = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress3[0], deviceAddress3[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + CaDARaceCar device3 = new CaDARaceCar("CaDARaceCar3", BitConverter.ToString(deviceAddress3).ToLower(), scanData3, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + float[] setValues3 = [1.0f, -1.0f, 1.0f]; + byte[] expectedPayload3 = [ + PayloadCommandIdentifier1, PayloadIdentifier2, + 0x11, // product/model identifier (CaDA RaceCar) + deviceAddress3[0], deviceAddress3[1], + AppIdentifier1, AppIdentifier2, + 0x70, 0x70, 0x01, // throttle, steering, lights + 0x70, 0x01, // checksum, sequence + PayloadCommandFooter[0], PayloadCommandFooter[1], PayloadCommandFooter[2], PayloadCommandFooter[3]]; // 4 bytes footer + + // Set the output values for the device + for (int i = 0; i < device1.NumberOfChannels; i++) + { + device1.SetOutput(i, setValues1[i]); + device2.SetOutput(i, setValues2[i]); + device3.SetOutput(i, setValues3[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 + payload1.Should().BeEquivalentTo(expectedPayload1); + payload2.Should().BeEquivalentTo(expectedPayload2); + payload3.Should().BeEquivalentTo(expectedPayload3); + } + + /// + /// 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(new byte[] { 0xab, 0xcd }, new float[] { 0.0f, 0.0f, 0.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0x5e, 0x5e, 0x00, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00 })] // all channels neutral + [InlineData(new byte[] { 0xcd, 0xef }, new float[] { 1.0f, 0.0f, 0.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0xa3, 0x23, 0x00, 0xa3, 0x01, 0x00, 0x00, 0x00, 0x00 })] // channel 1 maximum, others neutral + [InlineData(new byte[] { 0xef, 0xab }, new float[] { 0.0f, 1.0f, 0.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0x00, 0x7f, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00 })] // channel 2 maximum, others neutral + [InlineData(new byte[] { 0x11, 0x22 }, new float[] { 0.0f, 0.0f, 1.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0x1a, 0x1a, 0x01, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00 })] // channel 3 maximum, others neutral + [InlineData(new byte[] { 0x22, 0x33 }, new float[] { -1.0f, 0.0f, 0.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0xc4, 0xbb, 0x00, 0x3b, 0x01, 0x00, 0x00, 0x00, 0x00 })] // channel 1 minimum, others neutral + [InlineData(new byte[] { 0x33, 0x44 }, new float[] { 0.0f, -1.0f, 0.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0xde, 0x5e, 0x00, 0x5e, 0x01, 0x00, 0x00, 0x00, 0x00 })] // channel 2 minimum, others neutral + [InlineData(new byte[] { 0x44, 0x55 }, new float[] { 0.0f, 0.0f, -1.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0x80, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // channel 3 minimum, others neutral + [InlineData(new byte[] { 0x55, 0x66 }, new float[] { 9.0f, 9.0f, 9.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0x22, 0xdd, 0x01, 0x22, 0x01, 0x00, 0x00, 0x00, 0x00 })] // all channels above maximum, should be clamped to maximum + [InlineData(new byte[] { 0x66, 0x77 }, new float[] { -9.0f, -9.0f, -9.0f }, new byte[] { PayloadCommandIdentifier1, PayloadIdentifier2, 0x11, 0x00, 0x00, AppIdentifier1, AppIdentifier2, 0xbb, 0x44, 0x01, 0x44, 0x01, 0x00, 0x00, 0x00, 0x00 })] // all channels below minimum, should be clamped to minimum + public void TryGetTelegram_CommandDatagram_Payload(byte[] deviceAddress, float[] setValues, byte[] expectedPayload) + { + byte[] scanData = [0x00, 0x00, 0x00, 0x00, 0x00, deviceAddress[0], deviceAddress[1], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + CaDARaceCar device = new CaDARaceCar("CaDARaceCar", BitConverter.ToString(deviceAddress).ToLower(), scanData, _deviceRepository.Object, _bluetoothLEService.Object, _messageEncoderFactory); + + deviceAddress.CopyTo(expectedPayload, 3); // Copy device address to expectedPayload at index 3 + PayloadCommandFooter.CopyTo(expectedPayload, 12); // Copy footer to expectedPayload at index 12) + + // Set the output values for the device + for (int i = 0; i < device.NumberOfChannels; 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 + payload.Should().BeEquivalentTo(expectedPayload); + } +} diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCar.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCar.cs index 64a207cd..8fd0e522 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCar.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDARaceCar.cs @@ -18,6 +18,8 @@ public CaDARaceCar(string name, string address, byte[] deviceData, IDeviceReposi { // create message encoder for this device based on advertised data _messageEncoder = messageEncoderFactory.Create(deviceData); + + InitDevice(); } public override DeviceType DeviceType => DeviceType.CaDA_RaceCar; @@ -51,7 +53,7 @@ protected override void DisconnectDevice() { } - protected bool TryGetTelegram(bool getConnectTelegram, out byte[] currentData) + protected internal bool TryGetTelegram(bool getConnectTelegram, out byte[] currentData) { var changed = _outputValues.TryGetValues(out var outputValues); currentData = _messageEncoder.Encode(outputValues, getConnectTelegram); diff --git a/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs b/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs index a7448697..b444d85a 100644 --- a/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs +++ b/BrickController2/BrickController2/DeviceManagement/IO/OutputValuesGroup.cs @@ -47,7 +47,7 @@ public void Initialize() { // reset all values _outputValues.AsSpan().Clear(); - _commitedOutputValues.AsSpan().Fill(TValue.One); + _commitedOutputValues.AsSpan().Fill(TValue.One + TValue.One); // set to invalid value _values.AsSpan().Clear(); // enable sending for the first round _sendAttemptsLeft = MAX_SEND_ATTEMPTS;