using RioJoy.Core.Protocol; using RioJoy.Core.Serial; using Xunit; namespace RioJoy.Core.Tests.Serial; public class RioSerialLinkTests { private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(5); [Fact] public async Task AnalogReply_DecodesAndAcks() { var fake = new FakeTransport(); var link = new RioSerialLink(fake, new RioSerialLinkOptions { AutoPollAnalog = false }); var gotAnalog = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); link.AnalogReceived += r => gotAnalog.TrySetResult(r); using var cts = new CancellationTokenSource(); Task run = link.RunAsync(cts.Token); byte[] frame = PacketBuilder.Build(RioCommand.AnalogReply, new byte[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0 }); fake.Enqueue(frame); AnalogReport report = await gotAnalog.Task.WaitAsync(Timeout); Assert.Equal(1, report.Throttle); Assert.Equal(5, report.JoystickX); // The link should have ACK'd the packet. byte[] reply = await fake.NextWriteAsync(); Assert.Equal(new byte[] { (byte)RioControl.Ack }, reply); cts.Cancel(); await run; } [Fact] public async Task BadChecksumButton_IsNakd_WhenVerificationEnabled() { var fake = new FakeTransport(); var link = new RioSerialLink(fake, new RioSerialLinkOptions { AutoPollAnalog = false, VerifyInboundChecksum = true, }); using var cts = new CancellationTokenSource(); Task run = link.RunAsync(cts.Token); byte[] frame = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x05 }); frame[^1] ^= 0x01; // corrupt checksum fake.Enqueue(frame); byte[] reply = await fake.NextWriteAsync(); Assert.Equal(new byte[] { (byte)RioControl.Nak }, reply); cts.Cancel(); await run; } [Fact] public async Task BadChecksumButton_IsAckd_WhenVerificationDisabled() { var fake = new FakeTransport(); var link = new RioSerialLink(fake, new RioSerialLinkOptions { AutoPollAnalog = false }); using var cts = new CancellationTokenSource(); Task run = link.RunAsync(cts.Token); byte[] frame = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x05 }); frame[^1] ^= 0x01; fake.Enqueue(frame); byte[] reply = await fake.NextWriteAsync(); Assert.Equal(new byte[] { (byte)RioControl.Ack }, reply); cts.Cancel(); await run; } [Fact] public async Task VersionReply_DecodesAndRaisesEvent() { var fake = new FakeTransport(); var link = new RioSerialLink(fake, new RioSerialLinkOptions { AutoPollAnalog = false }); var gotVersion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); link.VersionReceived += v => gotVersion.TrySetResult(v); using var cts = new CancellationTokenSource(); Task run = link.RunAsync(cts.Token); fake.Enqueue(PacketBuilder.Build(RioCommand.VersionReply, new byte[] { 0, 3 })); VersionInfo version = await gotVersion.Task.WaitAsync(Timeout); Assert.Equal(0, version.Major); Assert.Equal(3, version.Minor); cts.Cancel(); await run; } [Fact] public async Task ControlByte_RaisesControlReceived() { var fake = new FakeTransport(); var link = new RioSerialLink(fake, new RioSerialLinkOptions { AutoPollAnalog = false }); var gotControl = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); link.ControlReceived += b => gotControl.TrySetResult(b); using var cts = new CancellationTokenSource(); Task run = link.RunAsync(cts.Token); fake.Enqueue((byte)RioControl.Ack); byte control = await gotControl.Task.WaitAsync(Timeout); Assert.Equal((byte)RioControl.Ack, control); cts.Cancel(); await run; } [Fact] public async Task AutoPoll_SendsAnalogRequest() { var fake = new FakeTransport(); var link = new RioSerialLink(fake, new RioSerialLinkOptions { AutoPollAnalog = true, AnalogPollInterval = TimeSpan.FromMilliseconds(20), }); using var cts = new CancellationTokenSource(); Task run = link.RunAsync(cts.Token); byte[] firstWrite = await fake.NextWriteAsync(); Assert.Equal(PacketBuilder.AnalogRequest(), firstWrite); cts.Cancel(); await run; } }