echo6-docs/vault/projects/meshcore-transport.md
echo6-autocommit ef8b1e0bd9 auto: docs sync 2026-07-13T12:00:23+00:00
Files changed: engine/.embcache.json engine/changelog.md engine/lint-report.md vault/.trash/2026-06-19.md vault/docs/hardware/environment.md vault/docs/hardware/ip-allocation.md vault/docs/matrix/archivist.md vault/docs/matrix/matrix_host.md vault/docs/matrix/mautrix_signal.md vault/docs/matrix/synapse.md vault/docs/matrix/synapse_retention_discovery.md vault/docs/navi/cc-rules.md vault/docs/navi/deployment.md vault/docs/navi/themes.md vault/docs/services/ots-setup.md vault/docs/services/services.md vault/docs/services/usenet.md vault/docs/software/authentik.md vault/docs/software/caddy.md vault/docs/software/central.md vault/docs/software/dns.md vault/docs/software/geo-tools.md vault/docs/software/navi.md vault/docs/software/recon.md vault/docs/software/searxng.md vault/glossary.md vault/notes/echo6-landing-page-data-export.md vault/notes/ia-download-queue.md vault/projects/advbbs-project.md vault/projects/argus.md vault/projects/deploy-livesync.md vault/projects/fleet-patch-audit.md vault/projects/fleet-platform-baseline.md vault/projects/matrix-synapse-deployment.md vault/projects/meshai-config-hot-apply.md vault/projects/meshai-region-routing-plan.md vault/projects/meshai.md vault/projects/meshcore-transport.md vault/projects/meshtastic-headscale-runbook.md vault/projects/mmud-project.md vault/projects/nominatim-v5-reimport.md vault/runbooks/add-peertube-channel.md vault/runbooks/authentik-access-groups.md vault/runbooks/authentik-create-invitation.md vault/runbooks/authentik-oidc-application.md vault/runbooks/authentik-upgrade.md vault/runbooks/central-deploy-cutover.md vault/runbooks/ct-runbook.md vault/runbooks/edge2-access-reference.md vault/runbooks/expose-service-contabo.md vault/runbooks/expose-service-edge2.md vault/runbooks/expose-service-home.md vault/runbooks/fleet-magicdns-resolved-migration.md vault/runbooks/headless-browser-page-verification.md vault/runbooks/headscale-oidc-boot-order.md vault/runbooks/headscale-onboard-node.md vault/runbooks/ia-cli-reference.md vault/runbooks/ia-download-mirror.md vault/runbooks/idahomesh-bridge-setup.md vault/runbooks/idahomesh-vpn-device-setup.md vault/runbooks/lxc-service-migration.md vault/runbooks/mailcow-create-mailbox.md vault/runbooks/meshai-prod-compose-override.md vault/runbooks/meshmonitor-password-reset.md vault/runbooks/meshtastic-sidecar-node.md vault/runbooks/meshtasticd-sim-nodes-runbook.md vault/runbooks/nordvpn-lxc.md vault/runbooks/peertube-remote-runner.md vault/runbooks/pg-backup.md vault/runbooks/pi-nas-omv-runbook.md vault/runbooks/pipeline-patterns.md vault/runbooks/proxmox-create-ubuntu-vm.md vault/runbooks/proxmox-onboard-node.md vault/runbooks/pymc-repeater-kiss-tnc-reenumeration.md vault/runbooks/recon-operations.md vault/runbooks/recon-service-integration.md vault/runbooks/syncthing-add-node.md vault/runbooks/toc-cortex-pve9.2-update.md vault/session-resume/SESSION-HANDOFF-meshai-test.md
2026-07-13 12:00:23 +00:00

126 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: MeshCore transport for meshai (dual Meshtastic + MeshCore)
type: project
tags:
- mesh
aliases:
- meshai meshcore transport
- meshai meshcore
- MeshCore transport for meshai
related:
- [[meshai]]
- [[meshai-region-routing-plan]]
- [[meshai-prod-compose-override]]
- [[SESSION-HANDOFF-meshai-test]]
- [[meshai-config-hot-apply]]
updated: 2026-07-13
status: proposed
---
# MeshCore transport for meshai (dual Meshtastic + MeshCore)
Design for adding MeshCore as a second mesh transport to the [[meshai]] LLM mesh assistant ([[meshai]]), running alongside Meshtastic.
## Status
Proposed / design — not yet built. Decisions below are agreed with Matt; three prerequisites remain open (hardware, dependency, branch timing — see Open items).
## Goal
Let meshai speak both **Meshtastic** and **MeshCore**, config-selectable as either or **both at once**. Target: full two-way parity (outbound announcements AND inbound interactive LLM replies) on both networks simultaneously.
## Key decisions (agreed)
1. **Uniform messages across both networks.** Every outbound message is formatted ONCE, sized to fit the SMALLER radio's budget (MeshCore, ~120 chars), and sent identically to both meshes. No per-network rendering, no divergent content. This is deliberately chosen over per-transport rendering (which was judged fragile: it would make every message formatter budget-polymorphic, double the test surface, run composition per-transport, and produce divergent content).
2. **Run mode: both simultaneously** — send every announcement to Meshtastic AND MeshCore. "Both" = the existing Meshtastic path (meshai over IP to MeshMonitor's vnode fronting the radio) + a **MeshCore Companion node reachable over IP/TCP**, paralleling that same all-over-IP model. The MeshCore radio need NOT be on the meshai host — it just needs to be network-reachable.
3. **The transport is the only thing that knows its size and the only place that fragments a message** (single source of truth for size = `transport.max_chars`; kill the 4 hard-coded 200s + the stray [:195]). Because messages are pre-fit to the smaller budget, both radios normally send a single packet; the transport's (k/N) splitter is a safety net.
4. **Network awareness is per-network, not uniform.** Outbound *announcements* (weather/fire/511/alerts) are outside-world info → uniform to both. But *network-status* answers from the LLM ("who's on the mesh / mesh health") are inside-the-network info and must be scoped to whichever network the question arrived on. A MeshCore user gets MeshCore answers; a Meshtastic user gets the existing MeshView/MeshMonitor-based answers.
## Character limits
- Meshtastic text payload: ~200237 bytes (meshai currently budgets 200).
- MeshCore: hard cap `MAX_TEXT_LEN = 160 bytes`; usable channel-message text is ~120150 chars (channel messages embed `<sender name>: ` + timestamp/flags), docs round to "~133". No auto-chunking in the companion chat path — over-length text is hard-truncated, so meshai must split itself.
- Practical uniform budget when both are active: ~120 chars.
## Companion vs client (how we connect to MeshCore)
MeshCore firmware is role-specific (flashed, not runtime): **Companion**, **Repeater**, **Room Server**. The **Companion** node is the one a computer attaches to and drives — analogous to Meshtastic's phone+node model. meshai attaches to a Companion-firmware radio via the official `meshcore` Python lib (asyncio). **TCP to a Companion node is the natural fit** here — either native-TCP MeshCore firmware or a `ser2net`/serial-to-IP bridge — matching meshai's all-over-IP [[deployment]] (the Meshtastic side already runs over IP to MeshMonitor's vnode). A USB-on-host radio is not how this deployment works. Avoid BLE on Linux. Broadcast to a channel with `send_chan_msg(index, text)`; channel 0 = "Public" (well-known PSK) = the broadcast primitive. Do NOT attach to a Repeater or Room Server for messaging.
## Connecting to MeshCore via pyMC (companion TCP frame server)
- meshai's MeshCore link will be a **TCP connection to the pyMC repeater's companion frame server**, not a directly-attached radio. "pyMC" = **pyMC_core** (Python MeshCore library) + **pyMC_Repeater** (the repeater daemon, also branded "openHop"), which runs on **aida-nebra** as `pymc-repeater.service` (node EchoBase, RAK4631). pyMC_core includes a `CompanionFrameServer` that exposes the MeshCore **Companion Protocol frames over a TCP socket** — so a companion client connects over TCP as if to a radio.
- **Wire compatibility is confirmed:** the `meshcore` Python lib's TCP transport (`tcp_cx.py`) is byte-for-byte compatible with pyMC's frame server — outbound `>`(0x3E)+LE16+data, inbound `<`(0x3C)+LE16+data. So meshai connects with `MeshCore.create_tcp(host, port)`; no framing shim needed.
- **Target:** meshai connects to the companion identity named **AIDA** on the pyMC repeater — aida-nebra at **100.64.0.9** over Tailscale, **port 5050** (the AIDA companion on aida-nebra; pyMC's out-of-box default is 5000). The repeater's radio node is **EchoBase**; **AIDA** is the companion frame-server identity meshai attaches to (set under `identities.companions[]` in the repeater's config). The frame server is confirmed enabled. No authentication on the frame server. Endpoint: `100.64.0.9:5050`.
- **⚠️ Single-client gotcha (important):** pyMC's companion frame server is **single-client** — a new connection **evicts** the existing one. So meshai and MeshMonitor (which we also want on MeshCore for network awareness) **cannot share one companion frame server** — they'd kick each other off. Resolution: pyMC's config supports **multiple companion identities** (`identities.companions[]` is a list), each on its own `tcp_port` — run a **dedicated companion identity/port for meshai** and a separate one for MeshMonitor. (This mirrors the Meshtastic side, where meshai and MeshMonitor coexist via MeshMonitor's vnode.) meshai's companion endpoint is the **AIDA** identity at `100.64.0.9:5050` — if MeshMonitor also needs MeshCore awareness it must use a **separate** companion identity/port so the two don't evict each other.
## Network awareness: MeshCore vs Meshtastic
- **Meshtastic (today):** rich, global, passive picture via MeshView + MeshMonitor (fed by Meshtastic's MQTT firehose).
- **MeshCore:** no MQTT firehose and no passive "every node ever heard" nodeDB — by design (privacy/routing model). Awareness is LOCAL/contact-scoped and mostly PULL-based.
- **Convenient fit:** MeshMonitor (the same tool meshai already uses for Meshtastic) supports MeshCore as a first-class source since v4.5+, over USB/TCP, with a REST API (`/api/nodes`). So MeshCore awareness reuses the existing MeshMonitor pattern. MeshMonitor is already [[central]] to meshai's mesh connectivity — meshai's Meshtastic link itself runs over IP through MeshMonitor's vnode — which makes reusing MeshMonitor for MeshCore awareness an especially natural fit.
- **What we CAN give a MeshCore user's LLM query:** contact roster (name, node type, last-advert, position if shared, known path/hops via `get_contacts()`), per-message SNR/RSSI, own device telemetry, on-demand telemetry from other nodes (`req_telemetry`/`req_status`, Cayenne LPP), trace/path discovery. Repeater stats (uptime/airtime/neighbors) only if the operator enabled guest access.
- **What's missing vs Meshtastic:** no global/passive view, no firehose, advert SNR/position not inline, companion has no neighbor table (only Repeaters do).
- **Verdict:** less than Meshtastic's effortless global view, but a real local picture — plan MeshCore awareness around active polling of a curated contact/repeater set, not passive ingestion.
## Architecture & code seams (current meshai)
- Today: one concrete `MeshConnector` (`meshai/connector.py`) hard-wired to the `meshtastic` package (serial/TCP; no BLE), imported directly by `main.py`, `router.py`, `responder.py`, `channels.py`, and the pipeline. No transport interface.
- Send path: `MeshConnector.send_message()``interface.sendText(...)`; broadcast = `BROADCAST_NUM` on a channel index.
- Inbound: meshtastic pubsub → `_on_receive` builds a `MeshMessage` (carries a meshtastic-specific `packet` dict) → `set_message_callback``MeshAIBot._on_message``MessageRouter`.
- Size assumptions duplicated in 4 places: `chunker.py` (`max_chars=200`), `MeshRenderer` + its `(k/N)` splitter (`renderers/mesh.py`), digest accumulator (`pipeline/digest.py`), and the newer `single_packet_max_chars` (`adapter_config/defaults.py`, used by `central/nws_handler.py`); plus a stray `[:195]` in `notifications/router.py`.
- Config: dataclasses in `meshai/config.py` (`ConnectionConfig`), loaded from `meshtastic.yaml` via `config_loader.py`. Exactly one connector is built in `main.py`.
## meshai config: the MeshCore menu
- There is **no MeshCore code in meshai yet** (greenfield; nothing to reconcile).
- meshai config is **two-layer**: (1) file — `meshai/config.py` `ConnectionConfig` dataclass (`type` serial|tcp, `tcp_host`, `tcp_port`), loaded from `meshtastic.yaml` via `config_loader.py`; (2) **live dashboard** — a React Config page (`dashboard-frontend/src/pages/Config.tsx`) with a "Connection" section, saved at runtime via `PUT /config/{section}` (`dashboard/api/config_routes.py`), with a restart banner when needed.
- The **MeshCore menu** = a new sub-form in the dashboard's Connection section (plus a matching config block). Fields it needs:
- transport enable / select (meshtastic | meshcore | both)
- `connection_type: tcp` (the pyMC path is TCP; BLE/serial exist in the lib but aren't used here)
- `host` (aida-nebra, `100.64.0.9`), `port` (`5050` — the AIDA companion on aida-nebra; pyMC's out-of-box default is 5000)
- `channel_index` (index into the companion's channel table — required to send/receive on a public channel)
- `channel_name` (optional label)
- `channel_psk` / secret — **store in `credentials`, NOT in this doc or any graph-tracked file**; only needed if meshai must provision the channel rather than relying on one pre-configured on the repeater
- `max_chars` (MeshCore per-chunk cap — est. ~130150; verify against companion max)
- `auto_reconnect` + `max_reconnect_attempts` (passed to `create_tcp`)
- optional self/node name for the companion identity
## Implementation plan (phased)
- **Phase 1 — Transport abstraction (pure refactor, testable without new hardware).** Extract `MeshTransport` ABC (`connect/disconnect/send_message/set_message_callback/my_node_id/connected/get_node_name/get_node_position`). Rename `MeshConnector``MeshtasticTransport` (behavior identical; assert byte-identical output in tests). Generalize `MeshMessage` (drop meshtastic-only `packet`; add a `transport` tag + generic reply-address token). Config-driven transport factory. Existing tests stay green.
- **Phase 2 — MeshCoreTransport (in + out).** Wrap the `meshcore` lib behind the same sync surface via a dedicated event-loop thread. Outbound `send_chan_msg` for broadcast, DM-by-pubkey for replies. Inbound: MeshCore channel/contact events → normalized `MeshMessage` → same callback. Contact/pubkey handling from adverts; `my_node_id` equivalent = our node pubkey (self-filter).
- **Phase 3 — Uniform sizing + one splitter.** Consolidate the 4 size sites into `transport.max_chars`; message formatting targets the minimum active budget (~120) so output is uniform; transport does final (k/N) safety split. Remove the stray [:195].
- **Phase 4 — CompositeTransport ("both") + reply routing.** Fan `send_message` to both children; merge both inbound streams; each reply goes back over the transport it arrived on (via the `MeshMessage.transport` tag).
- **Phase 5 — Per-network awareness + config + deploy.** Scope LLM network-status context to the originating network (MeshCore via MeshMonitor REST, mirroring the Meshtastic path). Add `transport: meshtastic|meshcore|both` + a `meshcore` config section (serial/tcp, channel index/PSK, max_chars). Add the `meshcore` pip dependency to the image. Deploy to CT 108 once the second radio is attached; smoke-test both meshes.
## Open items / prerequisites
1. **Hardware:** a **MeshCore Companion node reachable over IP/TCP** (native-TCP firmware or a `ser2net`/serial-to-IP bridge). Location is flexible — it need NOT be the meshai host — and no USB/udev pinning is required since the connection is over the network. "Both" can't run live until this exists — Phases 14 can be built/unit-tested without it.
2. **Dependency:** adds the `meshcore` pip package to the meshai image (needs approval per no-deps-without-permission policy).
3. **Timing:** Phase 1 touches core files (`connector.py`, `main.py`, `router.py`, `responder.py`, `channels.py`) near the in-flight `fix/fire-spam-drain-pacer` work; land this AFTER that branch merges to main to avoid a refactor conflict on the live host.
4.**RESOLVED — pyMC companion frame server:** confirmed **enabled** on aida-nebra, on **port 5050**, companion identity **AIDA** (repeater radio node EchoBase, RAK4631).
5.**RESOLVED — aida-nebra Tailscale IP = `100.64.0.9`** (confirmed authoritatively from Tailscale: `tailscale status`, `tailscale ip -4 aida-nebra`, MagicDNS `aida-nebra.echo6.mesh`, and `getent hosts` all agree). The old 100.64.0.18 figure is wrong.
6. **Single-client separation (still open):** ensure meshai's **AIDA** companion identity/port (`100.64.0.9:5050`) is not also used by MeshMonitor. If MeshMonitor needs MeshCore awareness, it must use a **separate companion identity/port** so the two don't evict each other.
7. Map the repeater's **channel table** (which index = which named channel/PSK).
8. Confirm **MeshCore max text length** to set `max_chars`.
## Risks
- Async bridge (meshcore asyncio ↔ meshai sync send).
- Reply routing correctness (right mesh).
- MeshCore firmware/lib version churn — pin the `meshcore` lib + Companion firmware together; constants (`MAX_TEXT_LEN`, opcodes) are firmware-defined.
- Uniform-to-smaller means Meshtastic messages don't use their extra capacity (accepted tradeoff for uniformity).
## References
- meshcore Python lib: github.com/meshcore-dev/meshcore_py ; pypi.org/project/meshcore
- Companion protocol: docs.meshcore.io/companion_protocol/ ; firmware: github.com/meshcore-dev/MeshCore
- MeshMonitor MeshCore support: meshmonitor.org/features/meshcore.html
- Roles overview: nodakmesh.org/blog/meshcore-how-it-works-guide
- pyMC_core (companion frame server): github.com/pyMC-dev/pyMC_core — src/pymc_core/companion/frame_server.py
- pyMC_Repeater / openHop: github.com/rightup/pyMC_Repeater
- meshcore_py TCP transport: github.com/meshcore-dev/meshcore_py — src/meshcore/tcp_cx.py
## Deploy context
meshai runs on CT 108 (Tailscale 100.64.0.32) as a docker compose build in /home/zvx/meshai/work/ — NOT the Central service on CT 104. Mesh connectivity is all over IP: no radio is attached to CT 108. The Meshtastic link runs over IP to MeshMonitor's vnode (which fronts the radio), and the planned MeshCore link is likewise a Companion node reached over TCP — so neither transport requires hardware on the meshai host. See the deploy topology note.