Documentation now describes the .NET Framework 4.8 stack (was .NET 8): - README build prerequisites + framework-dependent/no-runtime-install note, test count 136 -> 241. - PLAN.md architecture diagram, stack decision (with PolySharp/shims note), suite total 136 -> 241. - deploy/README-DEPLOY.txt: app is net48 framework-dependent (4.8 is in-box on Win10/11), and build prerequisites. Also migrate the three tools (RioJoySmokeTest, RioSerialMonitor, XcfRegionExtract) to net48 so they keep building against the now-net48 RioJoy.Core (a net8 project cannot reference a net48 one). Added PolySharp + System.Memory/System.Text.Json shims and replaced net-core-only APIs (string.Contains/IndexOf with comparison, Array.Fill, generic Enum.IsDefined, int.TryParse(span)). All three build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
159 lines
6.2 KiB
C#
159 lines
6.2 KiB
C#
using System.Diagnostics;
|
|
using RioJoy.Core.Editing;
|
|
using RioJoy.Core.Mapping;
|
|
using RioJoy.Core.Protocol;
|
|
using RioJoy.Core.Serial;
|
|
|
|
// Hardware bringup monitor: open the RIO COM port, run the real serial link, and
|
|
// log every event (buttons, keypad, axis, version/check, control bytes, framing).
|
|
// It flashes all lamps once to prove the PC -> RIO output path, then echoes a lamp
|
|
// on each button press so a physical press lights up.
|
|
// dotnet run --project tools/RioSerialMonitor -- [port] [seconds]
|
|
// Exit: 0 = ran, 2 = could not open the port.
|
|
|
|
string port = args.Length > 0 ? args[0] : "COM1";
|
|
int seconds = args.Length > 1 && int.TryParse(args[1], out int s) ? s : 30;
|
|
|
|
Dictionary<int, string> groupOf = CockpitPanel.Buttons().ToDictionary(b => b.Address, b => b.Group.Title);
|
|
string Name(int addr) => groupOf.TryGetValue(addr, out string? g) ? $"0x{addr:X2} ({g})" : $"0x{addr:X2}";
|
|
|
|
var gate = new object();
|
|
var sw = Stopwatch.StartNew();
|
|
void Log(string msg)
|
|
{
|
|
lock (gate) Console.WriteLine($"[{sw.Elapsed.TotalSeconds,6:F2}s] {msg}");
|
|
}
|
|
|
|
Console.WriteLine($"== RIO serial monitor :: {port} @ 9600 8N1 for {seconds}s ==");
|
|
|
|
SerialPortTransport transport;
|
|
try
|
|
{
|
|
transport = new SerialPortTransport(port);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($" FAILED to open {port}: {ex.GetType().Name}: {ex.Message}");
|
|
Console.WriteLine(" (Is the tray app or another program holding the port? Is the cable on the right COM?)");
|
|
return 2;
|
|
}
|
|
|
|
Console.WriteLine($" opened {transport.Description}");
|
|
var link = new RioSerialLink(transport);
|
|
|
|
int packets = 0, presses = 0, releases = 0, analog = 0, controls = 0, framing = 0;
|
|
var seen = new SortedSet<int>();
|
|
VersionInfo? version = null;
|
|
var checks = new List<CheckStatus>();
|
|
|
|
// Analog: log on meaningful change only (it polls ~18x/sec). Track per-field range.
|
|
const int Deadband = 30;
|
|
AnalogReport lastLogged = default;
|
|
bool haveAnalog = false;
|
|
TimeSpan lastAnalogLog = TimeSpan.Zero;
|
|
var min = new short[5]; var max = new short[5];
|
|
for (int i = 0; i < min.Length; i++) { min[i] = short.MaxValue; max[i] = short.MinValue; }
|
|
|
|
void Track(AnalogReport r)
|
|
{
|
|
Span<short> v = stackalloc short[] { r.Throttle, r.LeftPedal, r.RightPedal, r.JoystickY, r.JoystickX };
|
|
for (int i = 0; i < 5; i++) { if (v[i] < min[i]) min[i] = v[i]; if (v[i] > max[i]) max[i] = v[i]; }
|
|
}
|
|
|
|
bool Changed(AnalogReport a, AnalogReport b) =>
|
|
Math.Abs(a.Throttle - b.Throttle) > Deadband || Math.Abs(a.LeftPedal - b.LeftPedal) > Deadband ||
|
|
Math.Abs(a.RightPedal - b.RightPedal) > Deadband || Math.Abs(a.JoystickY - b.JoystickY) > Deadband ||
|
|
Math.Abs(a.JoystickX - b.JoystickX) > Deadband;
|
|
|
|
link.PacketReceived += p =>
|
|
{
|
|
packets++;
|
|
ReadOnlySpan<byte> pl = p.Payload.Span;
|
|
switch (p.Command)
|
|
{
|
|
case RioCommand.ButtonPressed when pl[0] < RioAddress.ButtonCount:
|
|
{
|
|
int a = RioAddress.FromButton(pl[0]); presses++; seen.Add(a);
|
|
Log($"BUTTON DOWN {Name(a)}");
|
|
_ = link.SetLampAsync((byte)a, RioLampState.SolidBright); // light it
|
|
break;
|
|
}
|
|
case RioCommand.ButtonReleased when pl[0] < RioAddress.ButtonCount:
|
|
{
|
|
int a = RioAddress.FromButton(pl[0]); releases++; seen.Add(a);
|
|
Log($"button up {Name(a)}");
|
|
_ = link.SetLampAsync((byte)a, RioLampState.SolidDim);
|
|
break;
|
|
}
|
|
case RioCommand.KeyPressed when pl[0] is 0 or 1 && pl[1] <= 0x0F:
|
|
{
|
|
int a = RioAddress.FromKeypad(pl[0], pl[1]); presses++; seen.Add(a);
|
|
Log($"KEYPAD DOWN pad{pl[0]} key 0x{pl[1]:X} -> {Name(a)}");
|
|
break;
|
|
}
|
|
case RioCommand.KeyReleased when pl[0] is 0 or 1 && pl[1] <= 0x0F:
|
|
{
|
|
int a = RioAddress.FromKeypad(pl[0], pl[1]); releases++; seen.Add(a);
|
|
Log($"keypad up pad{pl[0]} key 0x{pl[1]:X} -> {Name(a)}");
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
link.AnalogReceived += r =>
|
|
{
|
|
analog++;
|
|
Track(r);
|
|
if (!haveAnalog || (Changed(r, lastLogged) && sw.Elapsed - lastAnalogLog > TimeSpan.FromMilliseconds(200)))
|
|
{
|
|
Log($"analog {r}");
|
|
lastLogged = r; haveAnalog = true; lastAnalogLog = sw.Elapsed;
|
|
}
|
|
};
|
|
|
|
link.VersionReceived += v => { version = v; Log($"VERSION reply: firmware {v}"); };
|
|
link.CheckReceived += c => { checks.Add(c); Log($"CHECK reply: {c}"); };
|
|
link.ControlReceived += b => { controls++; Log($"control byte 0x{b:X2} ({(Enum.IsDefined(typeof(RioControl), (RioControl)b) ? (RioControl)b : (object)"?")})"); };
|
|
link.FramingError += () => { framing++; Log("FRAMING ERROR (resync)"); };
|
|
|
|
using var cts = new CancellationTokenSource();
|
|
Task run = link.RunAsync(cts.Token);
|
|
|
|
await Task.Delay(400);
|
|
Log("requesting firmware version + board/lamp check...");
|
|
await link.RequestVersionAsync();
|
|
await link.RequestCheckAsync();
|
|
await Task.Delay(900);
|
|
|
|
Log("lamp test: all lamps BRIGHT for 1.5s (watch the MFD)...");
|
|
for (int a = 0; a <= RioAddress.MaxAddress; a++) _ = link.SetLampAsync((byte)a, RioLampState.SolidBright);
|
|
await Task.Delay(1500);
|
|
for (int a = 0; a <= RioAddress.MaxAddress; a++) _ = link.SetLampAsync((byte)a, RioLampState.SolidOff);
|
|
|
|
Log(">>> NOW: press the MFD buttons, press keypad keys, and move the axis <<<");
|
|
Log(">>> a pressed button should light up <<<");
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(seconds));
|
|
cts.Cancel();
|
|
try { await run; } catch { /* shutdown */ }
|
|
transport.Dispose();
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine("== summary ==");
|
|
Console.WriteLine($" packets={packets} presses={presses} releases={releases} analog={analog} control={controls} framing={framing}");
|
|
Console.WriteLine($" firmware version : {(version is null ? "(no reply)" : version.ToString())}");
|
|
Console.WriteLine($" check items : {(checks.Count == 0 ? "(none)" : string.Join(", ", checks))}");
|
|
Console.WriteLine($" addresses seen : {(seen.Count == 0 ? "(none)" : string.Join(", ", seen.Select(Name)))}");
|
|
if (haveAnalog)
|
|
{
|
|
string[] axes = { "Throttle", "LeftPedal", "RightPedal", "JoyY", "JoyX" };
|
|
Console.WriteLine(" analog raw range (min..max), the live axis is the one that moved:");
|
|
for (int i = 0; i < 5; i++)
|
|
Console.WriteLine($" {axes[i],-10}: {min[i],6} .. {max[i],-6} (span {max[i] - min[i]})");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" analog: (no analog replies received)");
|
|
}
|
|
return 0;
|