Prepares RioJoy.Core for the net40 (Windows XP) target, which has no System.Memory, ValueTask, or System.Text.Json: - IRioTransport and the whole protocol/framing layer now use byte[] + Task (RioPacket.Payload, PacketParser/Builder, RioChecksum, replies, AnalogReport, RioHidReport). At 9600 baud Span bought nothing; the SerialPortTransport bridge copies disappear entirely. - ConfigStore/OverlayTemplateStore switch to Newtonsoft 13 with the same conventions (indented, PascalCase, string enums, null-skipping); verified against the real STJ-written config.json and regions.json (load + round-trip). System.Memory and System.Text.Json packages dropped. 275 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.9 KiB
C#
76 lines
2.9 KiB
C#
namespace RioJoy.Core.Protocol;
|
||
|
||
/// <summary>
|
||
/// The five raw analog axes decoded from an <see cref="RioCommand.AnalogReply"/>
|
||
/// payload. Values are the 14-bit signed raw counts straight off the RIO, before
|
||
/// any calibration/deadzone mapping (that math is Phase 4). Port of
|
||
/// <c>AnalogEvent</c> / <c>CombinePair</c> (riovjoy2.cpp#L1116); see
|
||
/// docs/PROTOCOL.md §4.
|
||
/// </summary>
|
||
public readonly struct AnalogReport
|
||
{
|
||
public short Throttle { get; }
|
||
public short LeftPedal { get; }
|
||
public short RightPedal { get; }
|
||
public short JoystickY { get; }
|
||
public short JoystickX { get; }
|
||
|
||
public AnalogReport(short throttle, short leftPedal, short rightPedal, short joystickY, short joystickX)
|
||
{
|
||
Throttle = throttle;
|
||
LeftPedal = leftPedal;
|
||
RightPedal = rightPedal;
|
||
JoystickY = joystickY;
|
||
JoystickX = joystickX;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Decode a 14-bit signed axis value from a (low, high) byte pair, each
|
||
/// carrying 7 data bits. Port of <c>CombinePair</c> (riovjoy2.cpp#L1116):
|
||
/// combine the 7-bit halves then sign-extend bit 13.
|
||
/// </summary>
|
||
public static short CombinePair(byte low, byte high)
|
||
{
|
||
int raw = (low & 0x7F) | (high << 7); // 14 bits
|
||
if ((raw & 0x2000) != 0) // bit 13 set → negative
|
||
raw |= ~0x3FFF; // sign-extend into the high bits
|
||
return (short)raw;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Try to decode an <see cref="RioCommand.AnalogReply"/> payload (10 bytes:
|
||
/// 5 axes × low,high in the order throttle, left pedal, right pedal,
|
||
/// joystick Y, joystick X). Returns <see langword="false"/> if any byte equals
|
||
/// <c>0xFE</c> (<see cref="RioControl.Restart"/>), which the RIO uses as an
|
||
/// "invalid sample" sentinel — the legacy code ignores such replies.
|
||
/// </summary>
|
||
public static bool TryParse(byte[] payload, out AnalogReport report)
|
||
{
|
||
if (payload is null) throw new ArgumentNullException(nameof(payload));
|
||
if (payload.Length != RioCommandTable.PayloadLength(RioCommand.AnalogReply))
|
||
throw new ArgumentException(
|
||
$"AnalogReply payload must be {RioCommandTable.PayloadLength(RioCommand.AnalogReply)} bytes.",
|
||
nameof(payload));
|
||
|
||
foreach (byte b in payload)
|
||
{
|
||
if (b == (byte)RioControl.Restart)
|
||
{
|
||
report = default;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
report = new AnalogReport(
|
||
throttle: CombinePair(payload[0], payload[1]),
|
||
leftPedal: CombinePair(payload[2], payload[3]),
|
||
rightPedal: CombinePair(payload[4], payload[5]),
|
||
joystickY: CombinePair(payload[6], payload[7]),
|
||
joystickX: CombinePair(payload[8], payload[9]));
|
||
return true;
|
||
}
|
||
|
||
public override string ToString() =>
|
||
$"T:{Throttle} L:{LeftPedal} R:{RightPedal} Y:{JoystickY} X:{JoystickX}";
|
||
}
|