diff --git a/README.md b/README.md index 9107596..7d90502 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,13 @@ device behavior grounded in the **real v4.2 firmware dump** 9600 8N1), never closer. A virtual null-modem has no UART, so unpaced writes would land at the host in microsecond bursts no real board could produce; vRIO's writer thread schedules each byte against a monotonic - slot deadline instead, so e.g. the 45-byte CheckRequest response takes - the same ~47 ms it takes real hardware. -- `CheckRequest` → one `BoardOk` CheckReply per board (the 11 boards from the - legacy firmware's table). `VersionRequest` → configurable version, - default **4.2**. + slot deadline instead, so e.g. the 51-byte CheckRequest response takes + the same ~53 ms it takes real hardware. +- `CheckRequest` → the real board's init handshake: `TestModeChange` **enter**, + one `BoardOk` CheckReply per board (the 11 boards from the legacy firmware's + table), then `TestModeChange` **exit**. Hosts wait (≤5 s per step) on both + test-mode packets and send nothing while test mode is active, so the exit is + mandatory. `VersionRequest` → configurable version, default **4.2**. - `ResetRequest` re-zeroes the targeted axis (or all). - A NAK re-sends the last event up to **4 times**, then gives up with a RESTART byte — the real board's retry budget. diff --git a/src/VRio.Core/Device/VRioDevice.cs b/src/VRio.Core/Device/VRioDevice.cs index 9fdbdb6..b2c4f1c 100644 --- a/src/VRio.Core/Device/VRioDevice.cs +++ b/src/VRio.Core/Device/VRioDevice.cs @@ -13,7 +13,9 @@ namespace VRio.Core.Device; /// Wire behavior (mirroring what RIOJoy expects from the real board): /// /// ACKs every well-formed inbound packet, NAKs one with a bad checksum. -/// CheckRequest → a BoardOk CheckReply per known board. +/// CheckRequest → TestModeChange ENTER, a BoardOk CheckReply per known +/// board (the self-test stream), then TestModeChange EXIT — the init +/// handshake the real board performs and the game waits (≤5s per step) on. /// VersionRequest → VersionReply with the configured firmware version /// (default 4.2, matching the real board's dumped EPROM). /// AnalogRequest → AnalogReply with the current five axis values. @@ -239,9 +241,14 @@ public sealed class VRioDevice switch (packet.Command) { case RioCommand.CheckRequest: - Logged?.Invoke("RX CheckRequest → all boards OK"); + // Init handshake (verified against a real v4.2 board tap): the + // host waits ≤5s for TestModeChange ENTER before anything else, + // and sends no requests at all until the matching EXIT arrives. + Logged?.Invoke("RX CheckRequest → test mode enter, all boards OK, exit"); + Send(PacketBuilder.TestModeChange(1)); foreach ((byte number, string _) in RioAddressSpace.Boards) Send(PacketBuilder.CheckReply(RioStatusType.BoardOk, number)); + Send(PacketBuilder.TestModeChange(0)); break; case RioCommand.VersionRequest: diff --git a/tests/VRio.Core.Tests/VRioDeviceTests.cs b/tests/VRio.Core.Tests/VRioDeviceTests.cs index 28ac905..a98688b 100644 --- a/tests/VRio.Core.Tests/VRioDeviceTests.cs +++ b/tests/VRio.Core.Tests/VRioDeviceTests.cs @@ -79,22 +79,35 @@ public class VRioDeviceTests } [Fact] - public void CheckRequest_reports_every_board_ok() + public void CheckRequest_enters_test_mode_reports_boards_then_exits() { var device = new VRioDevice(); var wire = new Wire(device); Send(device, PacketBuilder.Build(RioCommand.CheckRequest)); - Assert.Equal(RioAddressSpace.Boards.Count, wire.Packets.Count); - Assert.All(wire.Packets, p => + // The init handshake: TestModeChange ENTER, one CheckReply per board, + // TestModeChange EXIT. The game waits on both test-mode packets and + // stays mute forever if the EXIT never arrives. + Assert.Equal(RioAddressSpace.Boards.Count + 2, wire.Packets.Count); + + RioPacket enter = wire.Packets[0]; + Assert.Equal(RioCommand.TestModeChange, enter.Command); + Assert.Equal(new byte[] { 1 }, enter.Payload); + + RioPacket exit = wire.Packets[wire.Packets.Count - 1]; + Assert.Equal(RioCommand.TestModeChange, exit.Command); + Assert.Equal(new byte[] { 0 }, exit.Payload); + + var checks = wire.Packets.GetRange(1, RioAddressSpace.Boards.Count); + Assert.All(checks, p => { Assert.Equal(RioCommand.CheckReply, p.Command); Assert.Equal((byte)RioStatusType.BoardOk, p.Payload[0]); }); Assert.Equal( RioAddressSpace.Boards.Select(b => b.Number), - wire.Packets.Select(p => p.Payload[1])); + checks.Select(p => p.Payload[1])); } [Fact]