pymc_core was renamed openhop_core in 2026 and the current meshcore-uconsole
package depends on the new name, so a bridge that only knew the old one failed
its own preflight on a fresh install. It now finds either runtime, newest first
(an interpreter with openhop_core beats an earlier candidate with pymc_core;
MESHTERM_PYMC_PYTHON still overrides), imports through the name it found, and
lets two of its three compat shims stand down under openhop_core, which accepts
long ACK payloads and pushes completed trace replies itself; pushing a trace
twice was the alternative. The raw RX-log push is still the bridge's.
What the port found on the device shaped the rest. The 1.12.0 .deb is built for
trixie against Python 3.13 and cannot run on a bookworm uConsole, so the bridge
looks first in a venv of its own (~/.local/share/meshterm-spi-bridge/venv, made
with openhop-core[hardware]) and, without the GUI's helpers there, configures
the radio itself with the uConsole AIO preset rather than the runtime's
Waveshare defaults (TX-enable on pin 6, no DIO2 switch, no TCXO -- deaf on the
AIO), every MESHCORE_* knob the GUI reads honoured and passed only where the
constructor accepts it. It also loads the GUI's identity key first, so a bridge
in its own venv is still the same node. Verified on the uConsole: openhop-core
1.1.3, radio up, 159 contacts restored, info and contacts answered over TCP,
and the boot service now runs from that venv. The manual and the index say all
of this instead of the rename warning.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A Linux VT loads one font for the whole console, so asking for the 6x8 build
(53x40, fourteen rows more) changes what the shell is drawn in too. The new
console_font preference (6x12 default, 6x8 the alternative, offered on the
PicoCalc only, round-tripping through preferences.yaml on every platform)
therefore comes with services/consolefont.py: remember the font the shell had
with `setfont -O`, apply the one the preference names, and restore it on every
way out -- the menu's finally and an atexit hook, since ^Q and an unhandled
error leave by other doors. Verified on the device that setfont -O captures
the current font with its unicode table and that feeding it back reproduces it
exactly, with no sudo, because the VT is the process's own controlling
terminal. Only the interactive menu switches; a scripted run never touches the
console it was invited into. Changing the preference on its page applies live.
Shipping both fonts needed the licence untangled: the 6x8 base is the Linux
kernel's font_6x8 bitmap, GPL-2.0, so its build moves into
calculinux-console-font-6x8.sh under that licence with the GPLv2 text beside it
in scripts/LICENSE.GPL-2.0. The shared generator stays in the 6x12 script
between BEGIN/END markers and the 6x8 script executes that block, so the marks,
donors and aliases the font contract pins exist in one place. The produced PSF
is a data file the kernel console reads; MeshTerm never links or bundles it, and
NOTICE and the hardware manual say so. PrefSpec gains a platforms gate for the
page; the labels use an ASCII x because U+00D7 is not in the console font.
The 6x8 base has no Cyrillic; the manual says so and the owner accepts it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/hardware.md is the single place a reader goes to put MeshTerm on a
PicoCalc or a uConsole, written in the shape of the command-line manual: what
the three targets are and which exclude each other, why the console font is
built on the device, every phase of the Calculinux bring-up and its knobs, the
XIAO UART radio from parts to a verified link, the SPI bridge with an honest
note that it was written against pymc_core 1.0.x while today's uConsole stack
depends on the renamed openhop-core, and a closing list of what has only ever
been proven on one bench. scripts/README.md and xiao-radio/README.md become
short indexes pointing at it; the old ones were stale in four places (trace
through the bridge works, the firmware env is _serial, two scripts were
unlisted). README links the manual, gains the two dependency rows it lacked
(markdown-it-py, PyYAML) and a fixed fonts anchor.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every device address, login, key path, password and local venv path in the
.claude/ skills moves to dev.env at the repo root: gitignored, with a tracked
dev.env.example showing the shape. The skills now name $DEV_* keys and say to
read dev.env first, so they still run -- against whoever's hardware.
Two of the values were not secrets so much as a wrong assumption. The device
paths under that home directory are now $HOME-relative in run_instrumented.sh,
instrument.py and drive_console.py, and the deploy docs point at $DEPLOY_USER
and $KEY_PATH, which those scripts already parameterised. Hardcoding a home
directory was only ever correct for one machine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Lyra has no battery-backed RTC, so every cold boot starts at the
kernel's build-time epoch until something sets it -- wrong for
heard-node ages, TTL, and the SQLite observation log until then.
Adds time-sync.service alongside wifi-kick.service: a oneshot unit
that waits for a real default route, then steps the clock via
whichever NTP client the image ships (chronyd/ntpd/sntp), falling
back to an HTTPS response's Date header if none is present.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Complete, reproducible bring-up for giving a PicoCalc (Luckfox Lyra, Calculinux) a
MeshCore radio via a Seeed XIAO nRF52840 + Wio-SX1262 on the Lyra's hardware UART1:
- README.md -- parts, wiring, build -> flash -> setup, the why, troubleshooting
- build-firmware.sh -- clone + patch MeshCore, build the _usb env, emit the .uf2
- flash.py -- flash the .uf2 via the XIAO UF2 bootloader (Win/Linux/Mac)
- lyra-setup.sh -- boot-persistent UART1->GP4/GP5 mux service, dialout, profile
- uart1-mux.py -- the RK3506 matrix-IO register poke that routes UART1
- meshcore-uart1.patch -- the two firmware edits (Serial1 bind; I2C off D6/D7)
The two firmware edits are both required: bind the companion to hardware UART1
(Serial1 = D6/D7) instead of USB, and move I2C off D6/D7 (MeshCore maps it there and
sensors.begin() would otherwise seize the UART pins). Also documented in the top-level
scripts/README.md alongside the Calculinux and uConsole-SPI targets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sequences the full Lyra/PicoCalc bring-up: opkg python packages, the
rtl8xxxu Wi-Fi boot-scan kick (installed verbatim from the working device),
the deploy user, the read-only SSH clone, the venv + editable install with
TMPDIR off the RAM tmpfs, the login PATH, and the console font. Idempotent;
secrets (deploy key, Wi-Fi PSK) are checked for or read from the environment,
never embedded. README documents both scripts and their run order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose an SPI-attached MeshCore radio (uConsole AIO, Waveshare HATs) to
MeshTerm over the companion protocol on local TCP. A single stdlib-only
file that is both a plain-text setup menu and the bridge process: it finds
the pymc_core runtime and re-execs the bridge under it, offers run-now /
install-as-a-service / uninstall, and can add a [profiles.bridge] block so
the endpoint shows up in MeshTerm's device picker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>