Files
MeshTerm/scripts
Jean-Pierre MartineauandClaude Fable 5 84caae53f8 P3 rework: the PicoCalc visual language, done properly
The first P3 (a84abf3) ran on the wrong model and its palette never
reached the TUI: MESH_THEME_16 was written in 256-colour cube indices
instead of the 16 VT slots, and the platform theme pick was wired into
the scripted-CLI console while the real rasterizer stayed hardcoded to
truecolor. Replace it end to end:

- theme._VT_SLOTS: the canonical slot->RGB design (conventional hue
  families kept; slots 5/6 become the track/faint slates), MESH_THEME_16
  rewritten against color(0..15) with the VT's bold-as-brightness and
  dim-bank-background rules pinned by tests. /etc/vtrgb + a boot oneshot
  install it (console-font script), OSC fallback when kbd is absent.
- platforms.on_platform: set_platform now re-runs registered bindings,
  so theme, rasterizer and widgets swap implementations once per switch
  and hot paths read module globals - no per-call get_platform().
- The render-boundary fold (theme.fold_text): one str.translate pass
  (NFKD accent folds - the Terminus base has no accented Latin - plus
  symbol stand-ins and the width-padded emoji map), an embedded-SGR
  quantizer for canvas truecolor, and a ?-at-own-width catch-all that
  makes the no-wide-glyph short-circuit true by construction. Applied at
  the only two places every visible byte passes: render_to_ansi and
  MapCanvas.to_ansi_lines.
- ui/fontset.py freezes the installed font's inventory (dumped live);
  the gallery now asserts rendered picocalc ANSI carries no truecolor/
  256-colour SGR and no character outside it - the parity gate the
  first attempt faked by asserting a dataclass field.
- Font build: nine new 6x12 marks (ellipsis, warn, backspace, shift,
  gear, refresh, clock, crosshair, padlock) drawn into donor slots, with
  donor misses now fatal and a keeper canary (donating pi once erased
  Cyrillic pe - shared slot). Table re-dumped on-device: identical to
  fontset.py, 519 = 519.
- Type-based name colouring: registry moved to core/nodetypes (services
  no longer import ui), fed from monitor observations, heard_nodes and
  merge_contacts. Heat gradient quantizes to heat.* steps on picocalc.
- ui/marks.py: the P2-deferred constant relocation - widgets no longer
  drags map_render/mapcanvas/pathgraph for tuples and aliases.
- meshterm specimen: the visual language on one card through the real
  funnels - the on-device acceptance screen (JP's request).
- tools/chat: delivered/no-ack state uses the standard check/query
  marks, not the banned emoji pair.

Verified on the Lyra: font+palette installed, specimen framebuffer
captures in .claude/plans/. 1259 tests green, 13 xfailed (P6 list).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 12:48:07 -04:00
..

scripts

Device-side helpers for running MeshTerm on real hardware. Independent, self-contained targets live here:

  • Calculinux device (Luckfox Lyra / ClockworkPi PicoCalc) — one-time bring-up and the console font.
  • XIAO nRF52840 UART radio (xiao-radio/) — wire a Seeed XIAO + Wio-SX1262 to the Lyra's hardware UART and run MeshTerm against it. Full build → flash → setup guide with scripts. This is the way to give a PicoCalc a real MeshCore radio.
  • SPI-attached radio bridge (meshterm-spi-bridge) — expose a bare SPI LoRa chip to MeshTerm as if it were a companion device (uConsole AIO, Waveshare HATs).

Calculinux device (Luckfox Lyra / ClockworkPi PicoCalc)

Helpers for running MeshTerm on a Luckfox Lyra / ClockworkPi PicoCalc under Calculinux (the bare-framebuffer terminal target). They run on the device as root, not on your dev machine.

Script What it does
calculinux-setup.sh Complete one-time bring-up: opkg python packages, the Wi-Fi boot-scan kick, the deploy user, the read-only clone, the venv + pip install -e ., the login PATH, and the console font. Idempotent.
calculinux-console-font.sh Just the console font: builds/installs/persists the 512-glyph meshterm PSF (Terminus + braille + node glyphs + rounded frame corners + the list cursor). Called by the setup script; also runnable on its own after a MeshTerm update.

Usage

Copy the scripts/ folder to the device and, as root:

sh calculinux-setup.sh

Two prerequisites the setup script checks for but cannot embed:

  • Deploy key — place the read-only GitHub deploy key at $HOME/.ssh/id_ed25519 (mode 600) before running; the clone uses it over SSH.
  • Wi-Fi credentials — the kick only nudges a network iwd already knows. Either connect once by hand (iwctl station wlan0 connect <SSID>) or export WIFI_SSID and WIFI_PSK so the script writes the iwd config (secrets stay in your environment, never in the repo).

Both scripts are POSIX sh, idempotent, and safe to re-run.

XIAO nRF52840 UART radio — give a PicoCalc a real radio

A Seeed XIAO nRF52840 + Wio-SX1262 wired to the Luckfox Lyra's hardware UART1, running MeshCore companion firmware over Serial1 (D6/D7). MeshTerm talks to it directly on /dev/ttyS1 — no bridge process. Everything (parts, wiring, firmware build/flash, device setup, the why) is in xiao-radio/README.md, with scripts for each step:

Script Runs on What it does
xiao-radio/build-firmware.sh dev machine clone + patch MeshCore, build the _usb env, emit the .uf2
xiao-radio/flash.py dev machine flash the .uf2 to the XIAO via its UF2 bootloader
xiao-radio/lyra-setup.sh the Lyra (root) boot-persistent UART1→GP4/GP5 mux service, dialout, default MeshTerm profile
xiao-radio/uart1-mux.py the Lyra the matrix-IO register poke that routes UART1 to the header pads

Two things this depends on, both handled by the patch/guide: the firmware must bind the companion to Serial1 and move I²C off D6/D7 (MeshCore maps it there and it steals the UART pins); and MeshTerm must include the platform-UART liveness fix (serial_port_present treats a soldered /dev/ttyS1 as present).

meshterm-spi-bridge — expose an SPI-attached radio

Some boards wire the LoRa chip straight to the host's SPI bus — the ClockworkPi uConsole with the hackergadgets AIO, Waveshare LoRa HATs, and similar. There is no companion microcontroller and no firmware to flash: the mesh node runs in software on the host via the pymc_core library (on the uConsole this arrives with the meshcore-uconsole apt package, which also ships the meshcore-console GUI).

MeshTerm, however, speaks the MeshCore companion protocol over serial / BLE / TCP and expects to talk to a node, not to drive a bare radio. This bridge fills the gap: it runs the pymc_core node itself and puts the standard companion protocol in front of it on a local TCP port, so that

meshterm --tcp 127.0.0.1:5000

connects exactly as if a real companion device were plugged in.

Usage

Copy meshterm-spi-bridge onto the radio host and run it with any Python 3 — it finds the pymc_core runtime and re-execs the bridge under it for you:

./meshterm-spi-bridge          # interactive setup menu
./meshterm-spi-bridge --check  # run prerequisite checks and exit
./meshterm-spi-bridge --run    # run the bridge now, in this terminal

The menu offers:

  1. Run now — runs in the foreground; Ctrl+C stops it.
  2. Install as a service — a systemd --user unit that starts on boot and restarts on failure, running as you (so it uses your node identity).
  3. Uninstall — removes only the bridge service + its helper copy. It never touches the meshcore-console / pymc_core packages.

Caveats

  • Only one program may use the radio at a time. Run either the meshcore-console GUI or this bridge — never both. The bridge preflight warns you when the GUI is running.
  • The bridge and MeshTerm are two cooperating processes; keep the bridge running while you use MeshTerm.
  • Same identity as the GUI: when the meshcore-console stack is present, the bridge loads the node key from ~/.local/share/meshcore-uconsole/identity.key, so MeshTerm sees the same node. On a generic pymc_core-only install it mints and persists its own key under ~/.local/share/meshterm-spi-bridge/.
  • Trace is not wired yet. Messaging, contacts, adverts and config work today; the node's trace / path-discovery replies aren't yet pushed to the companion server, so MeshTerm's Trace screen won't populate until that's added.

Overriding hardware pins

Defaults match the hackergadgets uConsole AIO. For other SPI boards, set env vars before launching (read by the direct-pymc_core path): MESHCORE_BUS_ID, MESHCORE_CS_ID, MESHCORE_RESET_PIN, MESHCORE_BUSY_PIN, MESHCORE_IRQ_PIN, MESHCORE_FREQUENCY, MESHCORE_TX_POWER, MESHCORE_SPREADING_FACTOR, MESHCORE_BANDWIDTH, MESHCORE_CODING_RATE. To force a specific interpreter, set MESHTERM_PYMC_PYTHON.