Speaks the device side of the RIO serial protocol (per riojoy's PROTOCOL.md) on a COM port, behind an interactive replica of the profile editor's cockpit panel: click cells to press buttons/keys, drag the encoder gauges to move the five analog axes, and watch host-commanded lamp states (incl. flash modes) light the cells. Device behavior grounded in the real v4.2 firmware dump: version 4.2, 4-retry NAK budget ending in RESTART, and an optional emulation of the analog reply-wedge latch leak for exercising host recovery watchdogs. Verified: 33 unit tests, plus an interop harness driving RIOJoy's actual RioSerialLink against VRioDevice over an in-memory transport (version/check/analog/lamp/button/keypad/reset all round-trip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
100 lines
3.5 KiB
C#
100 lines
3.5 KiB
C#
using VRio.Core.Protocol;
|
|
using Xunit;
|
|
|
|
namespace VRio.Core.Tests;
|
|
|
|
public class ProtocolTests
|
|
{
|
|
[Fact]
|
|
public void Checksum_is_low7_of_sum_of_low7()
|
|
{
|
|
// 0x88 & 0x7F = 0x08, + 0x05 = 0x0D.
|
|
Assert.Equal(0x0D, RioChecksum.Compute(new byte[] { 0x88, 0x05 }));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(1)]
|
|
[InlineData(-1)]
|
|
[InlineData(8191)]
|
|
[InlineData(-8192)]
|
|
[InlineData(1234)]
|
|
[InlineData(-4321)]
|
|
public void AnalogCodec_round_trips_and_stays_7bit(short value)
|
|
{
|
|
AnalogCodec.Split(value, out byte low, out byte high);
|
|
|
|
Assert.Equal(0, low & 0x80); // payload bytes must keep the high bit clear
|
|
Assert.Equal(0, high & 0x80);
|
|
Assert.Equal(value, AnalogCodec.Combine(low, high));
|
|
}
|
|
|
|
[Fact]
|
|
public void Builder_output_parses_back_with_valid_checksum()
|
|
{
|
|
byte[] wire = PacketBuilder.AnalogReply(100, -200, 300, -8192, 8191);
|
|
|
|
var parser = new PacketParser();
|
|
RioRxEvent? got = null;
|
|
foreach (byte b in wire)
|
|
if (parser.Feed(b, out RioRxEvent ev))
|
|
got = ev;
|
|
|
|
Assert.NotNull(got);
|
|
Assert.Equal(RioRxKind.Packet, got!.Value.Kind);
|
|
Assert.True(got.Value.ChecksumValid);
|
|
Assert.Equal(RioCommand.AnalogReply, got.Value.Packet.Command);
|
|
|
|
byte[] p = got.Value.Packet.Payload;
|
|
Assert.Equal(100, AnalogCodec.Combine(p[0], p[1]));
|
|
Assert.Equal(-200, AnalogCodec.Combine(p[2], p[3]));
|
|
Assert.Equal(300, AnalogCodec.Combine(p[4], p[5]));
|
|
Assert.Equal(-8192, AnalogCodec.Combine(p[6], p[7]));
|
|
Assert.Equal(8191, AnalogCodec.Combine(p[8], p[9]));
|
|
}
|
|
|
|
[Fact]
|
|
public void Parser_reports_control_bytes_outside_framing()
|
|
{
|
|
var parser = new PacketParser();
|
|
Assert.True(parser.Feed((byte)RioControl.Ack, out RioRxEvent ev));
|
|
Assert.Equal(RioRxKind.ControlByte, ev.Kind);
|
|
Assert.Equal((byte)RioControl.Ack, ev.Byte);
|
|
}
|
|
|
|
[Fact]
|
|
public void Parser_resyncs_on_high_bit_byte_mid_packet()
|
|
{
|
|
var parser = new PacketParser();
|
|
|
|
// Start a LampRequest (2-byte payload), then interrupt with a new command byte.
|
|
Assert.False(parser.Feed(0x84, out _));
|
|
Assert.True(parser.Feed(0xFF, out RioRxEvent err)); // IDLE mid-packet = framing error
|
|
Assert.Equal(RioRxKind.FramingError, err.Kind);
|
|
Assert.False(parser.InPacket);
|
|
|
|
// A clean packet right after must still parse.
|
|
byte[] wire = PacketBuilder.VersionReply(1, 2);
|
|
RioRxEvent? got = null;
|
|
foreach (byte b in wire)
|
|
if (parser.Feed(b, out RioRxEvent ev))
|
|
got = ev;
|
|
Assert.Equal(RioRxKind.Packet, got!.Value.Kind);
|
|
Assert.True(got.Value.ChecksumValid);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0x00, LampBrightness.Off, LampFlash.Solid)]
|
|
[InlineData(0x14, LampBrightness.Dim, LampFlash.Solid)]
|
|
[InlineData(0x3C, LampBrightness.Bright, LampFlash.Solid)]
|
|
[InlineData(0x3D, LampBrightness.Bright, LampFlash.FlashSlow)]
|
|
[InlineData(0x16, LampBrightness.Dim, LampFlash.FlashMed)]
|
|
[InlineData(0x04, LampBrightness.Dim, LampFlash.Solid)] // field 1 only
|
|
[InlineData(0x30, LampBrightness.Bright, LampFlash.Solid)] // field 2 only
|
|
public void LampState_decodes_brightness_and_flash(byte state, LampBrightness brightness, LampFlash flash)
|
|
{
|
|
Assert.Equal(brightness, RioLampState.Brightness(state));
|
|
Assert.Equal(flash, RioLampState.Flash(state));
|
|
}
|
|
}
|