vRIO wrote whole reply packets in one SerialPort.Write; through a com0com null-modem (no UART) the host saw the bytes ~30-40 us apart, a burst no real board can produce and the prime suspect in a rocky game init. Replies now leave one byte per 10-bit frame (~1.04 ms), so the 45-byte CheckRequest response takes ~47 ms like real hardware (measured 0.94-1.10 ms gaps over a com0com pair). - Transmit frames queue to a writer thread; each byte is scheduled against a monotonic slot deadline slot = max(prev + period, now), so the stream averages true 9600 baud without bursting after idle. - After a write the schedule is floored at the actual emission time: a late wake-up can never be followed by a catch-up burst - two frames closer than the frame time is structurally impossible. - 1 ms system timer resolution while the port is open (timeBeginPeriod) so the pacer sleeps most of each gap and only spins the last ~1.8 ms. - Side benefit: UI clicks no longer block on SerialPort.Write (a stalled port could previously hang the UI for the 2 s write timeout). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.7 KiB
vRIO — virtual RIO cockpit device
A software replica of the cockpit RIO (Remote Input/Output) board: it opens a COM port and speaks the device side of the RIO serial protocol, so any host that expects the real hardware — most importantly RIOJoy — can talk to it without a cockpit attached.
The window is an interactive version of the cockpit control panel that RIOJoy's profile editor draws (the same functional layout from the original Win32 RIO design: five MFD clusters, four board columns, two keypads, encoder gauges). But where the editor edits bindings, vRIO's cells are the physical controls:
- Left-click a cell — momentary button press (
ButtonPressed/ReleasedorKeyPressed/Releasedon the wire). Right-click — latch it down. - Drag the X/Y box and the Z / L / R gauges — the five analog axes,
returned by the next
AnalogReply(14-bit signed, 7-bit-pair packed). Gauges span each axis' realistic hardware travel, not the full 14-bit range: Z/L/R rest at raw 0 at the gauge bottom and travel to −800 (throttle — forward travel runs negative, matching RIOJoy's ratchet) or +500 (spring-loaded pedals), and the stick covers ±80 around center — the windows RIOJoy's calibrator expects. - Cells shade to the lamp state the host commands (
LampRequest: off / dim / bright, with slow/med/fast flash), so RIOJoy's press-feedback lights the on-screen panel just like the real buttons. - Keyboard and Xbox (XInput) controller input drive the same controls
through a bindings file (
%APPDATA%\vRIO\bindings.txt, created with commented defaults on first run — Edit bindings… opens it, Reload bindings applies edits live). Keys and pad buttons press any RIO address; pad sticks/triggers and keys drive the axes in each axis' realistic travel window, with deflect (spring-back), rate (throttle-style, position holds), deadzone, and invert options. Defaults: arrows = stick, W/S = throttle, Q/E = pedals, numpad = internal keypad, IJKL/Space = hat + main, left stick / triggers / right stick = stick / pedals / throttle on the pad.
Wire behavior
Protocol per riojoy/docs/PROTOCOL.md (9600 8N1, [cmd][payload…][7-bit checksum], ACK 0xFC / NAK 0xFD / RESTART 0xFE / IDLE 0xFF), with
device behavior grounded in the real v4.2 firmware dump
(riojoy/rio-firmware/RIOv4_2-ANALYSIS.md):
- ACKs every well-formed packet; NAKs bad-checksum packets.
- TX is paced at the wire rate — one byte per 10-bit frame (~1.04 ms at 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→ oneBoardOkCheckReply per board (the 11 boards from the legacy firmware's table).VersionRequest→ configurable version, default 4.2.ResetRequestre-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.
- Optional v4.2 reply-wedge emulation: after retry exhaustion (or the
"Wedge analog now" button), analog requests are silently dropped — still
ACK'd, RX path alive — until a host
ResetRequest, reproducing the latch-leak fault the firmware analysis documents. Use it to exercise RIOJoy's 5-second no-analog recovery watchdog.
Using it with RIOJoy on one PC
The two apps need a crossed serial link. Install a
com0com virtual null-modem pair
(e.g. COM5 ⇄ COM6), then:
- Run
VRio.App, pickCOM5, Open. - Point RIOJoy at
COM6.
RIOJoy's DTR open-pulse shows up in the wire log (DSR handshake), its ~55 ms analog polling drives the "analog polls served" counter, and every click on the vRIO panel arrives at RIOJoy as real cockpit input. Two physical PCs with a null-modem cable work the same way.
Repository layout
| Path | Contents |
|---|---|
src/VRio.Core |
Protocol framing/builder/parser, the VRioDevice state machine, serial pump, panel layout data (class library) |
src/VRio.App |
WinForms panel UI |
tests/VRio.Core.Tests |
xUnit tests for the protocol + device engine |
Building
Same toolchain as RIOJoy: .NET SDK (8.0+) with the .NET Framework 4.8 targeting pack; apps target net48 so deployed builds run in-box on Windows 10/11.
dotnet build VRio.sln -c Release
dotnet test VRio.sln
Interop is additionally verified against RIOJoy's real RioSerialLink
(version/check/analog/lamp/button/keypad/reset round-trips over an in-memory
transport) — see the RIOJoy repo for the host side.