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;