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;