From 0b30a57edd8064483b11cf41fd07847811ba51dd Mon Sep 17 00:00:00 2001 From: n30nex Date: Sat, 20 Jun 2026 08:47:29 -0400 Subject: [PATCH 1/2] Add MC0 companion event revisions --- README.md | 13 +- docs/tdeck-feature-inventory.md | 2 +- docs/tdeck-firmware-roadmap.md | 19 +- docs/tdeck-meshcore-companion-protocol.md | 35 ++- scripts/mc_companion_usb_smoke.py | 116 +++++++- sim/main_sim.c | 47 +++- src/mc_companion.cpp | 3 + src/services/mesh.h | 1 + src/services/mesh_core.c | 312 ++++++++++++++++++---- 9 files changed, 452 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index bfcf95d..e36950a 100644 --- a/README.md +++ b/README.md @@ -128,11 +128,12 @@ iPhone-style dark look (status bar, battery glyph, grouped settings cards). enabling one frees the other. ### 🔭 Later -- **MeshCore companion bridge** — V0 protocol foundation is drafted, and an - initial `companion mc ...` USB serial-console smoke surface can report - snapshots and exercise send boundaries; the formal USB/BLE bridge remains - planned, and it is not official MeshCore app compatible unless the real - MeshCore app protocol is confirmed. +- **MeshCore companion bridge** — V0 USB foundation is in progress: + `companion mc ...` covers console smoke, and `companion mc usb on` enters + formal MC0 mode for identity/status/node/thread snapshots, send boundaries, + revision counters, and event controls. BLE transport remains planned, and it + is not official MeshCore app compatible unless the real MeshCore app protocol + is confirmed. - **Roll the iPhone look everywhere** — grouped cards / dividers across Messages, Nodes, Contacts. - **Local app platform** - scan local app manifests from `/sd/limitlezz/apps`, `/sd/apps`, `/appfs/apps`, and simulator data dirs, then show accepted apps @@ -185,7 +186,7 @@ iPhone-style dark look (status bar, battery glyph, grouped settings cards). - [`docs/tdeck-firmware-audit.md`](docs/tdeck-firmware-audit.md) - current firmware audit and risk list. - [`docs/tdeck-feature-inventory.md`](docs/tdeck-feature-inventory.md) - feature-by-feature implementation inventory. - [`docs/tdeck-firmware-roadmap.md`](docs/tdeck-firmware-roadmap.md) - roadmap to a complete T-Deck firmware. -- [`docs/tdeck-meshcore-companion-protocol.md`](docs/tdeck-meshcore-companion-protocol.md) - draft Phase 5/V0.8 MeshCore companion line protocol. +- [`docs/tdeck-meshcore-companion-protocol.md`](docs/tdeck-meshcore-companion-protocol.md) - Phase 5/V0.8 MeshCore companion V0 protocol. - [`docs/tdeck-hardware-dogfood-checklist.md`](docs/tdeck-hardware-dogfood-checklist.md) - stock-device hardware proof checklist. - [`docs/tdeck-release-checklist.md`](docs/tdeck-release-checklist.md) - slow-host Actions artifact and COM8 release evidence checklist. - [`docs/tdeck-troubleshooting.md`](docs/tdeck-troubleshooting.md) - build, flash, boot, radio, storage, Wi-Fi, and companion troubleshooting. diff --git a/docs/tdeck-feature-inventory.md b/docs/tdeck-feature-inventory.md index ad3fb6e..7b30397 100644 --- a/docs/tdeck-feature-inventory.md +++ b/docs/tdeck-feature-inventory.md @@ -75,7 +75,7 @@ Status labels: | MeshCore self-advert TX | Partial, needs validation | Ed25519 identity, self-advert builder, serial/UI advert commands | Needs interop proof with real MeshCore nodes. | | MeshCore public channel / rooms | Planned | README says receive/default Public channel still ahead | V0.6: implement group text decode/send, room model, and split airtime config. | | MeshCore DMs | Planned | MeshCore contacts are non-messageable while gated | V0.7: implement key/session model, send path, ACKs, and UI routing. | -| MeshCore companion bridge | Planned/In progress | `docs/tdeck-meshcore-companion-protocol.md` drafts the V0 USB serial line protocol; `companion mc ...` provides an initial firmware smoke surface for snapshots, sends, and self-test | V0.8: formalize USB first, mirror to BLE later, and do not claim external MeshCore app compatibility until the real app protocol is confirmed. | +| MeshCore companion bridge | Partial/In progress | `docs/tdeck-meshcore-companion-protocol.md` defines the V0 USB serial line protocol; `companion mc ...` provides console smoke coverage, and `companion mc usb on` enters formal MC0 mode for identity/status/node/thread snapshots, send boundaries, event controls, revision counters, and bounded event drain | V0.8: broaden event/status coverage, mirror to BLE later, and do not claim external MeshCore app compatibility until the real app protocol is confirmed. | ## User Interface diff --git a/docs/tdeck-firmware-roadmap.md b/docs/tdeck-firmware-roadmap.md index e04f738..fe9a1ec 100644 --- a/docs/tdeck-firmware-roadmap.md +++ b/docs/tdeck-firmware-roadmap.md @@ -47,7 +47,7 @@ These maintainer-provided beta labels are the canonical near-term sequence. The | V0.6 | MeshCore public chat and split airtime config | 🚧 Public chat send/receive hardware-verified; **split airtime may not be working — needs re-verification**; config UI still TODO | | V0.6 | MeshCore public chat and split airtime config | In progress - public chat send/receive hardware-verified; split-airtime serial dwell/switch smoke passed on COM8; packet-loss, latency, and real dual-network traffic soak still open | | V0.7 | MeshCore DMs and private chats | ✅ Encrypted DMs (X25519 ECDH + AES) send/receive hardware-verified against a real MeshCore peer | -| V0.8 | MeshCore USB companion and MeshCore BLE companion | 🚧 Protocol foundation drafted; USB/BLE implementation still planned and not external-app compatible yet | +| V0.8 | MeshCore USB companion and MeshCore BLE companion | In progress - USB MC0 mode now covers identity/status/node/thread snapshots, send boundaries, revision counters, event controls, and COM8 smoke hooks; BLE and external-app compatibility are still planned | | V0.9 | Code review, optimization, and emoji polish | ⬜ Not started | | V0.95 | Basic app SDK and infrastructure; Home UI supports adding apps and multiple home screens | 🚧 Local manifest scanner, Home paging, and detail shell started; runtime/catalog still TODO | | V0.96 | Upgraded Wi-Fi password storage | ✅ Implemented on T-Deck hardware: credentials use ESP32 NVS, legacy `wifi.cfg` migrates/removes, and diagnostics do not print passwords | @@ -189,17 +189,22 @@ Exit criteria: Goal: expose MeshCore companion functionality only after native MeshCore messaging is stable. -**Status:** protocol foundation in progress with an initial USB serial-console -smoke surface. `companion mc hello|status|nodes|threads|send|dm|test` now -exercises the firmware-owned MeshCore snapshots and send boundaries for COM8 -validation, while the formal `MC0` bridge, BLE transport, events, and real -external MeshCore app compatibility are still planned work. +**Status:** USB-first protocol foundation in progress. `companion mc +hello|status|nodes|threads|send|dm|test` still exercises the firmware-owned +MeshCore snapshots and send boundaries for COM8 validation, and formal +`companion mc usb on` MC0 mode now handles `HELLO`, `IDENTITY`, `STATUS`, +`NODES`, `THREADS`, `SEND_PUBLIC`, `SEND_DM`, `EVENTS`, and `EXIT` with +snapshot revisions plus a bounded event drain. BLE transport and real external +MeshCore app compatibility are still planned work. Deliverables: - Define the MeshCore companion protocol surface for node DB, public chat, private chats, and send/receive forwarding. Drafted as `docs/tdeck-meshcore-companion-protocol.md`. - Add a USB serial-console smoke surface that reports MeshCore companion status, nodes, threads, Public send, DM send, and self-test. -- Implement MeshCore USB companion mode. +- Implement MeshCore USB companion mode. In progress: MC0 USB request/response + mode is implemented for identity, status, node/thread snapshots, send + boundaries, event controls, and reconnect/resync counters; deeper event + coverage and external protocol mapping remain. - Implement MeshCore BLE companion mode. - Add UI and serial commands that distinguish Meshtastic companion from MeshCore companion. - Decide whether one companion session or one network can own the external-app bridge at a time. diff --git a/docs/tdeck-meshcore-companion-protocol.md b/docs/tdeck-meshcore-companion-protocol.md index e5cea30..bbf8a4b 100644 --- a/docs/tdeck-meshcore-companion-protocol.md +++ b/docs/tdeck-meshcore-companion-protocol.md @@ -1,10 +1,11 @@ # T-Deck MeshCore Companion V0 Protocol -Status: Phase 5 / V0.8 protocol foundation plus an initial firmware serial -smoke surface. The current firmware exposes `companion mc hello`, `status`, -`nodes`, `threads`, `send`, `dm`, and `test` for USB console validation; the -formal `MC0` request/response bridge, live events, BLE transport, and external -app compatibility are still planned. +Status: Phase 5 / V0.8 USB-first protocol foundation. The firmware exposes +`companion mc hello`, `status`, `nodes`, `threads`, `send`, `dm`, and `test` +for USB console validation, plus formal `companion mc usb on` MC0 mode for +`HELLO`, `IDENTITY`, `STATUS`, `NODES`, `THREADS`, `SEND_PUBLIC`, `SEND_DM`, +`EVENTS`, and `EXIT`. Snapshot revision counters and a bounded event drain are +implemented; BLE transport and external app compatibility are still planned. ## Goal @@ -49,8 +50,12 @@ the normal console prompt. The firmware enters MC0 mode with: ```text companion mc usb on MC0 1 HELLO proto=0 app=limitlezz-smoke host=windows want=none -MC0 2 STATUS -MC0 3 NODES since=0 limit=5 +MC0 2 IDENTITY +MC0 3 STATUS +MC0 4 NODES since=0 limit=5 +MC0 5 THREADS since=0 limit=5 +MC0 6 EVENTS mode=on types=nodes,messages,tx,status +MC0 7 EVENTS mode=off MC0 99 EXIT ``` @@ -373,12 +378,14 @@ Rules: 2. Add an initial USB serial-console smoke surface for `hello`, `status`, `nodes`, `threads`, Public send, DM send, and self-test. 3. Add a USB-only MeshCore companion mode with `HELLO`, `IDENTITY`, `STATUS`, - and `NODES`. + and `NODES`. Implemented. 4. Add `SEND_PUBLIC` and `SEND_DM` using the existing firmware-owned MeshCore - send paths. + send paths. Implemented. 5. Add event streaming for receive, send-status, node-change, and status - changes. -6. Add snapshot revision counters and reconnect/resync behavior. + changes. In progress: event controls and a bounded event drain are + implemented for node, message, and TX events. +6. Add snapshot revision counters and reconnect/resync behavior. Implemented + for node/thread snapshots and `STATUS`/`HELLO` resync. 7. Mirror the same logical protocol over BLE only after USB behavior is stable. 8. Revisit external-app compatibility only after the real MeshCore app protocol is confirmed. @@ -390,9 +397,9 @@ Rules: `companion mc test`. - Formal USB MC0 smoke is opt-in: `python scripts/mc_companion_usb_smoke.py --mc0-usb` enters the configured - USB mode, sends `HELLO`, `STATUS`, and `NODES`, asserts `MC0 ... OK`, - `BEGIN`, and `END` response markers, then exits through the configured - `MC0 EXIT` line. + USB mode, sends `HELLO`, `IDENTITY`, `STATUS`, `NODES`, `THREADS`, and + `EVENTS` on/off, asserts `MC0 ... OK`, revision, `BEGIN`, and `END` + response markers, then exits through the configured `MC0 EXIT` line. - If firmware lands different command names, use the smoke helper's `--mc0-enter-command`, `--mc0-*-template`, `--mc0-*-marker`, and `--mc0-exit-template` flags instead of editing firmware or weakening the diff --git a/scripts/mc_companion_usb_smoke.py b/scripts/mc_companion_usb_smoke.py index 1d7f61f..86b13fe 100644 --- a/scripts/mc_companion_usb_smoke.py +++ b/scripts/mc_companion_usb_smoke.py @@ -33,12 +33,20 @@ DEFAULT_PUBLIC_TEMPLATE = "companion mc send {text}" DEFAULT_MC0_ENTER_COMMAND = "companion mc usb on" DEFAULT_MC0_HELLO_ID = "1" -DEFAULT_MC0_STATUS_ID = "2" -DEFAULT_MC0_NODES_ID = "3" +DEFAULT_MC0_IDENTITY_ID = "2" +DEFAULT_MC0_STATUS_ID = "3" +DEFAULT_MC0_NODES_ID = "4" +DEFAULT_MC0_THREADS_ID = "5" +DEFAULT_MC0_EVENTS_ON_ID = "6" +DEFAULT_MC0_EVENTS_OFF_ID = "7" DEFAULT_MC0_EXIT_ID = "99" DEFAULT_MC0_HELLO_TEMPLATE = "MC0 {id} HELLO proto=0 app=limitlezz-smoke host=windows want=none" +DEFAULT_MC0_IDENTITY_TEMPLATE = "MC0 {id} IDENTITY" DEFAULT_MC0_STATUS_TEMPLATE = "MC0 {id} STATUS" DEFAULT_MC0_NODES_TEMPLATE = "MC0 {id} NODES since=0 limit=5" +DEFAULT_MC0_THREADS_TEMPLATE = "MC0 {id} THREADS since=0 limit=5" +DEFAULT_MC0_EVENTS_ON_TEMPLATE = "MC0 {id} EVENTS mode=on types=nodes,messages,tx,status" +DEFAULT_MC0_EVENTS_OFF_TEMPLATE = "MC0 {id} EVENTS mode=off" DEFAULT_MC0_EXIT_TEMPLATE = "MC0 {id} EXIT" DEFAULT_STATUS_MARKERS = ["mccomp: status", "MeshCore", "MC companion"] DEFAULT_TEST_MARKERS = ["PASS"] @@ -180,19 +188,44 @@ def build_mc0_specs(args: argparse.Namespace) -> list[CommandSpec]: hello_command = format_mc0_template( "--mc0-hello-template", args.mc0_hello_template, args.mc0_hello_id ) + identity_command = format_mc0_template( + "--mc0-identity-template", args.mc0_identity_template, args.mc0_identity_id + ) status_command = format_mc0_template( "--mc0-status-template", args.mc0_status_template, args.mc0_status_id ) nodes_command = format_mc0_template( "--mc0-nodes-template", args.mc0_nodes_template, args.mc0_nodes_id ) + threads_command = format_mc0_template( + "--mc0-threads-template", args.mc0_threads_template, args.mc0_threads_id + ) + events_on_command = format_mc0_template( + "--mc0-events-on-template", args.mc0_events_on_template, args.mc0_events_on_id + ) + events_off_command = format_mc0_template( + "--mc0-events-off-template", args.mc0_events_off_template, args.mc0_events_off_id + ) return [ CommandSpec( "mc0 hello", hello_command, default_marker_override( args.mc0_hello_marker, - [f"MC0 {args.mc0_hello_id} OK"], + [ + f"MC0 {args.mc0_hello_id} OK", + "event_seq=", + "nodes_rev=", + "messages_rev=", + ], + ), + ), + CommandSpec( + "mc0 identity", + identity_command, + default_marker_override( + args.mc0_identity_marker, + [f"MC0 {args.mc0_identity_id} OK", "addr=", "addr_format="], ), ), CommandSpec( @@ -200,7 +233,7 @@ def build_mc0_specs(args: argparse.Namespace) -> list[CommandSpec]: status_command, default_marker_override( args.mc0_status_marker, - [f"MC0 {args.mc0_status_id} OK"], + [f"MC0 {args.mc0_status_id} OK", "event_seq=", "nodes_rev=", "messages_rev="], ), ), CommandSpec( @@ -211,6 +244,30 @@ def build_mc0_specs(args: argparse.Namespace) -> list[CommandSpec]: [f"MC0 {args.mc0_nodes_id} BEGIN", f"MC0 {args.mc0_nodes_id} END"], ), ), + CommandSpec( + "mc0 threads", + threads_command, + default_marker_override( + args.mc0_threads_marker, + [f"MC0 {args.mc0_threads_id} BEGIN", f"MC0 {args.mc0_threads_id} END"], + ), + ), + CommandSpec( + "mc0 events on", + events_on_command, + default_marker_override( + args.mc0_events_on_marker, + [f"MC0 {args.mc0_events_on_id} OK", "events=on", "types="], + ), + ), + CommandSpec( + "mc0 events off", + events_off_command, + default_marker_override( + args.mc0_events_off_marker, + [f"MC0 {args.mc0_events_off_id} OK", "events=off"], + ), + ), ] @@ -549,14 +606,23 @@ def main() -> int: help="Seconds of quiet serial input that ends an optional MC0 read.", ) mc0.add_argument("--mc0-hello-id", default=DEFAULT_MC0_HELLO_ID) + mc0.add_argument("--mc0-identity-id", default=DEFAULT_MC0_IDENTITY_ID) mc0.add_argument("--mc0-status-id", default=DEFAULT_MC0_STATUS_ID) mc0.add_argument("--mc0-nodes-id", default=DEFAULT_MC0_NODES_ID) + mc0.add_argument("--mc0-threads-id", default=DEFAULT_MC0_THREADS_ID) + mc0.add_argument("--mc0-events-on-id", default=DEFAULT_MC0_EVENTS_ON_ID) + mc0.add_argument("--mc0-events-off-id", default=DEFAULT_MC0_EVENTS_OFF_ID) mc0.add_argument("--mc0-exit-id", default=DEFAULT_MC0_EXIT_ID) mc0.add_argument( "--mc0-hello-template", default=DEFAULT_MC0_HELLO_TEMPLATE, help="MC0 HELLO line template; supports {id}.", ) + mc0.add_argument( + "--mc0-identity-template", + default=DEFAULT_MC0_IDENTITY_TEMPLATE, + help="MC0 IDENTITY line template; supports {id}.", + ) mc0.add_argument( "--mc0-status-template", default=DEFAULT_MC0_STATUS_TEMPLATE, @@ -567,15 +633,35 @@ def main() -> int: default=DEFAULT_MC0_NODES_TEMPLATE, help="MC0 NODES line template; supports {id}.", ) + mc0.add_argument( + "--mc0-threads-template", + default=DEFAULT_MC0_THREADS_TEMPLATE, + help="MC0 THREADS line template; supports {id}.", + ) + mc0.add_argument( + "--mc0-events-on-template", + default=DEFAULT_MC0_EVENTS_ON_TEMPLATE, + help="MC0 EVENTS-on line template; supports {id}.", + ) + mc0.add_argument( + "--mc0-events-off-template", + default=DEFAULT_MC0_EVENTS_OFF_TEMPLATE, + help="MC0 EVENTS-off line template; supports {id}.", + ) mc0.add_argument( "--mc0-hello-marker", action="append", - help="Override HELLO expected marker(s). Defaults to 'MC0 OK'.", + help="Override HELLO expected marker(s). Defaults to OK plus revision fields.", + ) + mc0.add_argument( + "--mc0-identity-marker", + action="append", + help="Override IDENTITY expected marker(s). Defaults to OK plus identity fields.", ) mc0.add_argument( "--mc0-status-marker", action="append", - help="Override STATUS expected marker(s). Defaults to 'MC0 OK'.", + help="Override STATUS expected marker(s). Defaults to OK plus revision fields.", ) mc0.add_argument( "--mc0-nodes-marker", @@ -585,6 +671,24 @@ def main() -> int: "'MC0 BEGIN' and 'MC0 END'." ), ) + mc0.add_argument( + "--mc0-threads-marker", + action="append", + help=( + "Override THREADS expected marker(s). Defaults to both " + "'MC0 BEGIN' and 'MC0 END'." + ), + ) + mc0.add_argument( + "--mc0-events-on-marker", + action="append", + help="Override EVENTS-on expected marker(s). Defaults to OK plus events=on.", + ) + mc0.add_argument( + "--mc0-events-off-marker", + action="append", + help="Override EVENTS-off expected marker(s). Defaults to OK plus events=off.", + ) mc0.add_argument( "--mc0-exit-template", default=DEFAULT_MC0_EXIT_TEMPLATE, diff --git a/sim/main_sim.c b/sim/main_sim.c index 3073a12..1816437 100644 --- a/sim/main_sim.c +++ b/sim/main_sim.c @@ -2086,29 +2086,54 @@ static int codec_selftest(void) char mc0[900], proto[120]; bool mc0_exit = false; lz_svc_mc_companion_handle_line("MC0 1 HELLO proto=0 app=selftest", mc0, sizeof mc0, &mc0_exit); - CHECK(strstr(mc0, "MC0 1 OK proto=0") != NULL && strstr(mc0, "caps=") != NULL, + const char *nodes_rev_s = strstr(mc0, "nodes_rev="); + const char *messages_rev_s = strstr(mc0, "messages_rev="); + CHECK(strstr(mc0, "MC0 1 OK proto=0") != NULL && strstr(mc0, "caps=") != NULL && + nodes_rev_s != NULL && messages_rev_s != NULL, "MeshCore MC0 HELLO reports protocol capabilities"); lz_svc_mc_companion_handle_line("MC0 2 STATUS", mc0, sizeof mc0, &mc0_exit); CHECK(strstr(mc0, "MC0 2 OK") != NULL && strstr(mc0, "nodes=1") != NULL && - strstr(mc0, "threads=2") != NULL, - "MeshCore MC0 STATUS counts snapshots"); + strstr(mc0, "threads=2") != NULL && strstr(mc0, "events=off") != NULL, + "MeshCore MC0 STATUS counts snapshots and event state"); lz_svc_mc_companion_handle_line("MC0 3 NODES since=0 limit=5", mc0, sizeof mc0, &mc0_exit); CHECK(strstr(mc0, "MC0 3 BEGIN type=nodes") != NULL && + strstr(mc0, "rev=") != NULL && strstr(mc0, "name=CompanionPeer") != NULL && strstr(mc0, "MC0 3 END type=nodes") != NULL, - "MeshCore MC0 NODES snapshot lists peer"); - lz_svc_mc_companion_handle_line("MC0 4 THREADS", mc0, sizeof mc0, &mc0_exit); - CHECK(strstr(mc0, "MC0 4 BEGIN type=threads") != NULL && + "MeshCore MC0 NODES snapshot lists peer with revision"); + lz_svc_mc_companion_handle_line("MC0 4 NODES since=999999 limit=5", mc0, sizeof mc0, &mc0_exit); + CHECK(strstr(mc0, "count=0") != NULL && strstr(mc0, "rev=") != NULL, + "MeshCore MC0 NODES since current revision returns empty snapshot"); + lz_svc_mc_companion_handle_line("MC0 5 THREADS", mc0, sizeof mc0, &mc0_exit); + CHECK(strstr(mc0, "MC0 5 BEGIN type=threads") != NULL && strstr(mc0, "text=CompanionPeer%3A%20public%20hello") != NULL && strstr(mc0, "text=dm%20hello") != NULL, "MeshCore MC0 THREADS snapshot lists encoded thread text"); - lz_svc_mc_companion_handle_line("MC0 5 SEND_PUBLIC text=mc0%20public", mc0, sizeof mc0, &mc0_exit); - CHECK(strstr(mc0, "MC0 5 OK accepted=1") != NULL, + lz_svc_mc_companion_handle_line("MC0 6 EVENTS mode=on types=nodes,messages,tx", mc0, sizeof mc0, &mc0_exit); + CHECK(strstr(mc0, "MC0 6 OK events=on") != NULL && + strstr(mc0, "types=nodes,messages,tx") != NULL, + "MeshCore MC0 EVENTS enables typed streaming"); + lz_core_on_mc_channel_text("CompanionPeer", "event hello", -7.5f); + char ev[360]; + lz_svc_mc_companion_drain_events(ev, sizeof ev); + CHECK(strstr(ev, "MC0 EVT") != NULL && strstr(ev, "rx_public") != NULL && + strstr(ev, "event%20hello") != NULL, + "MeshCore MC0 event drain emits enabled message events"); + lz_svc_mc_companion_handle_line("MC0 7 SEND_PUBLIC text=mc0%20public client_mid=pc-1", mc0, sizeof mc0, &mc0_exit); + CHECK(strstr(mc0, "MC0 7 OK accepted=1") != NULL && + strstr(mc0, "event_seq=") != NULL && + strstr(mc0, "client_mid=pc-1") != NULL, "MeshCore MC0 SEND_PUBLIC uses service boundary"); - lz_svc_mc_companion_handle_line("MC0 6 SEND_DM to_name=companionpeer text=mc0%20dm", mc0, sizeof mc0, &mc0_exit); - CHECK(strstr(mc0, "MC0 6 OK accepted=1") != NULL, + lz_svc_mc_companion_drain_events(ev, sizeof ev); + CHECK(strstr(ev, "tx_status") != NULL && strstr(ev, "client_mid=pc-1") != NULL, + "MeshCore MC0 SEND_PUBLIC emits queued tx_status event"); + lz_svc_mc_companion_handle_line("MC0 8 SEND_DM to_name=companionpeer text=mc0%20dm", mc0, sizeof mc0, &mc0_exit); + CHECK(strstr(mc0, "MC0 8 OK accepted=1") != NULL, "MeshCore MC0 SEND_DM uses service boundary"); - lz_svc_mc_companion_handle_line("MC0 7 EXIT", mc0, sizeof mc0, &mc0_exit); + lz_svc_mc_companion_handle_line("MC0 9 EVENTS mode=off", mc0, sizeof mc0, &mc0_exit); + CHECK(strstr(mc0, "MC0 9 OK events=off") != NULL, + "MeshCore MC0 EVENTS disables streaming"); + lz_svc_mc_companion_handle_line("MC0 10 EXIT", mc0, sizeof mc0, &mc0_exit); CHECK(mc0_exit && strstr(mc0, "state=detached") != NULL, "MeshCore MC0 EXIT returns to console"); lz_svc_mc_companion_selftest(proto, sizeof proto); diff --git a/src/mc_companion.cpp b/src/mc_companion.cpp index 14719b5..0c389f4 100644 --- a/src/mc_companion.cpp +++ b/src/mc_companion.cpp @@ -87,6 +87,9 @@ extern "C" void lz_mcc_usb_poll(void) g_mc_len = 0; } } + char events[700]; + if(lz_svc_mc_companion_drain_events(events, sizeof events) > 0) + Serial.print(events); } #endif /* LZ_TARGET_TDECK */ diff --git a/src/services/mesh.h b/src/services/mesh.h index 81cb0b1..2f01d67 100644 --- a/src/services/mesh.h +++ b/src/services/mesh.h @@ -380,6 +380,7 @@ int lz_svc_mc_companion_threads(char *buf, int n); bool lz_svc_mc_companion_send_public(const char *text); bool lz_svc_mc_companion_send_dm(const char *name, const char *text); int lz_svc_mc_companion_handle_line(const char *line, char *buf, int n, bool *exit_mode); +int lz_svc_mc_companion_drain_events(char *buf, int n); int lz_svc_mc_companion_selftest(char *buf, int n); /* ---- radio stats (airtime accounting) ---- */ diff --git a/src/services/mesh_core.c b/src/services/mesh_core.c index afd12ea..92b7c8e 100644 --- a/src/services/mesh_core.c +++ b/src/services/mesh_core.c @@ -77,6 +77,16 @@ static void (*g_dirty)(void); static uint32_t g_pkt_seq; static bool g_nodes_dirty; /* node DB has unsaved high-freq fields */ static uint32_t g_nodes_dirty_ms; /* tick when it first became dirty */ +static uint32_t g_mc_event_seq; /* MC0 companion resync/event anchor */ +static uint32_t g_mc_nodes_rev; /* MeshCore node snapshot revision */ +static uint32_t g_mc_messages_rev; /* MeshCore thread/message snapshot revision */ +static bool g_mc_events_enabled; +static char g_mc_event_types[40] = "none"; +#define MC0_EVENT_QUEUE 6 +#define MC0_EVENT_LINE 240 +static char g_mc_events[MC0_EVENT_QUEUE][MC0_EVENT_LINE]; +static uint8_t g_mc_event_head; +static uint8_t g_mc_event_count; /* placeholder until onboarding sets the real name (and MAC sets the num) */ static lz_identity_t g_id = { 0x7c3af1d0, "!7c3af1d0", "Node", "NODE" }; static bool g_have_identity; /* false until onboarding done */ @@ -97,6 +107,11 @@ static struct { } g_delivery[LZ_DELIVERY_PEND]; static lz_thread_rt *find_thread(uint32_t num); +static void mc0_note_node_change(const lz_node_rt *n); +static void mc0_note_message_change(const char *kind, const lz_thread_rt *t, + const char *text, bool self); +static void mc0_note_tx_status(const char *kind, const char *target, + const char *client_mid, const char *status); /* ---------- helpers ---------- */ @@ -1441,6 +1456,103 @@ static int mc0_append_pct(char *buf, int n, int pos, const char *s) return pos; } +static bool mc0_event_type_match(const char *want, const char *token) +{ + size_t tl = strlen(token); + const char *p = want ? want : ""; + while(*p) { + while(*p == ',' || *p == ' ') p++; + const char *s = p; + while(*p && *p != ',') p++; + if((size_t)(p - s) == tl && strncmp(s, token, tl) == 0) return true; + if((size_t)(p - s) == 3 && strncmp(s, "all", 3) == 0) return true; + } + return false; +} + +static bool mc0_event_enabled_for(const char *bucket) +{ + return g_mc_events_enabled && mc0_event_type_match(g_mc_event_types, bucket); +} + +static void mc0_event_push(const char *line) +{ + if(!line || !line[0]) return; + uint8_t slot; + if(g_mc_event_count < MC0_EVENT_QUEUE) { + slot = (uint8_t)((g_mc_event_head + g_mc_event_count) % MC0_EVENT_QUEUE); + g_mc_event_count++; + } else { + slot = g_mc_event_head; + g_mc_event_head = (uint8_t)((g_mc_event_head + 1) % MC0_EVENT_QUEUE); + } + snprintf(g_mc_events[slot], sizeof g_mc_events[slot], "%s", line); +} + +static void mc0_note_node_change(const lz_node_rt *n) +{ + if(!n || n->net != LZ_NET_MC) return; + uint32_t seq = ++g_mc_event_seq; + g_mc_nodes_rev++; + if(!mc0_event_enabled_for("nodes")) return; + + char line[MC0_EVENT_LINE]; + int pos = snprintf(line, sizeof line, "MC0 EVT %lu node_upsert addr=%s nodes_rev=%lu name=", + (unsigned long)seq, n->id, (unsigned long)g_mc_nodes_rev); + pos = mc0_append_pct(line, sizeof line, pos, n->name); + pos = buf_appendf(line, sizeof line, pos, " role="); + pos = mc0_append_pct(line, sizeof line, pos, n->role); + buf_appendf(line, sizeof line, pos, "\n"); + mc0_event_push(line); +} + +static void mc0_note_message_change(const char *kind, const lz_thread_rt *t, + const char *text, bool self) +{ + if(!t || t->net != LZ_NET_MC) return; + uint32_t seq = ++g_mc_event_seq; + g_mc_messages_rev++; + if(!mc0_event_enabled_for(self ? "tx" : "messages")) return; + + char line[MC0_EVENT_LINE]; + int pos = snprintf(line, sizeof line, "MC0 EVT %lu %s messages_rev=%lu kind=%s addr=%s ", + (unsigned long)seq, kind ? kind : "snapshot_dirty", + (unsigned long)g_mc_messages_rev, + t->is_channel ? "public" : "dm", t->addr); + if(self) { + pos = buf_appendf(line, sizeof line, pos, "status=sent text="); + } else { + pos = buf_appendf(line, sizeof line, pos, "from_name="); + pos = mc0_append_pct(line, sizeof line, pos, t->name); + pos = buf_appendf(line, sizeof line, pos, " text="); + } + pos = mc0_append_pct(line, sizeof line, pos, text); + buf_appendf(line, sizeof line, pos, "\n"); + mc0_event_push(line); +} + +static void mc0_note_tx_status(const char *kind, const char *target, + const char *client_mid, const char *status) +{ + uint32_t seq = ++g_mc_event_seq; + if(!mc0_event_enabled_for("tx")) return; + + char line[MC0_EVENT_LINE]; + int pos = snprintf(line, sizeof line, "MC0 EVT %lu tx_status kind=%s status=%s", + (unsigned long)seq, kind ? kind : "unknown", + status ? status : "queued"); + if(target && target[0]) { + pos = buf_appendf(line, sizeof line, pos, " target="); + pos = mc0_append_pct(line, sizeof line, pos, target); + } + if(client_mid && client_mid[0]) { + pos = buf_appendf(line, sizeof line, pos, " client_mid="); + pos = mc0_append_pct(line, sizeof line, pos, client_mid); + } + buf_appendf(line, sizeof line, pos, "\n"); + mc0_event_push(line); +} + static bool mc0_get_arg(const char *p, const char *key, char *out, int cap) { char tok[220]; @@ -1494,8 +1606,10 @@ static int mc0_hello(char *buf, int n, const char *id) { int pos = mc0_ok_prefix(buf, n, id); return buf_appendf(buf, n, pos, - "proto=0 fw=0.8-draft device=tdeck caps=identity,nodes,status,threads,send_public,send_dm,events,exit max_line=512 max_text=%d event_seq=0 nodes_rev=0 messages_rev=0\n", - LZ_TEXT_MAX); + "proto=0 fw=0.8-draft device=tdeck caps=identity,nodes,status,threads,send_public,send_dm,events,exit max_line=512 max_text=%d event_seq=%lu nodes_rev=%lu messages_rev=%lu\n", + LZ_TEXT_MAX, (unsigned long)g_mc_event_seq, + (unsigned long)g_mc_nodes_rev, + (unsigned long)g_mc_messages_rev); } static int mc0_identity(char *buf, int n, const char *id) @@ -1519,62 +1633,79 @@ static int mc0_status(char *buf, int n, const char *id) lz_backend_mc_addr(addr, sizeof addr); int pos = mc0_ok_prefix(buf, n, id); return buf_appendf(buf, n, pos, - "proto=0 mc=%s bridge=usb mc_companion=attached mt_companion=%s addr=%s nodes=%d threads=%d unread=%d public=%d dm=%d event_seq=0 nodes_rev=0 messages_rev=0\n", + "proto=0 mc=%s bridge=usb mc_companion=%s mt_companion=%s addr=%s nodes=%d threads=%d unread=%d public=%d dm=%d events=%s event_seq=%lu nodes_rev=%lu messages_rev=%lu\n", LZ_MESHCORE_ENABLED ? "on" : "disabled", + g_mc_events_enabled ? "streaming" : "attached", lz_mtc_active() ? "on" : "off", addr, nodes, threads, unread, lz_backend_mc_send_public ? 1 : 0, - lz_backend_mc_dm ? 1 : 0); + lz_backend_mc_dm ? 1 : 0, + g_mc_events_enabled ? "on" : "off", + (unsigned long)g_mc_event_seq, + (unsigned long)g_mc_nodes_rev, + (unsigned long)g_mc_messages_rev); } -static int mc0_nodes(char *buf, int n, const char *id) +static int mc0_nodes(char *buf, int n, const char *id, const char *args) { int count = mc0_count_nodes(); + char since_s[16]; + uint32_t since = mc0_get_arg(args, "since", since_s, sizeof since_s) + ? (uint32_t)strtoul(since_s, NULL, 10) : 0; + if(since && since >= g_mc_nodes_rev) count = 0; int pos = buf_appendf(buf, n, 0, - "MC0 %s BEGIN type=nodes rev=0 count=%d more=0 cursor=end\n", - id, count); - for(int i = 0; i < g_node_count; i++) { - const lz_node_rt *nd = &g_nodes[i]; - if(nd->net != LZ_NET_MC) continue; - uint32_t seen_ms = 0; - uint32_t now_s = now_epoch(); - if(nd->last_heard && now_s >= nd->last_heard) - seen_ms = (now_s - nd->last_heard) * 1000u; - pos = buf_appendf(buf, n, pos, "MC0 %s NODE addr=%s name=", id, nd->id); - pos = mc0_append_pct(buf, n, pos, nd->name); - pos = buf_appendf(buf, n, pos, " role="); - pos = mc0_append_pct(buf, n, pos, nd->role); - pos = buf_appendf(buf, n, pos, " seen_ms=%lu snr=%.1f dm=%s\n", - (unsigned long)seen_ms, (double)nd->snr, - mc_companion_dm_target(nd) ? "ready" : "not_messageable"); + "MC0 %s BEGIN type=nodes rev=%lu count=%d more=0 cursor=end\n", + id, (unsigned long)g_mc_nodes_rev, count); + if(count) { + for(int i = 0; i < g_node_count; i++) { + const lz_node_rt *nd = &g_nodes[i]; + if(nd->net != LZ_NET_MC) continue; + uint32_t seen_ms = 0; + uint32_t now_s = now_epoch(); + if(nd->last_heard && now_s >= nd->last_heard) + seen_ms = (now_s - nd->last_heard) * 1000u; + pos = buf_appendf(buf, n, pos, "MC0 %s NODE addr=%s name=", id, nd->id); + pos = mc0_append_pct(buf, n, pos, nd->name); + pos = buf_appendf(buf, n, pos, " role="); + pos = mc0_append_pct(buf, n, pos, nd->role); + pos = buf_appendf(buf, n, pos, " seen_ms=%lu snr=%.1f dm=%s\n", + (unsigned long)seen_ms, (double)nd->snr, + mc_companion_dm_target(nd) ? "ready" : "not_messageable"); + } } return buf_appendf(buf, n, pos, - "MC0 %s END type=nodes rev=0 count=%d more=0 cursor=end\n", - id, count); + "MC0 %s END type=nodes rev=%lu count=%d more=0 cursor=end\n", + id, (unsigned long)g_mc_nodes_rev, count); } -static int mc0_threads(char *buf, int n, const char *id) +static int mc0_threads(char *buf, int n, const char *id, const char *args) { int count = mc0_count_threads(NULL); + char since_s[16]; + uint32_t since = mc0_get_arg(args, "since", since_s, sizeof since_s) + ? (uint32_t)strtoul(since_s, NULL, 10) : 0; + if(since && since >= g_mc_messages_rev) count = 0; int pos = buf_appendf(buf, n, 0, - "MC0 %s BEGIN type=threads rev=0 count=%d more=0 cursor=end\n", - id, count); - for(int oi = 0; oi < g_thread_count; oi++) { - int idx = (oi < LZ_MAX_THREADS) ? g_order[oi] : -1; - if(idx < 0 || idx >= g_thread_count) continue; - const lz_thread_rt *t = &g_threads[idx]; - if(t->net != LZ_NET_MC) continue; - pos = buf_appendf(buf, n, pos, "MC0 %s THREAD addr=%s name=", id, t->addr); - pos = mc0_append_pct(buf, n, pos, t->name); - pos = buf_appendf(buf, n, pos, " kind=%s unread=%d last=%lu text=", - t->is_channel ? "public" : "dm", t->unread, - (unsigned long)t->last_ts); - pos = mc0_append_pct(buf, n, pos, t->last_text); - pos = buf_appendf(buf, n, pos, "\n"); + "MC0 %s BEGIN type=threads rev=%lu count=%d more=0 cursor=end\n", + id, (unsigned long)g_mc_messages_rev, count); + if(count) { + for(int oi = 0; oi < g_thread_count; oi++) { + int idx = (oi < LZ_MAX_THREADS) ? g_order[oi] : -1; + if(idx < 0 || idx >= g_thread_count) continue; + const lz_thread_rt *t = &g_threads[idx]; + if(t->net != LZ_NET_MC) continue; + pos = buf_appendf(buf, n, pos, "MC0 %s THREAD addr=%s name=", id, t->addr); + pos = mc0_append_pct(buf, n, pos, t->name); + pos = buf_appendf(buf, n, pos, " kind=%s unread=%d last=%lu text=", + t->is_channel ? "public" : "dm", t->unread, + (unsigned long)t->last_ts); + pos = mc0_append_pct(buf, n, pos, t->last_text); + pos = buf_appendf(buf, n, pos, "\n"); + } } return buf_appendf(buf, n, pos, - "MC0 %s END type=threads rev=0 count=%d more=0 cursor=end\n", - id, count); + "MC0 %s END type=threads rev=%lu count=%d more=0 cursor=end\n", + id, (unsigned long)g_mc_messages_rev, count); } static lz_node_rt *mc0_node_by_addr(const char *addr) @@ -1618,20 +1749,28 @@ static lz_node_rt *mc0_node_by_name_unique(const char *name, bool *ambiguous) static int mc0_send_public(char *buf, int n, const char *id, const char *args) { - char text[LZ_TEXT_MAX + 1]; + char text[LZ_TEXT_MAX + 1], client_mid[40]; if(!mc0_get_arg(args, "text", text, sizeof text)) return mc0_err(buf, n, id, "bad_request", false, "missing text"); if((int)strlen(text) > LZ_TEXT_MAX) return mc0_err(buf, n, id, "text_too_long", false, "text too long"); if(!lz_svc_mc_companion_send_public(text)) return mc0_err(buf, n, id, "send_failed", true, "public send failed"); + mc0_get_arg(args, "client_mid", client_mid, sizeof client_mid); + mc0_note_tx_status("public", "public", client_mid, "queued"); int pos = mc0_ok_prefix(buf, n, id); - return buf_appendf(buf, n, pos, "accepted=1 kind=public status=queued\n"); + pos = buf_appendf(buf, n, pos, "accepted=1 kind=public status=queued event_seq=%lu", + (unsigned long)g_mc_event_seq); + if(client_mid[0]) { + pos = buf_appendf(buf, n, pos, " client_mid="); + pos = mc0_append_pct(buf, n, pos, client_mid); + } + return buf_appendf(buf, n, pos, "\n"); } static int mc0_send_dm(char *buf, int n, const char *id, const char *args) { - char text[LZ_TEXT_MAX + 1], name[64], addr[32]; + char text[LZ_TEXT_MAX + 1], name[64], addr[32], client_mid[40]; if(!mc0_get_arg(args, "text", text, sizeof text)) return mc0_err(buf, n, id, "bad_request", false, "missing text"); if((int)strlen(text) > LZ_TEXT_MAX) @@ -1656,10 +1795,52 @@ static int mc0_send_dm(char *buf, int n, const char *id, const char *args) if(!lz_svc_mc_companion_send_dm(target->name, text)) return mc0_err(buf, n, id, "send_failed", true, "DM send failed"); + mc0_get_arg(args, "client_mid", client_mid, sizeof client_mid); + mc0_note_tx_status("dm", target->id, client_mid, "queued"); int pos = mc0_ok_prefix(buf, n, id); pos = buf_appendf(buf, n, pos, "accepted=1 kind=dm to_name="); pos = mc0_append_pct(buf, n, pos, target->name); - return buf_appendf(buf, n, pos, " status=queued\n"); + pos = buf_appendf(buf, n, pos, " status=queued event_seq=%lu", (unsigned long)g_mc_event_seq); + if(client_mid[0]) { + pos = buf_appendf(buf, n, pos, " client_mid="); + pos = mc0_append_pct(buf, n, pos, client_mid); + } + return buf_appendf(buf, n, pos, "\n"); +} + +static int mc0_events(char *buf, int n, const char *id, const char *args) +{ + char mode[12], types[40]; + if(!mc0_get_arg(args, "mode", mode, sizeof mode)) { + int pos = mc0_ok_prefix(buf, n, id); + return buf_appendf(buf, n, pos, + "events=%s types=%s event_seq=%lu nodes_rev=%lu messages_rev=%lu\n", + g_mc_events_enabled ? "on" : "off", g_mc_event_types, + (unsigned long)g_mc_event_seq, + (unsigned long)g_mc_nodes_rev, + (unsigned long)g_mc_messages_rev); + } + if(strcmp(mode, "on") == 0) { + g_mc_events_enabled = true; + if(mc0_get_arg(args, "types", types, sizeof types)) + snprintf(g_mc_event_types, sizeof g_mc_event_types, "%s", types); + else + snprintf(g_mc_event_types, sizeof g_mc_event_types, "nodes,messages,tx,status"); + } else if(strcmp(mode, "off") == 0) { + g_mc_events_enabled = false; + snprintf(g_mc_event_types, sizeof g_mc_event_types, "none"); + g_mc_event_head = 0; + g_mc_event_count = 0; + } else { + return mc0_err(buf, n, id, "bad_request", false, "bad events mode"); + } + int pos = mc0_ok_prefix(buf, n, id); + return buf_appendf(buf, n, pos, + "events=%s types=%s event_seq=%lu nodes_rev=%lu messages_rev=%lu\n", + g_mc_events_enabled ? "on" : "off", g_mc_event_types, + (unsigned long)g_mc_event_seq, + (unsigned long)g_mc_nodes_rev, + (unsigned long)g_mc_messages_rev); } int lz_svc_mc_companion_handle_line(const char *line, char *buf, int n, bool *exit_mode) @@ -1679,14 +1860,11 @@ int lz_svc_mc_companion_handle_line(const char *line, char *buf, int n, bool *ex if(strcmp(verb, "HELLO") == 0) return mc0_hello(buf, n, id); if(strcmp(verb, "IDENTITY") == 0) return mc0_identity(buf, n, id); if(strcmp(verb, "STATUS") == 0) return mc0_status(buf, n, id); - if(strcmp(verb, "NODES") == 0) return mc0_nodes(buf, n, id); - if(strcmp(verb, "THREADS") == 0) return mc0_threads(buf, n, id); + if(strcmp(verb, "NODES") == 0) return mc0_nodes(buf, n, id, p); + if(strcmp(verb, "THREADS") == 0) return mc0_threads(buf, n, id, p); if(strcmp(verb, "SEND_PUBLIC") == 0) return mc0_send_public(buf, n, id, p); if(strcmp(verb, "SEND_DM") == 0) return mc0_send_dm(buf, n, id, p); - if(strcmp(verb, "EVENTS") == 0) { - int pos = mc0_ok_prefix(buf, n, id); - return buf_appendf(buf, n, pos, "events=off types=none event_seq=0\n"); - } + if(strcmp(verb, "EVENTS") == 0) return mc0_events(buf, n, id, p); if(strcmp(verb, "EXIT") == 0) { if(exit_mode) *exit_mode = true; int pos = mc0_ok_prefix(buf, n, id); @@ -1695,6 +1873,22 @@ int lz_svc_mc_companion_handle_line(const char *line, char *buf, int n, bool *ex return mc0_err(buf, n, id, "unknown_command", false, "unknown MC0 command"); } +int lz_svc_mc_companion_drain_events(char *buf, int n) +{ + int pos = 0; + if(!buf || n <= 0) return 0; + buf[0] = 0; + while(g_mc_event_count && pos + 1 < n) { + const char *line = g_mc_events[g_mc_event_head]; + int len = (int)strlen(line); + if(pos && pos + len >= n) break; + pos = buf_appendf(buf, n, pos, "%s", line); + g_mc_event_head = (uint8_t)((g_mc_event_head + 1) % MC0_EVENT_QUEUE); + g_mc_event_count--; + } + return pos; +} + int lz_svc_mc_companion_selftest(char *buf, int n) { char out[900]; @@ -1707,7 +1901,11 @@ int lz_svc_mc_companion_selftest(char *buf, int n) lz_svc_mc_companion_handle_line("MC0 3 NODES", out, sizeof out, &exit_mode); ok = ok && strstr(out, "MC0 3 BEGIN type=nodes") != NULL && strstr(out, "MC0 3 END type=nodes") != NULL; - lz_svc_mc_companion_handle_line("MC0 4 EXIT", out, sizeof out, &exit_mode); + lz_svc_mc_companion_handle_line("MC0 4 EVENTS mode=on types=nodes,messages,tx", out, sizeof out, &exit_mode); + ok = ok && strstr(out, "events=on") != NULL && strstr(out, "types=nodes,messages,tx") != NULL; + lz_svc_mc_companion_handle_line("MC0 5 EVENTS mode=off", out, sizeof out, &exit_mode); + ok = ok && strstr(out, "events=off") != NULL; + lz_svc_mc_companion_handle_line("MC0 6 EXIT", out, sizeof out, &exit_mode); ok = ok && exit_mode && strstr(out, "state=detached") != NULL; return snprintf(buf, (size_t)n, "MeshCore MC0 protocol selftest: %s", ok ? "PASS" : "FAIL"); } @@ -1772,6 +1970,7 @@ void lz_core_on_nodeinfo(uint32_t from, const char *id, const char *long_name, if(!isnan(snr)) n->snr = snr; n->last_heard = now_epoch(); lz_store_save_nodes(g_nodes, g_node_count); + mc0_note_node_change(n); mark_dirty(); } @@ -1811,6 +2010,10 @@ static void mc_thread_append(lz_thread_rt *t, bool self, const char *text) touch_thread_meta(t, text, ts, !self && g_open != t); /* unread only for inbound */ reorder_threads(); lz_store_save_threads(g_threads, g_thread_count); + if(t->net == LZ_NET_MC) { + const char *kind = self ? "tx_status" : (t->is_channel ? "rx_public" : "rx_dm"); + mc0_note_message_change(kind, t, text, self); + } mark_dirty(); } @@ -2136,6 +2339,13 @@ void lz_svc_init(const char *datadir, bool seed_demo) if(LZ_MESHCORE_ENABLED) lz_svc_mc_channel_thread(); /* MeshCore Public always in Channels too */ track_stored_delivery(); reorder_threads(); + g_mc_event_seq = 0; + g_mc_nodes_rev = 0; + g_mc_messages_rev = 0; + g_mc_events_enabled = false; + snprintf(g_mc_event_types, sizeof g_mc_event_types, "none"); + g_mc_event_head = 0; + g_mc_event_count = 0; lz_backend_init(); } From 07a609686296e02edbf6869dc446fb39483ffe91 Mon Sep 17 00:00:00 2001 From: n30nex Date: Sat, 20 Jun 2026 08:58:13 -0400 Subject: [PATCH 2/2] Fix Windows artifact smoke tooling --- scripts/serial_harness.py | 5 +++++ scripts/tdeck_smoke.py | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/serial_harness.py b/scripts/serial_harness.py index 1e2cba4..77a6bab 100644 --- a/scripts/serial_harness.py +++ b/scripts/serial_harness.py @@ -12,6 +12,11 @@ import sys import time +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") +if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") + try: import serial from serial.tools import list_ports diff --git a/scripts/tdeck_smoke.py b/scripts/tdeck_smoke.py index 06d493d..4bb5e35 100644 --- a/scripts/tdeck_smoke.py +++ b/scripts/tdeck_smoke.py @@ -115,16 +115,16 @@ def nostub_upload(project_dir: Path, env_name: str, port: str, baud: int, artifa "--baud", str(baud), "--before", - "default-reset", + "default_reset", "--after", - "hard-reset", + "hard_reset", "--no-stub", - "write-flash", - "--flash-mode", + "write_flash", + "--flash_mode", "dio", - "--flash-freq", + "--flash_freq", "80m", - "--flash-size", + "--flash_size", "16MB", "0x0", str(bootloader),