diff --git a/tools/RioSerialMonitor/Program.cs b/tools/RioSerialMonitor/Program.cs new file mode 100644 index 0000000..50cb3b8 --- /dev/null +++ b/tools/RioSerialMonitor/Program.cs @@ -0,0 +1,158 @@ +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 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(); +VersionInfo? version = null; +var checks = new List(); + +// 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]; +Array.Fill(min, short.MaxValue); Array.Fill(max, short.MinValue); + +void Track(AnalogReport r) +{ + Span 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 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((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; diff --git a/tools/RioSerialMonitor/RioSerialMonitor.csproj b/tools/RioSerialMonitor/RioSerialMonitor.csproj new file mode 100644 index 0000000..70d5b50 --- /dev/null +++ b/tools/RioSerialMonitor/RioSerialMonitor.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0-windows + enable + enable + RioSerialMonitor + + + + + + +