From c0e7c17252a78879709209d4f4803dd7cfcec2d3 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Tue, 6 Oct 2026 21:44:55 -0400 Subject: [PATCH] The guides and the device manuals are in ASD-STE100 The four user guides and the manuals for the PicoCalc, the uConsole, and the Cardputer Zero. Procedures are numbered steps, with a WARNING or a CAUTION before each step that has a risk. The step headings use a colon, and each link to them changed with them. Quoted error messages are in quotation marks, and the menu question is quoted as the app shows it. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/devices/README.md | 163 ++--- docs/devices/cardputer-zero.md | 209 +++--- docs/devices/picocalc-lyra.md | 1091 +++++++++++++++++--------------- docs/devices/uconsole.md | 670 ++++++++++---------- docs/guide/configuration.md | 197 +++--- docs/guide/connecting.md | 163 ++--- docs/guide/features.md | 113 ++-- docs/guide/terminals.md | 63 +- 8 files changed, 1425 insertions(+), 1244 deletions(-) diff --git a/docs/devices/README.md b/docs/devices/README.md index 6dde0a4..81d652a 100644 --- a/docs/devices/README.md +++ b/docs/devices/README.md @@ -1,66 +1,74 @@ # MeshTerm on hardware -MeshTerm talks to a **node** — a MeshCore radio that speaks the companion protocol over -serial, Bluetooth LE, or TCP. Plug a companion board into USB, or pair one over Bluetooth, -and none of this page applies: MeshTerm finds it, and the [README](../../README.md) and the -[command line manual](../cli/README.md) are all you need. +MeshTerm talks to a **node**. The node is a MeshCore radio that speaks the companion +protocol over serial, Bluetooth LE, or TCP. If you plug a companion board into USB, or pair +one over Bluetooth, nothing on this page applies. MeshTerm finds the companion. For this +case, read the [README](../../README.md) and the [command line manual](../cli/README.md). -This page is for the hardware that *doesn't* present a node that way. Each such device has -its own step-by-step manual, and this page only tells you which one is yours. +This page is for the hardware that does not present a node in that way. Each such handheld +has its own manual with numbered steps. This page tells you which manual is yours. ## Which guide is yours | I have… | Read | | --- | --- | -| a ClockworkPi **PicoCalc**, stock or already running Calculinux on a Luckfox Lyra, and I want MeshTerm on its own screen | **[MeshTerm on the PicoCalc](picocalc-lyra.md)** — from the shopping list to a working machine | -| …and I want a **radio** inside it | the same manual, [Phase 3](picocalc-lyra.md#phase-3--add-the-radio) — a XIAO nRF52840 + Wio-SX1262 soldered to the Lyra's UART | -| a ClockworkPi **uConsole** with the hackergadgets AIO board — an SX1262 on the host's own SPI bus | **[MeshTerm on the uConsole](uconsole.md)** — MeshTerm drives it directly with `--spi`, or a bridge service fronts it as a companion if you want it always on | -| an M5Stack **Cardputer Zero** — the one with a Raspberry Pi inside, not the ESP32 Cardputer or Cardputer-Adv | **[MeshTerm on the Cardputer Zero](cardputer-zero.md)** — ⚠️ **not supported yet**: it has never run on the device; the page says what works in the emulator | -| a MeshCore companion on USB, Bluetooth, or Wi-Fi | nothing here — the [README](../../README.md), and [When a companion won't connect](../guide/connecting.md) if it doesn't | +| a ClockworkPi **PicoCalc**, stock or already running Calculinux on a Luckfox Lyra, and I want MeshTerm on its own display | **[MeshTerm on the PicoCalc](picocalc-lyra.md)**: from the shopping list to a working machine | +| …and I want a **radio** inside it | the same manual, [Phase 3](picocalc-lyra.md#phase-3-add-the-radio): a XIAO nRF52840 + Wio-SX1262 soldered to the UART of the Lyra | +| a ClockworkPi **uConsole** with the hackergadgets AIO board (an SX1262 on the SPI bus of the host) | **[MeshTerm on the uConsole](uconsole.md)**: MeshTerm drives the radio directly with `--spi`. A bridge service can also present the radio as a companion, if you want it always on | +| an M5Stack **Cardputer Zero**, the one with a Raspberry Pi inside (not the ESP32 Cardputer or the Cardputer-Adv) | **[MeshTerm on the Cardputer Zero](cardputer-zero.md)**: ⚠️ **not supported yet**. MeshTerm has never run on the handheld. The page tells you what works in the emulator | +| a MeshCore companion on USB, Bluetooth, or Wi-Fi | nothing here. Read the [README](../../README.md). If the companion does not connect, read [When a companion does not connect](../guide/connecting.md) | -The devices have nothing in common but MeshTerm itself. The device-side scripts each -manual runs live in [`scripts/`](../../scripts/), one folder per handheld; the -[index below](#the-scripts) says what every file does and which machine it runs on. Read a -script before you run it; most run as root and change the machine they run on. +The handhelds have nothing in common except MeshTerm. Each manual runs scripts on the +handheld. The scripts are in [`scripts/`](../../scripts/), with one folder for each +handheld. The [index below](#the-scripts) tells you what each file does and which machine +runs it. Read a script before you run it. Most scripts run as root and change the machine +that runs them. ## The scripts -[`scripts/`](../../scripts/) holds the device-side helpers, one folder per manual: -[`picocalc-lyra/`](../../scripts/picocalc-lyra/) for the PicoCalc — bringing up Calculinux on the Lyra, -building its console font, and (under [`xiao-radio/`](../../scripts/picocalc-lyra/xiao-radio/)) -giving it a MeshCore radio over UART — and [`uconsole/`](../../scripts/uconsole/) for the -always-on bridge that fronts the uConsole's SPI-attached LoRa chip as a companion (MeshTerm -can also drive that chip directly, with no script at all — the uConsole manual covers -both). The manual is the -walkthrough; each script's own header says what it assumes about the machine and why each -step is the way it is. On the Lyra, only one program may hold `/dev/ttyS1` — MeshTerm -directly, or a pump in front of it, never both. +[`scripts/`](../../scripts/) has the helper scripts that run on the handheld. There is one +folder for each manual: + +- [`picocalc-lyra/`](../../scripts/picocalc-lyra/) is for the PicoCalc. It has the scripts + that bring up Calculinux on the Lyra and build the console font. Under + [`xiao-radio/`](../../scripts/picocalc-lyra/xiao-radio/), it also has the scripts that + give the PicoCalc a MeshCore radio over UART. +- [`uconsole/`](../../scripts/uconsole/) has the always-on bridge. The bridge presents the + LoRa chip on the SPI bus of the uConsole as a companion. MeshTerm can also drive that chip + directly, with no script. The uConsole manual describes both ways. + +The manual is the walkthrough. The header of each script says what the script assumes about +the machine, and why each step is as it is. On the Lyra, only one program can hold +`/dev/ttyS1`. It is MeshTerm directly, or a pump in front of MeshTerm, but never both. | File | Runs on | What it does | | --- | --- | --- | -| [`picocalc-lyra/calculinux-setup.sh`](../../scripts/picocalc-lyra/calculinux-setup.sh) | the Lyra (root) | One-time bring-up: python packages, the Wi-Fi boot-scan kick, a boot clock sync (there is no RTC), the deploy user, the checkout, the venv and `pip install -e .`, the login PATH, and the console font. Idempotent. | -| [`picocalc-lyra/calculinux-console-font-6x12.sh`](../../scripts/picocalc-lyra/calculinux-console-font-6x12.sh) | the Lyra (root) | Just the console font: builds, installs and persists the 512-glyph **meshterm** PSF (braille, node marks, rounded frame corners, the list cursor) and restores the stock 16-slot palette. Calls the 6×8 build below when it sits alongside. Run it alone after a MeshTerm update, from the pulled checkout (`/home/meshterm/MeshTerm/scripts/picocalc-lyra/`) rather than the copy made at install time. | -| [`picocalc-lyra/calculinux-console-font-6x8.sh`](../../scripts/picocalc-lyra/calculinux-console-font-6x8.sh) | the Lyra (root) | The shorter-cell companion font, **meshterm8** (6×8 → 53×40), for anyone who wants more rows than the 6×12 default gives. Its base bitmap is the kernel's `font_6x8`, so this one file and the PSF it builds are **GPL-2.0-only** ([`LICENSE.GPL-2.0`](../../scripts/picocalc-lyra/LICENSE.GPL-2.0) sits beside it); it reads the donor/alias/keeper tables out of the script above rather than keeping a second copy. Pick between the two fonts from MeshTerm's Preferences page. | -| [`picocalc-lyra/calculinux-wifi-set.sh`](../../scripts/picocalc-lyra/calculinux-wifi-set.sh) | the Lyra (sudo) | Interactive Wi-Fi join: prompts for an SSID and passphrase, writes iwd's credentials, connects now. This is how a network *becomes* known to the boot kick. | -| [`picocalc-lyra/calculinux-radio-bridge.sh`](../../scripts/picocalc-lyra/calculinux-radio-bridge.sh) | the Lyra (root) | **Unfinished, unsupported.** An early UART-to-TCP pump: it serves `/dev/ttyS1` on TCP port 5000 and writes a `radio` TCP profile. It does not route UART1 to the header pads. Superseded by `xiao-radio/lyra-setup.sh`, which does the routing and points MeshTerm at the serial port directly. | -| [`picocalc-lyra/xiao-radio/`](../../scripts/picocalc-lyra/xiao-radio/) | see below | Everything for the XIAO nRF52840 + Wio-SX1262 radio wired to the Lyra's UART1; its [bench card](../../scripts/picocalc-lyra/xiao-radio/README.md) has the wiring table. The firmware is MeshCore, MIT-licensed — [`LICENSE.MeshCore`](../../scripts/picocalc-lyra/xiao-radio/LICENSE.MeshCore) sits beside the patch. | -| [`xiao-radio/build-firmware.sh`](../../scripts/picocalc-lyra/xiao-radio/build-firmware.sh) | dev machine | Clones and patches MeshCore, builds the `Xiao_nrf52_companion_radio_serial` environment, emits the `.uf2`. | -| [`xiao-radio/flash.py`](../../scripts/picocalc-lyra/xiao-radio/flash.py) | dev machine | Flashes the `.uf2` to the XIAO over its UF2 bootloader, refusing the wrong board or SoftDevice; prints the serial-DFU command when the bootloader offers no drive. | -| [`xiao-radio/meshcore-uart1.patch`](../../scripts/picocalc-lyra/xiao-radio/meshcore-uart1.patch) | — | The two firmware hunks: the companion on nRF52 `Serial1`, and I²C moved off the UART pads. Not upstream yet. | -| [`xiao-radio/lyra-setup.sh`](../../scripts/picocalc-lyra/xiao-radio/lyra-setup.sh) | the Lyra (root) | Boot-persistent UART1 → GP4/GP5 mux service, `dialout` membership, and a default MeshTerm serial profile for `/dev/ttyS1`. | -| [`xiao-radio/uart1-mux.py`](../../scripts/picocalc-lyra/xiao-radio/uart1-mux.py) | the Lyra (root) | The matrix-IO register poke that routes UART1 to the header pads, run once per boot by the service above. | -| [`uconsole/meshterm-spi-bridge`](../../scripts/uconsole/meshterm-spi-bridge) | the radio host (your user) | The **always-on** option: runs a mesh node in software over an SPI-attached SX1262 (uConsole AIO, Waveshare HATs) and serves the companion protocol on `127.0.0.1:5000`, so the node stays on the mesh while MeshTerm is closed. Setup menu, preflight, optional `systemd --user` service. Runs on `openhop_core` (preferred) or its predecessor `pymc_core`; see the manual for the venv it wants on a bookworm uConsole. Most people should reach for `meshterm --spi` instead — no script, no service, MeshTerm drives the chip itself while it runs. | +| [`picocalc-lyra/calculinux-setup.sh`](../../scripts/picocalc-lyra/calculinux-setup.sh) | the Lyra (root) | One-time bring-up. It sets up the python packages, the Wi-Fi boot-scan kick, a clock sync at boot (there is no RTC), the deploy user, the checkout, the venv and `pip install -e .`, the login PATH, and the console font. You can run it again without a problem. | +| [`picocalc-lyra/calculinux-console-font-6x12.sh`](../../scripts/picocalc-lyra/calculinux-console-font-6x12.sh) | the Lyra (root) | Only the console font. It builds, installs, and keeps the 512-glyph **meshterm** PSF (braille, node marks, rounded frame corners, and the list pointer). It also restores the stock 16-slot palette. It calls the 6×8 build below when that script is in the same folder. After a MeshTerm update, run it alone from the pulled checkout (`/home/meshterm/MeshTerm/scripts/picocalc-lyra/`). Do not run the copy that the install made. | +| [`picocalc-lyra/calculinux-console-font-6x8.sh`](../../scripts/picocalc-lyra/calculinux-console-font-6x8.sh) | the Lyra (root) | The companion font with shorter cells, **meshterm8** (6×8, which gives 53×40). Use it if you want more rows than the 6×12 default gives. Its base bitmap is the `font_6x8` of the kernel. Thus this file and the PSF that it builds are **GPL-2.0-only**. The file [`LICENSE.GPL-2.0`](../../scripts/picocalc-lyra/LICENSE.GPL-2.0) is next to it. The script reads the donor, alias, and keeper tables from the script above and does not keep a second copy. Select between the two fonts on the Preferences page of MeshTerm. | +| [`picocalc-lyra/calculinux-wifi-set.sh`](../../scripts/picocalc-lyra/calculinux-wifi-set.sh) | the Lyra (sudo) | Interactive Wi-Fi join. It asks for an SSID and a passphrase, writes the credentials of iwd, and connects at once. This is how a network becomes known to the boot kick. | +| [`picocalc-lyra/calculinux-radio-bridge.sh`](../../scripts/picocalc-lyra/calculinux-radio-bridge.sh) | the Lyra (root) | **Unfinished and not supported.** An early UART-to-TCP pump. It serves `/dev/ttyS1` on TCP port 5000 and writes a `radio` TCP profile. It does not route UART1 to the header pads. `xiao-radio/lyra-setup.sh` replaces it. That script does the routing and points MeshTerm at the serial port directly. | +| [`picocalc-lyra/xiao-radio/`](../../scripts/picocalc-lyra/xiao-radio/) | see below | Everything for the XIAO nRF52840 + Wio-SX1262 radio that is wired to UART1 of the Lyra. Its [bench card](../../scripts/picocalc-lyra/xiao-radio/README.md) has the wiring table. The firmware is MeshCore, with the MIT licence. The file [`LICENSE.MeshCore`](../../scripts/picocalc-lyra/xiao-radio/LICENSE.MeshCore) is next to the patch. | +| [`xiao-radio/build-firmware.sh`](../../scripts/picocalc-lyra/xiao-radio/build-firmware.sh) | development machine | Clones and patches MeshCore, builds the `Xiao_nrf52_companion_radio_serial` environment, and makes the `.uf2` file. | +| [`xiao-radio/flash.py`](../../scripts/picocalc-lyra/xiao-radio/flash.py) | development machine | Flashes the `.uf2` file to the XIAO through its UF2 bootloader. It refuses the wrong board or SoftDevice. When the bootloader gives no drive, it prints the serial-DFU command. | +| [`xiao-radio/meshcore-uart1.patch`](../../scripts/picocalc-lyra/xiao-radio/meshcore-uart1.patch) | none | The two firmware hunks: the companion on `Serial1` of the nRF52, and I²C moved off the UART pads. It is not upstream yet. | +| [`xiao-radio/lyra-setup.sh`](../../scripts/picocalc-lyra/xiao-radio/lyra-setup.sh) | the Lyra (root) | A UART1 → GP4/GP5 mux service that stays after a reboot, membership of `dialout`, and a default MeshTerm serial profile for `/dev/ttyS1`. | +| [`xiao-radio/uart1-mux.py`](../../scripts/picocalc-lyra/xiao-radio/uart1-mux.py) | the Lyra (root) | The matrix-IO register poke that routes UART1 to the header pads. The service above runs it once at each boot. | +| [`uconsole/meshterm-spi-bridge`](../../scripts/uconsole/meshterm-spi-bridge) | the radio host (your user) | The **always-on** option. It runs a mesh node in software over an SX1262 on an SPI bus (uConsole AIO, Waveshare HATs). It serves the companion protocol on `127.0.0.1:5000`. Thus the node stays on the mesh while MeshTerm is closed. It has a setup menu, a preflight check, and an optional `systemd --user` service. It runs on `openhop_core` (preferred) or on its predecessor `pymc_core`. The manual describes the venv that it needs on a bookworm uConsole. Most users must use `meshterm --spi` instead. It needs no script and no service, and MeshTerm drives the chip itself while MeshTerm runs. | ## The platforms MeshTerm resolves one frozen **platform** at boot and draws everything through it. The -**regular** platform is a desktop or ssh terminal: 72 readable columns, truecolour, emoji, -a footer hint line. The **picocalc-lyra** platform is a framebuffer console: 53 columns, no -emoji, an F-key lane instead of a hint line, and sixteen colour slots rather than a colour -space. Nothing loses its colour there: the node hue and the recency heat gradient -quantise to those slots instead. It is detected from the device tree on the Lyra, and -`--platform picocalc-lyra` or `MESHTERM_PLATFORM=picocalc-lyra` forces it anywhere, which -is how you preview the handheld layout in your own terminal: +**regular** platform is a desktop terminal or an ssh terminal. It has 72 readable columns, +truecolour, emoji, and a footer hint line. + +The **picocalc-lyra** platform is a framebuffer console. It has 53 columns and no emoji. +It has an F-key lane, not a hint line. It has sixteen colour slots, not a colour space. +No element loses its colour there. The hue of a node and the heat gradient of recency are +quantized to those slots instead. MeshTerm detects this platform from the device tree on +the Lyra. You can force it on any machine with `--platform picocalc-lyra` or +`MESHTERM_PLATFORM=picocalc-lyra`. With this, you can preview the layout of the handheld +in your own terminal: ```bash meshterm --mock --platform picocalc-lyra # the whole app, simulated radio, handheld layout @@ -68,19 +76,19 @@ meshterm specimen # the visual language on one card meshterm platform # which platform this terminal resolved to ``` -The **cardputer-zero** platform is the Cardputer Zero's 53×14 screen, which MeshTerm draws -itself. It is [not supported yet](cardputer-zero.md), so it is never detected: only -`--platform cardputer-zero` chooses it. +The **cardputer-zero** platform is the display of the Cardputer Zero, which is 53×14. +MeshTerm draws this display itself. The platform is [not supported yet](cardputer-zero.md), +thus MeshTerm never detects it. Only `--platform cardputer-zero` selects it. -What that platform needs from the console — a font with the glyphs the interface draws, -and the stock sixteen-slot palette — is installed by the PicoCalc manual's setup script; -the details are in that manual and in the scripts' own headers. +The platform needs two things from the console: a font that has the glyphs that the +interface draws, and the stock sixteen-slot palette. The setup script in the PicoCalc +manual installs both. The manual and the headers of the scripts give the details. ## The emulator -`--platform` gives you a handheld's layout in your own terminal, drawn in your terminal's -font and colours. For a closer look, `meshterm emulate` opens a window showing MeshTerm the -way the handheld's own screen draws it: +`--platform` gives you the layout of a handheld in your own terminal. MeshTerm draws it in +the font and the colours of your terminal. For a closer look, run `meshterm emulate`. It +opens a window that shows MeshTerm as the display of the handheld draws it: ```bash meshterm emulate --fetch-fonts # once: the font the handhelds draw in @@ -88,25 +96,34 @@ meshterm emulate picocalc-lyra --mock # the PicoCalc's 320×320 screen meshterm emulate cardputer-zero --mock # the Cardputer Zero's 320×170 screen ``` -**It isn't a strict emulator.** It doesn't imitate the device's computer: MeshTerm runs on -your own machine, at your machine's speed. What it copies is the display's constraints — -the screen's size in pixels, the grid of letters on it, the font, the colours the device -can show, and the keys that drive the F-key lane. On the PicoCalc that means sixteen -colours, and bold drawn as a brighter colour rather than a heavier letter, the way its -console does it. Those are what decide whether a screen fits and reads well, so those are -what the window reproduces. +**The emulator is not a strict emulator.** It does not copy the computer of the handheld. +MeshTerm runs on your own machine, at the speed of your machine. The emulator copies the +constraints of the display and the keyboard: -Your keyboard's F-keys stand in for the lane's keys: **F1** to **F5** for the PicoCalc's, -and **F4** to **F8** for the Cardputer Zero's **Fn+4** to **Fn+8**. Hold **Shift** for the -second set. You can also click them: click a coloured key at the bottom of the screen, or -one of the Cardputer Zero's keys drawn under it, and hold **Shift** while you click for the -second set. Everything you type after the device's name goes to the MeshTerm in the window: -`--mock` for the simulated radio, or `--port`, `--ble` or `--tcp` for a real one. `--scale` -sets the zoom, **Ctrl+Shift+S** saves a screenshot at the screen's real size, and closing -the window quits MeshTerm. +- the size of the display in pixels +- the grid of letters on the display +- the font +- the colours that the handheld can show +- the keys that drive the F-key lane -It needs MeshTerm installed with pip or pipx; the one-file downloads leave out the part of -Python that opens windows. The font is Terminus, which has its own licence, so MeshTerm -doesn't ship it: `--fetch-fonts` downloads the official release and checks it is the real -one. On a machine that can't download it, fetch `terminus-font-4.49.1.tar.gz` some other -way and install it with `meshterm emulate --archive terminus-font-4.49.1.tar.gz`. +On the PicoCalc, this means sixteen colours. It also means that bold text is a brighter +colour and not a heavier letter, as on the console of the PicoCalc. These constraints +decide if a screen fits and is easy to read. Thus the window reproduces them. + +The F-keys of your keyboard replace the keys of the lane. Use **F1** to **F5** for the +PicoCalc. Use **F4** to **F8** for **Fn+4** to **Fn+8** on the Cardputer Zero. Hold +**Shift** for the second set. You can also click the keys. Click a coloured key at the +bottom of the display, or one of the keys of the Cardputer Zero that are drawn under it. +Hold **Shift** while you click for the second set. + +Each argument that you type after the name of the handheld goes to the MeshTerm in the +window. Use `--mock` for the simulated radio. Use `--port`, `--ble`, or `--tcp` for a real +one. `--scale` sets the zoom. **Ctrl+Shift+S** saves a screenshot at the real size of the +display. When you close the window, MeshTerm quits. + +The emulator needs MeshTerm installed with pip or pipx. The one-file downloads do not have +the part of Python that opens windows. The font is Terminus, which has its own licence. +Thus MeshTerm does not include it. `--fetch-fonts` downloads the official release and +checks that it is the real one. If your machine cannot download it, download +`terminus-font-4.49.1.tar.gz` in another way. Then install it with +`meshterm emulate --archive terminus-font-4.49.1.tar.gz`. diff --git a/docs/devices/cardputer-zero.md b/docs/devices/cardputer-zero.md index a0cee9b..e5e87a0 100644 --- a/docs/devices/cardputer-zero.md +++ b/docs/devices/cardputer-zero.md @@ -3,18 +3,18 @@ > [!CAUTION] > **The Cardputer Zero is not supported yet.** > -> MeshTerm now runs on a real Cardputer Zero, with M5Stack's Cap LoRa-1262 as its radio, -> but only from a copy of the source code you set up yourself. There is no package in -> M5Stack's app store, and the keyboard has only been checked by sending key events to it -> from another computer, not by someone typing on it. +> MeshTerm now runs on a real Cardputer Zero, with M5Stack's Cap LoRa-1262 as its +> radio. But it runs only from a copy of the source code that you set up yourself. There is +> no package in M5Stack's app store. Also, nobody has typed on the keyboard. Its keys +> are checked only when you send key events to it from another computer. > -> Don't buy a Cardputer Zero to run MeshTerm on it yet. This page will say so when that +> Do not buy a Cardputer Zero to run MeshTerm on it yet. This page will tell you when that > changes. -M5Stack makes three Cardputers. This page is about the **Cardputer Zero**: the one with a -Raspberry Pi Compute Module 0 inside, running Linux. The original Cardputer and the -Cardputer-Adv are ESP32 microcontroller boards; they can't run MeshTerm, and nothing here -applies to them. +M5Stack makes three Cardputers. This page is about the **Cardputer Zero**. It has a +Raspberry Pi Compute Module 0 inside, and it runs Linux. The original Cardputer and the +Cardputer-Adv are ESP32 microcontroller boards. They cannot run MeshTerm, and nothing on +this page applies to them. - [The device](#the-device) - [Where the work stands](#where-the-work-stands) @@ -33,46 +33,50 @@ applies to them. | Keyboard | 46 keys, with Shift, Fn and Sym layers | | System | Raspberry Pi OS "Trixie", with M5Stack's own app launcher | -MeshTerm draws the screen in 53 columns by 14 rows: the same width as on the PicoCalc, -but only about half the height, so many screens have a more compact layout here. +MeshTerm draws the display in 53 columns by 14 rows. The width is the same as on the +PicoCalc, but the height is approximately half. Thus many screens have a more compact +layout here. -The launcher gives an app the whole screen and the keyboard, but no text console to run -in. So MeshTerm brings its own: a small program inside MeshTerm that draws every -character on the screen itself and reads the keys directly. +The launcher gives an app the whole display and the keyboard, but it gives no text console +to run in. Thus MeshTerm has its own console. This is a small program inside MeshTerm. It +draws each character on the display itself and reads the keys directly. ## Where the work stands **Done, and tried on a real Cardputer Zero:** -- MeshTerm starts from M5Stack's app launcher and draws on the screen itself. -- MeshTerm's layout for the 14-row screen: a title bar that also carries the unread count - and the battery, one-row tab strips, and dialogs sized for the short screen. -- The battery in the title bar is the Cardputer Zero's own. -- The row of function-key labels along the bottom of the screen, driven by **Fn+4** to - **Fn+8**, the keys that sit right under the display. **Shift** with them is a second set. -- M5Stack's Cap LoRa-1262 radio add-on (see [Radios](#radios)). -- Holding **Esc** to quit, which is how the launcher closes any app. After a second, - MeshTerm shows a box saying so, with a bar that fills over the next two seconds, and it - stops sending anything new over the radio while the box is up. Let go before the bar - fills and you're back where you were. Hold on, and MeshTerm saves everything and closes - in good time, before the launcher would force it. A quick press of **Esc** still goes - back a screen, as it does everywhere else. +- MeshTerm starts from M5Stack's app launcher and draws on the display itself. +- MeshTerm has a layout for the display of 14 rows. The title bar also shows the unread + count and the battery. The tab strips have one row. The dialogs fit the short display. +- The battery in the title bar is the Cardputer Zero's own battery. +- The row of function-key labels is along the bottom of the display. **Fn+4** to **Fn+8** + drive it. These keys are directly under the display. **Shift** with them gives a second + set. +- M5Stack's Cap LoRa-1262 radio add-on (refer to [Radios](#radios)). +- Hold **Esc** to quit. This is how the launcher closes each app. After one second, + MeshTerm shows a box that says so. The box has a bar that fills over the next two + seconds. While the box is visible, MeshTerm sends nothing new over the radio. If you + release **Esc** before the bar is full, you return to the screen that you were on. If you + keep holding it, MeshTerm saves everything and closes in good time, before the launcher + forces it. A short press of **Esc** still goes back one screen, as on all other + handhelds. **Not done:** -- A package for M5Stack's app store. Until there is one, you install MeshTerm yourself - (see below). -- Typing on the real keyboard. The arrows, Enter, Esc, Fn+4 to Fn+8, and Shift have been - checked by sending their key events to the keyboard from another computer, and the - symbols on the Sym layer match M5Stack's own keymap on the device. Nobody has typed a +- A package for M5Stack's app store. Until there is one, you install MeshTerm + yourself (refer to the next section). +- Typing on the real keyboard. Key events for the arrows, Enter, Esc, Fn+4 to Fn+8, and + Shift were sent to the keyboard from another computer. They worked. The symbols on the + Sym layer agree with M5Stack's own keymap on the handheld. Nobody has typed a message on it yet. ## Installing it on the device -This is for people comfortable with a Linux command line. You need the Cardputer Zero on -Wi-Fi, and a way to type commands on it: SSH from another computer is easiest. +This section is for users who know a Linux command line. The Cardputer Zero must be on +Wi-Fi. You also need a way to type commands on it. SSH from another computer is the +easiest way. -Get MeshTerm and the radio library, and fetch the font the screen is drawn in: +1. Get MeshTerm and the radio library, and download the font that the display uses: ```bash git clone https://github.com/jpmartineau/MeshTerm.git @@ -82,124 +86,137 @@ python3 -m venv .venv .venv/bin/meshterm emulate --fetch-fonts ``` -Then put MeshTerm in the launcher. This asks for your password once, because the launcher's -list of apps belongs to the system: +2. Put MeshTerm in the launcher. The command asks for your password one time, because the + list of apps of the launcher belongs to the system: ```bash scripts/cardputer-zero/launcher-entry.sh ``` -MeshTerm now has an icon in the launcher. `scripts/cardputer-zero/launcher-entry.sh remove` -takes it out again. +MeshTerm now has an icon in the launcher. To remove the icon, run +`scripts/cardputer-zero/launcher-entry.sh remove`. ### Typing long text from your computer -A long password or key is hard work on a keyboard this small. From another computer that -can reach the Cardputer over SSH, `scripts/cardputer-zero/type-text.py` types it for you, -into whatever app has the screen: +A long password or key is hard to type on a keyboard this small. Use another computer that +can reach the Cardputer over SSH. The tool `scripts/cardputer-zero/type-text.py` types the +text for you, into the app that has the display: ```bash python scripts/cardputer-zero/type-text.py --host pi@cardputer.local ``` -Paste the text when it asks, and press Enter. Your typing stays hidden, and the text goes -to the Cardputer over SSH, so it never shows on either screen. Open the field you want it -in on the Cardputer first, and leave its keyboard alone until the tool says it's done. +1. On the Cardputer, open the field that you want to fill. +2. Run the command above on your computer. +3. Paste the text when the tool asks for it, and press Enter. The tool does not show your + typing. The text goes to the Cardputer over SSH, thus it does not show on either + display. +4. Do not touch the Cardputer's keyboard until the tool says that it is done. ## Trying it in the emulator -The emulator opens a window showing the Cardputer Zero's screen, scaled up so it's -readable. Run it with a simulated radio, and you can try the layout without the device or -a radio. +The emulator opens a window that shows the display of the Cardputer Zero. The window +scales the display up so that you can read it. If you run it with a simulated radio, you +can try the layout without the handheld and without a radio. -It isn't a strict emulator. It doesn't pretend to be the Cardputer Zero's computer: -MeshTerm runs on your own computer, at your computer's speed. What it copies is the -screen and the keyboard — the screen's size in pixels, the font, the colours, and the keys -that drive the labels along the bottom. That's enough to show what fits on the screen and -what doesn't, which is what it's for. +The emulator is not a strict emulator. It does not copy the computer of the Cardputer +Zero. MeshTerm runs on your own computer, at the speed of your computer. The emulator +copies the constraints of the display and the keyboard: the size of the display in pixels, +the font, the colours, and the keys that drive the labels along the bottom. This shows what +fits on the display and what does not. That is the purpose of the emulator. -It needs MeshTerm installed with pip or pipx (see the [README](../../README.md)). The -one-file downloads leave out the part of Python that opens windows. +The emulator needs MeshTerm installed with pip or pipx (refer to the +[README](../../README.md)). The one-file downloads do not have the part of Python that +opens windows. -Once, fetch the font the screen is drawn in: +1. Download the font that the display uses. Do this one time: ```bash meshterm emulate --fetch-fonts ``` -Then start the emulator: +2. Start the emulator: ```bash meshterm emulate cardputer-zero --mock ``` -`--mock` is the simulated radio. Point `MESHTERM_HOME` at an empty folder first, so its -made-up nodes stay out of your real history. `--scale 4` makes the window bigger. +`--mock` is the simulated radio. Before you start, set `MESHTERM_HOME` to an empty folder. +Then the made-up nodes of the simulation stay out of your real history. `--scale 4` makes +the window bigger. -In the window, your keyboard's **F4** to **F8** stand in for **Fn+4** to **Fn+8**, and -holding **Shift** shows the second set. You can click the keys instead, either the ones -drawn under the screen or the coloured keys along its bottom row; hold **Shift** while you -click for the second set. **Ctrl+Shift+S** saves a screenshot at the screen's -real size. Closing the window quits MeshTerm, and so does holding **Esc** for three -seconds, as it does on the device. +In the window, the **F4** to **F8** keys of your keyboard replace **Fn+4** to **Fn+8**. +Hold **Shift** to show the second set. You can also click the keys. Click the keys that are +drawn under the display, or the coloured keys along its bottom row. Hold **Shift** while +you click for the second set. **Ctrl+Shift+S** saves a screenshot at the real size of the +display. When you close the window, MeshTerm quits. It also quits when you hold **Esc** for +three seconds, as it does on the handheld. ## Radios -The Cardputer Zero has no LoRa radio built in. There are three ways to add one: +The Cardputer Zero has no LoRa radio built in. You can add one in three ways: | Radio | Status | | --- | --- | -| M5Stack's **Cap LoRa-1262** add-on board | Works, with no setup. MeshTerm runs the mesh node itself while it's open, the same way it does on a uConsole. | +| **Cap LoRa-1262** add-on board from M5Stack | Works, with no setup. MeshTerm runs the mesh node itself while it is open, as it does on a uConsole. | | A MeshCore companion on USB | Expected to work as on any Linux computer. | | A MeshCore companion over Bluetooth | Expected to work as on any Linux computer. | ### The Cap LoRa-1262 -Push the Cap onto the expansion header at the back, then start MeshTerm. The device screen -lists it as **Cap LoRa-1262**, and `meshterm --spi` reaches it from the command line. +1. Push the Cap onto the expansion header at the back. +2. Start MeshTerm. -The Cap has no MeshCore firmware of its own: it's just a radio chip. So while MeshTerm is -open, MeshTerm *is* the node, the way it is on a uConsole's AIO board. It needs the radio -library installed beside MeshTerm: +The device screen lists the Cap as **Cap LoRa-1262**. `meshterm --spi` also reaches it from the +command line. + +The Cap has no MeshCore firmware of its own. It is only a radio chip. Thus, while MeshTerm +is open, MeshTerm is the node, as it is on the AIO board of a uConsole. MeshTerm needs the +radio library, installed next to it: ```bash pip install 'mesh-term[spi]' ``` -When MeshTerm connects, it does three things the Cardputer Zero needs before the radio -answers. It switches on the power to the expansion header, which is off until something -asks for it. It makes sure two of the header's pins are connected to the radio and not to -the USB port that shares them. And it switches on the Cap's antenna path. When MeshTerm -quits, it switches the header's power back off, so the Cap doesn't drain the battery -while nothing is using it. +When MeshTerm connects, it does three things that the Cardputer Zero needs before the +radio answers: -A new node starts on MeshCore's US/Canada settings (910.525 MHz). Anywhere else, change -them on **Device config** before you send anything. The node's key, contacts, and channels -are kept in `~/.meshterm/radio/spidev0.1/`. +- It switches on the power to the expansion header. The power is off until a program asks + for it. +- It makes sure that two pins of the header connect to the radio and not to the USB port + that shares them. +- It switches on the antenna path of the Cap. + +When MeshTerm quits, it switches the power of the header off. Thus the Cap does not drain +the battery while nothing uses it. + +A new node starts with the US/Canada settings of MeshCore (910.525 MHz). In other regions, +change the settings on **Device config** before you send anything. MeshTerm keeps the key, +the contacts, and the channels of the node in `~/.meshterm/radio/spidev0.1/`. #### The Cap's GPS -The Cap also carries a GPS, and MeshTerm can keep your node's position up to date from it. -It's off until you switch it on, the same as on MeshCore firmware. To switch it on: +The Cap also has a GPS. MeshTerm can use it to keep the position of your node current. The +GPS is off until you switch it on, as on MeshCore firmware. To switch it on: 1. Open **Device config**. 2. Under **Custom variables**, set **GPS** to on. 3. Apply. -Once the GPS has a fix, it becomes your node's position. Out in the open, the first fix -usually takes about a minute. Indoors, it may never come. +When the GPS has a fix, the fix becomes the position of your node. In the open air, the +first fix usually takes approximately one minute. Indoors, the fix may never come. -Two things don't change: +Two things do not change: -- **Whether your adverts include your position is still up to you.** That's the **Share - location** setting on Device config. With it off, the GPS still moves your node on - MeshTerm's own map, but nobody else sees where you are. -- **Moving your node on the map by hand doesn't stick while the GPS is on.** The next fix - puts it back where the GPS says you are. Switch the GPS off first. +- **You decide if your adverts include your position.** This is the **Share location** + setting on Device config. If it is off, the GPS still moves your node on MeshTerm's own map, + but nobody else sees where you are. +- **A hand move of your node on the map does not stay while the GPS is on.** The next fix + puts the node back where the GPS says you are. Switch the GPS off first. -**GPS interval** sets how often the position is updated, in seconds. It's 0 to start with, -which means every fix, about once a second. MeshTerm remembers your last fix when it quits, -so the next start begins where you were. +**GPS interval** sets how often the position changes, in seconds. The default is 0. This +means each fix, approximately one each second. MeshTerm remembers your last fix when it +quits. Thus the next start begins where you were. -The GPS talks to the Cardputer over a serial port, the way a companion on a USB cable -does. It isn't a companion, though, so the device screen doesn't list it. +The GPS talks to the Cardputer over a serial port, as a companion on a USB cable does. But +it is not a companion, thus the device screen does not list it. diff --git a/docs/devices/picocalc-lyra.md b/docs/devices/picocalc-lyra.md index 7ba0c7b..2bd8aef 100644 --- a/docs/devices/picocalc-lyra.md +++ b/docs/devices/picocalc-lyra.md @@ -1,63 +1,69 @@ # MeshTerm on the PicoCalc -You start with a stock ClockworkPi PicoCalc — the kit that ships with a Raspberry Pi Pico — -and a short shopping list. You end with a Linux handheld: MeshTerm running full-screen on -the PicoCalc's own display and keyboard, a LoRa radio soldered inside, and no laptop needed -to use it day to day. There are three phases: build the machine (swap the Pico for a Linux -board), install the OS and MeshTerm, and add the radio. Budget an afternoon for the first -two phases, most of it spent waiting for a toolchain to download; the radio phase needs a -soldering iron and can be done later — MeshTerm's `--mock` mode runs perfectly well without -a radio in the meantime. +You start with a stock ClockworkPi PicoCalc (the kit that comes with a Raspberry Pi Pico) +and a short shopping list. You end with a Linux handheld. MeshTerm runs full-screen on the +display and the keyboard of the PicoCalc. A LoRa radio is soldered inside. You do not need +a laptop for daily use. -**Want to see it before you build it?** On any computer with MeshTerm installed, -`meshterm emulate picocalc-lyra --mock` opens a window showing MeshTerm as the PicoCalc's -screen draws it, with a simulated radio. It isn't a strict emulator — it copies the -screen's size, font, and colours, not the machine — but it shows you what you're building -toward. [The emulator](README.md#the-emulator) has the details. +There are three phases: -> ⚠ **Read this first.** This build takes the PicoCalc apart, swaps its core board, and -> solders to it. It assumes you can already solder cleanly, use a multimeter, and work with -> small electronics without forcing anything. There is a real risk of damaging the -> hardware if a step goes wrong — a cracked screen, a snapped shell post, a dead board, or -> an overcharged lithium cell — and some of that damage is not repairable. You do this at -> your own risk. +1. Build the machine. You replace the Pico with a Linux board. +2. Install the OS and MeshTerm. +3. Add the radio. + +Plan an afternoon for the first two phases. Most of this time is the wait for a toolchain +to download. The radio phase needs a soldering iron, and you can do it later. In the +meantime, the `--mock` mode of MeshTerm runs well without a radio. + +**Do you want to see MeshTerm before you build the machine?** On any computer that has +MeshTerm installed, run `meshterm emulate picocalc-lyra --mock`. It opens a window that +shows MeshTerm as the display of the PicoCalc draws it, with a simulated radio. The +emulator is not a strict emulator. It copies the size, the font, and the colours of the +display, but it does not copy the machine. It shows you what you will build. +[The emulator](README.md#the-emulator) has the details. + +> ⚠ **WARNING: Read this first.** In this build, you take the PicoCalc apart, replace its +> core board, and solder to it. You must be able to solder cleanly, use a multimeter, and +> work with small electronics without force. If a step goes wrong, the hardware can be +> damaged: a cracked screen, a broken shell post, a dead board, or an overcharged lithium +> cell. Some of this damage cannot be repaired. You do this at your own risk. > -> **Read the whole manual through once before you buy anything or open anything.** Several -> steps only make sense in light of a later one — where the SD card goes, what must be out -> of the case before a board is pressed in, which USB port is never to be powered — and the -> shopping list is easier to get right when you know what each part is for. +> **Read the whole manual one time before you buy or open anything.** Several steps are +> clear only when you know a later step. These are where the SD card goes, what you must +> remove from the case before you press in a board, and which USB port you must never +> power. You can also make the shopping list more easily when you know what each part is +> for. > -> This is a guide, not a prescription. Despite the author's best efforts it may contain -> errors, and parts of it will go out of date as boards, images and firmware change. It is -> up to you to check every fact in it against your own hardware and the current sources -> before you act on it, and every decision along the way is yours. The author accepts no -> responsibility for any damage, loss, or injury that results from following this guide, -> and nothing in it is a warranty that your parts, your tools, or your hands will behave the -> way the author's did. If you are not comfortable with any step, stop and get help from -> someone who is. +> This is a guide, not a prescription. The author tried to make it correct, but it can +> have errors. Parts of it will become out of date when boards, images, and firmware +> change. You must check each fact in it against your own hardware and the current sources +> before you act on it. Each decision is yours. The author accepts no responsibility for +> damage, loss, or injury that results from this guide. It is not a warranty that your +> parts, your tools, or your hands will work as the parts, tools, and hands of the author +> did. If you are not comfortable with a step, stop and ask for help from a person who is. - [Shopping list](#shopping-list) - [Before you start](#before-you-start) - [Three ways to break it](#three-ways-to-break-it) -- [Phase 1 — Build the machine](#phase-1--build-the-machine) - - [Step 1 — Assemble the PicoCalc](#step-1--assemble-the-picocalc) - - [Step 2 — Swap the Pico for the Lyra](#step-2--swap-the-pico-for-the-lyra) - - [Step 3 — Attach the Wi-Fi dongle](#step-3--attach-the-wi-fi-dongle) -- [Phase 2 — Install Calculinux and MeshTerm](#phase-2--install-calculinux-and-meshterm) - - [Step 4 — Write the image to the microSD](#step-4--write-the-image-to-the-microsd) - - [Step 5 — First boot](#step-5--first-boot) - - [Step 6 — Open the serial console](#step-6--open-the-serial-console) - - [Step 7 — Join Wi-Fi](#step-7--join-wi-fi) - - [Step 8 — Copy the setup scripts to the device](#step-8--copy-the-setup-scripts-to-the-device) - - [Step 9 — Run calculinux-setup.sh](#step-9--run-calculinux-setupsh) - - [Step 10 — Log in as meshterm](#step-10--log-in-as-meshterm) -- [Phase 3 — Add the radio](#phase-3--add-the-radio) - - [Step 11 — Install PlatformIO](#step-11--install-platformio) - - [Step 12 — Build the firmware](#step-12--build-the-firmware) - - [Step 13 — Flash the XIAO](#step-13--flash-the-xiao) - - [Step 14 — Wire the radio](#step-14--wire-the-radio) - - [Step 15 — Set up the Lyra](#step-15--set-up-the-lyra) - - [Step 16 — Run it](#step-16--run-it) +- [Phase 1: Build the machine](#phase-1-build-the-machine) + - [Step 1: Assemble the PicoCalc](#step-1-assemble-the-picocalc) + - [Step 2: Swap the Pico for the Lyra](#step-2-swap-the-pico-for-the-lyra) + - [Step 3: Attach the Wi-Fi dongle](#step-3-attach-the-wi-fi-dongle) +- [Phase 2: Install Calculinux and MeshTerm](#phase-2-install-calculinux-and-meshterm) + - [Step 4: Write the image to the microSD](#step-4-write-the-image-to-the-microsd) + - [Step 5: First boot](#step-5-first-boot) + - [Step 6: Open the serial console](#step-6-open-the-serial-console) + - [Step 7: Join Wi-Fi](#step-7-join-wi-fi) + - [Step 8: Copy the setup scripts to the device](#step-8-copy-the-setup-scripts-to-the-device) + - [Step 9: Run calculinux-setup.sh](#step-9-run-calculinux-setupsh) + - [Step 10: Log in as meshterm](#step-10-log-in-as-meshterm) +- [Phase 3: Add the radio](#phase-3-add-the-radio) + - [Step 11: Install PlatformIO](#step-11-install-platformio) + - [Step 12: Build the firmware](#step-12-build-the-firmware) + - [Step 13: Flash the XIAO](#step-13-flash-the-xiao) + - [Step 14: Wire the radio](#step-14-wire-the-radio) + - [Step 15: Set up the Lyra](#step-15-set-up-the-lyra) + - [Step 16: Run it](#step-16-run-it) - [Day-to-day](#day-to-day) - [Troubleshooting](#troubleshooting) - [What has been tested](#what-has-been-tested) @@ -68,217 +74,239 @@ toward. [The emulator](README.md#the-emulator) has the details. ### The machine -| Item | Product / what to search for | Approx. price | Link | Note | +| Item | Product / what to search for | Price (approximate) | Link | Note | | --- | --- | --- | --- | --- | -| PicoCalc kit | ClockworkPi PicoCalc | $89 | [clockworkpi.com/product-page/picocalc](https://www.clockworkpi.com/product-page/picocalc) | Comes with a Raspberry Pi Pico 1H, screen, keyboard, shell, hex key and a 32 GB SD card. **18650 batteries are not included.** | -| Luckfox Lyra | "Luckfox Lyra 128MB, pre-soldered header, no NAND" | $20–25 | [luckfox.com/Luckfox-Lyra](https://www.luckfox.com/Luckfox-Lyra) | Buy the **plain 128 MB RAM, Pico-form-factor** variant, **with the header pre-soldered**. Other RAM sizes have incompatible pinouts, so this is the one part of the list where the wrong SKU won't work. | -| microSD card | 16 GB+, Class 10 or better | $6–10 | — | The PicoCalc's stock 32 GB card works fine — it's more than the 8 GB minimum. You can reuse it instead of buying a new one. | -| An 18650 battery | e.g. Samsung 30Q, Molicel P26A, Sony VTC6 (examples, not endorsements) | $5–10 | — | **Unprotected**, flat-top or button-top, Ø18 × 65–69 mm. One is enough to run; a second is optional (below). See [the battery note](#a-note-on-batteries) below. | -| USB Wi-Fi dongle | TP-Link TL-WN725N (an RTL8188EU nano dongle), or search the **chipset name**: RTL8192CU, R8712U, RTL8188EU | $5–10 | [amazon.ca/dp/B008IFXQFU](https://www.amazon.ca/dp/B008IFXQFU) | The TL-WN725N is the dongle this guide was built with. Must run at **3.3 V** — see [Step 3](#step-3--attach-the-wi-fi-dongle). Only needed for setup and updates; MeshTerm itself runs offline. | -| MX1.25 4-pin pigtail | "MX1.25 4P cable with leads" (a pack of pre-crimped pigtails, 10–15 cm) | $2–4 | — | One end of the DIY USB lead in [Step 3](#step-3--attach-the-wi-fi-dongle). Pre-crimped, because MX1.25 contacts need a crimper you don't otherwise own. | -| USB-A female socket | "USB 2.0 type A female socket, solder / through-hole" | $2–3 | — | The other end of the DIY lead. A ready-made [MX1.25 4P to USB-A cable](https://spotpear.com/shop/Luckfox-Lyra-MX1.25-4P-To-USB-A-Cable.html) (about $1, 30 cm) works too, but it is a lot of cable to fold into the shell. | +| PicoCalc kit | ClockworkPi PicoCalc | $89 | [clockworkpi.com/product-page/picocalc](https://www.clockworkpi.com/product-page/picocalc) | The kit has a Raspberry Pi Pico 1H, a screen, a keyboard, a shell, a hex key, and a 32 GB SD card. **It does not have 18650 batteries.** | +| Luckfox Lyra | "Luckfox Lyra 128MB, pre-soldered header, no NAND" | $20–25 | [luckfox.com/Luckfox-Lyra](https://www.luckfox.com/Luckfox-Lyra) | Buy the **plain 128 MB RAM, Pico-form-factor** variant, **with the header pre-soldered**. Other RAM sizes have pinouts that do not fit. This is the one part of the list where the wrong SKU does not work. | +| microSD card | 16 GB+, Class 10 or better | $6–10 | none | The stock 32 GB card of the PicoCalc works. It is more than the minimum of 8 GB. You can use it again and not buy a new card. | +| An 18650 battery | For example Samsung 30Q, Molicel P26A, Sony VTC6 (examples, not endorsements) | $5–10 | none | **Unprotected**, flat-top or button-top, Ø18 × 65–69 mm. One battery is enough to run the machine. A second battery is optional (refer to [the battery note](#a-note-on-batteries) below). | +| USB Wi-Fi dongle | TP-Link TL-WN725N (an RTL8188EU nano dongle). You can also search for the **chipset name**: RTL8192CU, R8712U, RTL8188EU | $5–10 | [amazon.ca/dp/B008IFXQFU](https://www.amazon.ca/dp/B008IFXQFU) | The TL-WN725N is the dongle that this guide used. It must run at **3.3 V** (refer to [Step 3](#step-3-attach-the-wi-fi-dongle)). You need it only for setup and updates. MeshTerm itself runs offline. | +| MX1.25 4-pin pigtail | "MX1.25 4P cable with leads" (a pack of pre-crimped pigtails, 10–15 cm) | $2–4 | none | One end of the DIY USB lead in [Step 3](#step-3-attach-the-wi-fi-dongle). Buy it pre-crimped, because MX1.25 contacts need a crimper that you probably do not have. | +| USB-A female socket | "USB 2.0 type A female socket, solder / through-hole" | $2–3 | none | The other end of the DIY lead. A ready-made [MX1.25 4P to USB-A cable](https://spotpear.com/shop/Luckfox-Lyra-MX1.25-4P-To-USB-A-Cable.html) (approximately $1, 30 cm) also works. But it is much cable to fold into the shell. | ### The radio -| Item | Product / what to search for | Approx. price | Link | Note | +| Item | Product / what to search for | Price (approximate) | Link | Note | | --- | --- | --- | --- | --- | -| XIAO nRF52840 + Wio-SX1262 kit | "XIAO nRF52840 & Wio-SX1262 Kit for Meshtastic", SKU 102010710 | $13.49 | [seeedstudio.com](https://www.seeedstudio.com/XIAO-nRF52840-Wio-SX1262-Kit-for-Meshtastic-p-6400.html) | Includes the XIAO nRF52840, the Wio-SX1262 LoRa board (stacked and wired together) and an antenna. Covers both 868 and 915 MHz. Plain or Sense XIAO both work. **Do not** buy the "-N" variant — it has no antenna connector. | +| XIAO nRF52840 + Wio-SX1262 kit | "XIAO nRF52840 & Wio-SX1262 Kit for Meshtastic", SKU 102010710 | $13.49 | [seeedstudio.com](https://www.seeedstudio.com/XIAO-nRF52840-Wio-SX1262-Kit-for-Meshtastic-p-6400.html) | The kit has the XIAO nRF52840, the Wio-SX1262 LoRa board (stacked and wired together), and an antenna. It covers 868 MHz and 915 MHz. The plain XIAO and the Sense XIAO both work. **Do not** buy the "-N" variant. It has no antenna connector. | ### Tools and consumables | Item | Note | | --- | --- | | Soldering iron, fine tip, and solder | For the four radio wires. | -| Hookup wire, 26–30 AWG, four short lengths | TX and RX get crossed — see [Step 14](#step-14--wire-the-radio). | -| 2.5 mm hex key | Comes with the PicoCalc kit. | -| USB-C data cable | For the serial console, and later for flashing the XIAO. A charge-only cable won't do; it has to carry data. | -| microSD card reader | For writing the Calculinux image from your PC. | -| Small side cutters | For trimming wire ends, and a section of the case grille if the Wi-Fi lead needs the room. | -| Small needle file | To open the back panel's USB-C cutout a little if the Lyra's connector doesn't clear it. | -| Kapton (polyimide) tape | Wraps the XIAO stack and the DIY USB lead so nothing bare touches the board or the shell. Heat-proof, thin, and it peels off cleanly. | -| A PC (Windows/macOS/Linux) with Python 3 and git | Runs the image-writing, firmware-build and flashing steps. | +| Hookup wire, 26–30 AWG, four short lengths | TX and RX are crossed (refer to [Step 14](#step-14-wire-the-radio)). | +| 2.5 mm hex key | It comes with the PicoCalc kit. | +| USB-C data cable | For the serial console, and later to flash the XIAO. A cable that only charges does not work. The cable must carry data. | +| microSD card reader | To write the Calculinux image from your PC. | +| Small side cutters | To cut wire ends. Also to cut a section of the case grille, if the Wi-Fi lead needs the space. | +| Small needle file | To make the USB-C cutout in the back panel a little bigger, if the connector of the Lyra does not clear it. | +| Kapton (polyimide) tape | It wraps the XIAO stack and the DIY USB lead. Then no bare part touches the board or the shell. It is thin, it resists heat, and it comes off cleanly. | +| A PC (Windows/macOS/Linux) with Python 3 and git | It runs the steps that write the image, build the firmware, and flash the XIAO. | ### Optional | Item | Why | | --- | --- | -| A second 18650 battery | The holder takes two; one is enough to run, two roughly double your runtime. | -| Powered USB hub | If you want the Wi-Fi dongle and something else on the Lyra's single USB host socket at once, or a 5 V dongle. | -| Foam tape | To secure the XIAO board inside the shell once it's wired in. | +| A second 18650 battery | The holder takes two batteries. One is enough to run the machine. Two give approximately twice the runtime. | +| Powered USB hub | Use it if you want the Wi-Fi dongle and another device on the single USB host socket of the Lyra at the same time. Also use it for a 5 V dongle. | +| Foam tape | To hold the XIAO board in the shell after you wire it. | -Approximate total: **$130–150** for the machine and radio together, before batteries — most -of that is the PicoCalc kit ($89), the Lyra (~$22) and the radio kit ($13.49). +The approximate total is **$130–150** for the machine and the radio together, before +batteries. Most of this is the PicoCalc kit ($89), the Lyra (approximately $22), and the +radio kit ($13.49). **What you do NOT need:** -- A level shifter. Every signal in this build — the Lyra's header, the XIAO, the SX1262 — is - 3.3 V. -- A separate USB-to-serial adapter. The PicoCalc mainboard already has one built in. -- Bluetooth anything. Neither the Lyra nor this radio path uses it. -- A new SD card, if your stock 32 GB card is handy — it's well over the 8 GB minimum - Calculinux asks for. +- A level shifter. Each signal in this build is 3.3 V: the header of the Lyra, the XIAO, + and the SX1262. +- A separate USB-to-serial adapter. The PicoCalc mainboard has one built in. +- Bluetooth. Neither the Lyra nor this radio path uses it. +- A new SD card, if you have the stock 32 GB card. It is much more than the minimum of + 8 GB that Calculinux asks for. ### A note on batteries -ClockworkPi has not published an official battery spec for the PicoCalc — a -[still-open GitHub issue](https://github.com/clockworkpi/PicoCalc/issues/6) asking for one -has gone unanswered. What's known from the kit itself: the holder takes two 18650 cells, one -cell is enough to power the board, and the physical envelope is about Ø18 mm × 65–69 mm, -which rules out most *protected* cells — the protection circuit adds length that doesn't -fit. Unprotected flat-top or button-top cells both work; the PicoCalc mainboard has its own -charge circuit built in. Buy from a reputable brand rather than a cheap unbranded listing — -Samsung 30Q, Molicel P26A and Sony VTC6 are commonly recommended examples, not an -endorsement of any one of them. +ClockworkPi did not publish an official battery specification for the PicoCalc. A +[GitHub issue](https://github.com/clockworkpi/PicoCalc/issues/6) that asks for one is +still open and has no answer. These facts come from the kit itself: + +- The holder takes two 18650 cells. +- One cell is enough to power the board. +- The physical size is approximately Ø18 mm × 65–69 mm. This excludes most *protected* + cells, because the protection circuit adds length that does not fit. +- Unprotected flat-top cells and button-top cells both work. +- The PicoCalc mainboard has its own charge circuit. + +Buy from a reputable brand, not from a cheap unbranded listing. Samsung 30Q, Molicel P26A, +and Sony VTC6 are examples that people recommend often. They are not an endorsement of one +of them. --- ## Before you start -Work happens on two machines, and it helps to keep straight which is which: +You work on two machines. It helps to know which machine does which job: - **Your PC** writes the Calculinux image to the microSD card, builds the radio firmware, - and flashes the XIAO. Nothing in these three jobs touches the PicoCalc itself. -- **The PicoCalc** (really, the Luckfox Lyra inside it) does everything else — setup, - Wi-Fi, running MeshTerm — either over the serial console from your PC or on its own - screen and keyboard. Both work identically; the serial console is just easier for pasting - long commands. + and flashes the XIAO. These three jobs do not touch the PicoCalc itself. +- **The PicoCalc** (the Luckfox Lyra inside it) does all other jobs: setup, Wi-Fi, and + MeshTerm. You can do these jobs over the serial console from your PC, or on the display + and the keyboard of the PicoCalc. Both ways work in the same manner. The serial console + is easier when you must paste long commands. -The device has two logins you'll use for different things: **`root`**, which you use once -to run the setup script and fix Wi-Fi, and **`meshterm`**, the unprivileged account -MeshTerm actually runs under day to day. +The PicoCalc has two logins for different jobs: -Calculinux is a console-only Linux — no desktop, no window manager. That's deliberate: the -PicoCalc's screen and keyboard are a terminal, and MeshTerm is a full-screen terminal -program built for exactly that. +- **`root`** is for the setup script and for Wi-Fi repairs. You use it one time. +- **`meshterm`** is the unprivileged account that MeshTerm runs under each day. + +Calculinux is a Linux that has a console only. It has no desktop and no window manager. +This is intentional. The display and the keyboard of the PicoCalc are a terminal. MeshTerm +is a full-screen terminal program that is made for this. --- ## Three ways to break it -The PicoCalc is a kit, and its forum has a well-worn list of the ways people have damaged -theirs. Three of them apply directly to this build, and the first two are not theoretical: -the author of this guide **cracked a screen and snapped screw posts** building the very -machine it describes. Read this section before you open the shell. +The PicoCalc is a kit. Its forum has a long list of the ways in which people damaged their +PicoCalc. Three of them apply to this build. The first two are real. The author of this +guide **cracked a screen and broke screw posts** when building the machine that this guide +describes. Read this section before you open the shell. -> ⚠ **The screen is extremely fragile.** It is a bare glass panel with no frame of its own, -> and when the case is closed it sits directly against the mainboard. Anything that flexes -> the mainboard flexes the glass: pushing a board into the Pico socket, prying one out, -> closing the shell with the screen not sitting square in its opening, or pressing a key -> while the halves are being screwed together. Forum members have cracked screens doing -> each of those, and the swap in [Step 2](#step-2--swap-the-pico-for-the-lyra) is the -> classic one — pressing a new board into the socket with the mainboard still in the case -> is enough on its own. That is exactly how the screen in this guide's own PicoCalc broke. +> ⚠ **WARNING: The screen is very fragile.** It is a bare glass panel with no frame. When +> the case is closed, the screen is directly against the mainboard. Each force that bends +> the mainboard also bends the glass. These are the actions that bend it: +> +> - You push a board into the Pico socket. +> - You pull a board out. +> - You close the shell when the screen is not square in its opening. +> - You press a key while you screw the two halves together. +> +> Forum members cracked screens in each of these ways. The swap in +> [Step 2](#step-2-swap-the-pico-for-the-lyra) is the classic case. If you press a new +> board into the socket while the mainboard is in the case, this alone can crack the +> screen. This is how the screen of the PicoCalc of the author broke. > ([Before replacing the Pico](https://forum.clockworkpi.com/t/before-replacing-the-pico-read-this-to-avoid-cracked-screen/16666), > [Avoid breaking your screen](https://forum.clockworkpi.com/t/avoid-breaking-your-screen/18805)) > -> What to do instead: +> Do these things: > -> - **Never push a board into the socket, or pull one out, with the mainboard in the case.** -> Unplug the display's ribbon cable (the FPC) from the mainboard, lift the mainboard out, -> and do the swap on a flat, padded surface — a folded microfibre cloth is ideal. Ease a -> board out a little at each end in turn, never by levering one end. -> - **Secure the screen with Kapton tape** along its edges so it cannot shift in the -> opening while you work and while the case is closed. Kapton is the right tape: thin, -> it stays put, and it peels off cleanly. Don't use anything that sets hard. -> - **Before the back goes on, check the screen sits flush and square** in the front half's -> opening with no pressure on any corner. If the mainboard doesn't sit perfectly flush on -> the front half, stop and find out why before you add the back. -> - Handle the panel as little as possible, and keep your fingers off the keyboard while -> closing the shell. +> - **Never push a board into the socket, or pull one out, while the mainboard is in the +> case.** Unplug the ribbon cable of the display (the FPC) from the mainboard. Lift the +> mainboard out. Do the swap on a flat, padded surface. A folded microfibre cloth is +> good. Ease a board out a little at each end in turn. Never lever one end. +> - **Hold the screen with Kapton tape** along its edges. Then it cannot move in the +> opening while you work or while the case is closed. Kapton is the correct tape. It is +> thin, it stays in position, and it comes off cleanly. Do not use a tape that sets +> hard. +> - **Before you fit the back, check that the screen is flush and square** in the opening +> of the front half, with no pressure on a corner. If the mainboard is not perfectly +> flush on the front half, stop. Find the cause before you add the back. +> - Touch the panel as little as possible. Keep your fingers off the keyboard while you +> close the shell. -> ⚠ **Don't overtighten the screws.** The shell's screw posts are brass inserts moulded -> into plastic, and forcing a screw snaps the post out of the shell — a repair that needs -> epoxy and never quite comes back +> ⚠ **WARNING: Do not tighten the screws too much.** The screw posts of the shell are +> brass inserts that are moulded into plastic. If you force a screw, the post breaks out of +> the shell. This repair needs epoxy and is never as good as the original > ([Broken screw thread](https://forum.clockworkpi.com/t/broken-screw-thread/20726)). -> This guide's author snapped several on the first assembly; it takes less force than you -> would think. -> Turn each screw until it just seats, then stop. If a screw won't go in easily, the halves -> aren't aligned, or something inside is in the way; more force is never the answer. +> The author broke several posts at the first assembly. You need less force than you think. +> Turn each screw until it just seats, then stop. If a screw does not go in easily, the two +> halves are not aligned, or a part inside is in the way. More force is never the answer. -> ⚠ **Never power the Lyra through its own USB-C while the batteries are in.** The -> PicoCalc charges its cells only through the **mainboard's** USB-C port, under the control -> of its power-management chip (an AXP2101). Power fed in through the core board's USB port -> — the Pico's originally, the Lyra's now — arrives on the header's system-power pin and -> bypasses that charge control. Forum members who powered the Pico's port directly measured -> their cells at **4.46 V and 4.74 V** afterwards, well over the 4.2 V a lithium cell is -> allowed to reach -> ([New PicoCalc — battery charging](https://forum.clockworkpi.com/t/new-picocalc-battery-charging/22330)). -> An overcharged cell is a fire risk, not just a worn one. +> ⚠ **WARNING: Never power the Lyra through its own USB-C while the batteries are in.** +> The PicoCalc charges its cells only through the USB-C port of the **mainboard**. A +> power-management chip (an AXP2101) controls the charge. Power that comes in through the +> USB port of the core board (the Pico's originally, now the Lyra's) goes to the +> system-power pin of the header. It bypasses the charge control. Forum members who powered +> the port of the Pico directly measured **4.46 V and 4.74 V** on their cells afterwards. +> This is much more than the 4.2 V that a lithium cell can have +> (["New PicoCalc — battery charging"](https://forum.clockworkpi.com/t/new-picocalc-battery-charging/22330)). +> An overcharged cell is a fire risk, not only a worn cell. > -> Nothing in this guide needs the Lyra's USB-C: the serial console, charging and power all -> use the mainboard's port on the side of the case, and the Wi-Fi dongle uses the 4-pin -> socket on top of the Lyra. If you ever must plug the Lyra's USB-C into a PC — to -> reflash it with Luckfox's tools, say — **take both batteries out first.** +> Nothing in this guide needs the USB-C of the Lyra. The serial console, the charging, and +> the power all use the port of the mainboard on the side of the case. The Wi-Fi dongle uses +> the 4-pin socket on top of the Lyra. If you must connect the USB-C of the Lyra to a PC +> (for example, to flash it again with the tools of Luckfox), **remove both batteries +> first.** --- -## Phase 1 — Build the machine +## Phase 1: Build the machine -### Step 1 — Assemble the PicoCalc +### Step 1: Assemble the PicoCalc -Follow ClockworkPi's own -[assembly guidelines](https://github.com/clockworkpi/PicoCalc/blob/master/Clockwork_PicoCalc_Assembly_Guidelines.pdf) -(a PDF in the [PicoCalc repository](https://github.com/clockworkpi/PicoCalc), which also -has a wiki) to put the kit together: mainboard, screen, keyboard, speakers, and shell. Insert your 18650 -battery (or two), watching the `+`/`-` marks in the battery compartment. +1. Follow the + [assembly guidelines](https://github.com/clockworkpi/PicoCalc/blob/master/Clockwork_PicoCalc_Assembly_Guidelines.pdf) + of ClockworkPi to put the kit together. The guidelines are a PDF in the + [PicoCalc repository](https://github.com/clockworkpi/PicoCalc), which also has a wiki. + You assemble the mainboard, the screen, the keyboard, the speakers, and the shell. +2. Put in your 18650 battery (or two). Look at the `+`/`-` marks in the battery + compartment. -⚠ Two of the warnings in [Three ways to break it](#three-ways-to-break-it) apply here: tape -the screen's edges with Kapton before you close the shell and check it sits square, and -turn the screws only until they seat. You will open the shell again in the next step, so -there is no reason to make the screws tight now. +WARNING: Two of the warnings in [Three ways to break it](#three-ways-to-break-it) apply +here. Before you close the shell, tape the edges of the screen with Kapton and check that +the screen is square. Turn the screws only until they seat. You open the shell again in +the next step. Thus there is no reason to make the screws tight now. -Power it on once with the **stock Pico still in place**. You should see the stock BASIC -firmware boot on the screen, and the keyboard should respond. This confirms the screen, -keyboard, and battery are all good before you start swapping boards. +3. Turn the power on one time with the **stock Pico still in place**. You must see the + stock BASIC firmware start on the screen. The keyboard must respond. This confirms that + the screen, the keyboard, and the battery are good before you replace boards. +4. Turn the power off. -Power it back off. +### Step 2: Swap the Pico for the Lyra -### Step 2 — Swap the Pico for the Lyra +1. Remove the back of the shell with the hex key. -Remove the back of the shell with the hex key. +WARNING: Do the swap with the mainboard out of the case. This step is the one that cracks +screens (refer to [Three ways to break it](#three-ways-to-break-it)). -⚠ **Do the swap with the mainboard out of the case.** This is the step that cracks screens -(see [Three ways to break it](#three-ways-to-break-it)). Unplug the display's ribbon cable -from the mainboard, lift the mainboard out, and lay it on a padded surface. Only then ease -the Raspberry Pi Pico out of its socket, a little at each end in turn — **keep it**, you're -just setting it aside. +2. Unplug the ribbon cable of the display from the mainboard. +3. Lift the mainboard out and put it on a padded surface. +4. Ease the Raspberry Pi Pico out of its socket, a little at each end in turn. **Keep the + Pico.** You only put it to one side. +5. Check if your Luckfox Lyra has SPI NAND on the board (a "Lyra B"). If it has, erase the + NAND first. If you do not erase it, the boot ROM ignores the SD card and starts from + the flash on the board. Refer to + [the hardware requirements page of Calculinux](https://calculinux.org/getting-started/hardware-requirements/) + to see how. +6. Put the Lyra in the same socket that the Pico left. Keep the same orientation. The USB-C + port of the Lyra must be at the end where the USB port of the Pico was. Then it lines up + with the cutout in the back of the shell. +7. Press the Lyra fully down while the mainboard is still on the padded surface. A Lyra + that is not fully seated does not start. +8. Connect the ribbon cable again. +9. Put the mainboard back in the front half. Check that the screen is square before you + tighten any screw. -If your Luckfox Lyra has SPI NAND on board (a "Lyra B"), erase it first — the boot ROM -otherwise ignores the SD card entirely and boots the on-board flash instead. See -[Calculinux's hardware requirements page](https://calculinux.org/getting-started/hardware-requirements/) -for how. +You must see the Lyra flat on the socket, with no gap at either end. After the mainboard is +back in the case, the USB-C port of the Lyra must be at the centre of the cutout in the +back panel. -Seat the Lyra into the same socket the Pico came out of, in the same orientation — its -USB-C port at the end where the Pico's USB port was, so it lines up with the cutout in the -back of the shell. Press it down fully with the mainboard still on the padded surface; a -Lyra that isn't fully seated won't boot. Then reconnect the ribbon cable and set the -mainboard back into the front half, checking the screen sits square before anything is -screwed down. +The cutout was made for the micro-USB port of the Pico. The USB-C connector of the Lyra has +a different shape. Depending on the connector that Luckfox fitted to your board, the back +panel may not close over it. If the panel catches, do these steps: -You should see the Lyra sitting flat on the socket with no gap at either end, and, once -the mainboard is back in, its USB-C port centred in the back-panel cutout. +1. Open the hole a little with a small needle file. +2. Test the fit as you file. +3. Stop when the panel sits down without pressure on the board. -The cutout was made for the Pico's micro-USB, and the Lyra's USB-C connector is a -different shape. Depending on the connector Luckfox fitted to your board, the back panel -may not close over it. If it catches, open the hole a little with a small needle file, -testing the fit as you go, until the panel sits down without pressing on the board. +Leave the back off for now. In [Step 4](#step-4-write-the-image-to-the-microsd), the +microSD card goes into the **slot of the Lyra itself**. You can reach that slot more easily +when the board is open. -Leave the back off for now. The microSD card goes into the **Lyra's own slot** in -[Step 4](#step-4--write-the-image-to-the-microsd), and that slot is easier to reach with the -board exposed. +### Step 3: Attach the Wi-Fi dongle -### Step 3 — Attach the Wi-Fi dongle +The Lyra has two USB connectors. The USB-C connector on its edge is for power and flashing. +You do not use it here. The **small 4-pin socket on top of the board** is the USB host. It +is an MX1.25 connector, next to the two buttons at the USB-C end. The Wi-Fi dongle connects +to it through a short lead that you make yourself. A ready-made cable is 30 cm long and is +difficult to fold into the shell. Also, you already have the soldering iron out for the +radio. -The Lyra has two USB connectors. The USB-C on its edge is for power and flashing and is -not used here. The **tiny 4-pin socket on top of the board** (an MX1.25 connector, next to -the two buttons by the USB-C end) is the USB host. The Wi-Fi dongle hangs off it through a -short lead you make yourself: a ready-made cable is 30 cm long and a struggle to fold into -the shell, and you have the soldering iron out for the radio anyway. - -**Make the lead.** Solder the four wires of an MX1.25 pigtail to a USB-A female socket, -keeping the run short — 5 to 8 cm is enough to reach a spot where the dongle sits flat. -The pigtail's pins are in the standard USB order, which the pre-made cable's wire colours -follow: +**Make the lead.** Solder the four wires of an MX1.25 pigtail to a USB-A female socket. +Keep the lead short. A length of 5 to 8 cm is enough to reach a place where the dongle sits +flat. The pins of the pigtail are in the standard USB order. The wire colours of the +ready-made cable follow this order: | MX1.25 pin | USB-A socket pin | Signal | Pigtail wire colour (usually) | | --- | --- | --- | --- | @@ -287,110 +315,124 @@ follow: | 3 | 3 | D+ | green | | 4 | 4 | GND | black | -USB-A socket pins are numbered 1 to 4 across the connector's opening: VBUS on one edge, -GND on the other, the data pair between them. Colours on a pigtail are a convention, not a -promise, so check pin 1 before you plug anything in: with the Lyra powered, the socket's -pin 1 reads **3.3 V** against a GND header pin and the pin at the other end reads 0 V. -Swapping VBUS and GND is the one mistake that damages the dongle. +The pins of the USB-A socket are numbered 1 to 4 across the opening of the connector. VBUS +is on one edge, GND is on the other edge, and the data pair is between them. The colours of +a pigtail are a convention, not a guarantee. Thus check pin 1 before you connect anything. -Once soldered, wrap the socket's solder side and the exposed wire in Kapton tape. Plug the -MX1.25 end into the socket on the Lyra — it is keyed and only goes in one way — and the -Wi-Fi dongle into the USB-A end. Lead and dongle stay inside the shell; tuck them where -they don't press on the board. +WARNING: Make sure that the soldering iron is in its stand when you do not use it. The tip +is hot enough to burn your skin. -The MX1.25 socket sits on top of the Lyra, and the lead leaves it upward, which is where -the back panel's grille is. The wire may interfere with the grille when the back goes on. -If it does, snip a small section out of the grille with the side cutters where the wire -passes and shape the edge until nothing presses on the plug or the wire. Better to remove a +CAUTION: Do not swap VBUS and GND. This is the one mistake that damages the dongle. + +1. Solder the four wires to the USB-A socket. +2. Turn the power of the Lyra on. +3. With a multimeter, measure pin 1 of the socket against a GND pin of the header. It must + read **3.3 V**. +4. Measure the pin at the other end of the socket. It must read 0 V. +5. Turn the power off. +6. Wrap the solder side of the socket and the bare wire in Kapton tape. +7. Push the MX1.25 end into the socket on the Lyra. The socket is keyed, and the plug goes + in only one way. +8. Push the Wi-Fi dongle into the USB-A end. +9. Put the lead and the dongle inside the shell, where they do not press on the board. + +The MX1.25 socket is on top of the Lyra, and the lead leaves it upward. The grille of the +back panel is there. The wire can interfere with the grille when you fit the back. If it +does, cut a small section out of the grille with the side cutters, where the wire passes. +Shape the edge until nothing presses on the plug or the wire. It is better to remove a sliver of plastic than to close the case against a connector. -> **The Lyra's USB port is 3.3 V, not 5 V.** A standard 5 V USB Wi-Fi dongle will not work -> and may damage the board. Stick to the tested chipsets — RTL8192CU, R8712U, or RTL8188EU -> — which is why the shopping list names a known dongle (the TP-Link TL-WN725N) and -> otherwise tells you to search by chipset rather than by brand. +> **CAUTION: The USB port of the Lyra is 3.3 V, not 5 V.** A standard 5 V USB Wi-Fi dongle +> does not work and can damage the board. Use only the tested chipsets: RTL8192CU, R8712U, +> or RTL8188EU. For this reason, the shopping list names a known dongle (the TP-Link +> TL-WN725N). For other dongles, it tells you to search for the chipset and not for the +> brand. -Don't power the device on yet — Wi-Fi is set up later, once Calculinux is installed. +Do not turn the power on yet. You set up Wi-Fi later, after you install Calculinux. --- -## Phase 2 — Install Calculinux and MeshTerm +## Phase 2: Install Calculinux and MeshTerm -### Step 4 — Write the image to the microSD +### Step 4: Write the image to the microSD -On your PC, download `calculinux-image-luckfox-lyra.rootfs-.wic.gz` from the -newest release on the -[Calculinux releases page](https://github.com/Calculinux/meta-calculinux/releases). Every -Calculinux release is marked **Pre-release**, so there is no "Latest" badge to follow; take -the newest pre-release. - -Decompress it: +1. On your PC, download `calculinux-image-luckfox-lyra.rootfs-.wic.gz` from the + newest release on the + [Calculinux releases page](https://github.com/Calculinux/meta-calculinux/releases). + Each Calculinux release is marked **Pre-release**. Thus there is no "Latest" badge to + follow. Take the newest pre-release. +2. Decompress the file: ```bash # on the PC gunzip calculinux-image-luckfox-lyra.rootfs-*.wic.gz ``` -(The Calculinux installation page mentions `unxz`, but the file is a `.gz`, so use -`gunzip`. 7-Zip works on any platform if you'd rather not use the command line.) + The Calculinux installation page mentions `unxz`, but the file is a `.gz`. Thus use + `gunzip`. If you do not want to use the command line, 7-Zip works on all platforms. -Write the resulting `.wic` file to your microSD card. Three options: +CAUTION: Write the `.wic` file to the correct device. This action erases everything that is +on the card. -- **Balena Etcher** (recommended, all platforms) — open it, select the `.wic` file, select - your SD card, click Flash. -- **`dd`**, on Linux/macOS. Check the device name first — writing to the wrong device - destroys its contents: +3. Write the resulting `.wic` file to your microSD card. Use one of these three tools: - ```bash + - **Balena Etcher** (recommended, all platforms): open it, select the `.wic` file, + select your SD card, and click Flash. + - **`dd`**, on Linux or macOS. Check the device name first. If you write to the wrong + device, you destroy its contents: + +```bash # on the PC lsblk # find your SD card's device, e.g. /dev/sdb sudo dd if=calculinux-image-luckfox-lyra.rootfs-*.wic of=/dev/sdX bs=4M status=progress conv=fsync - ``` +``` -- **Rufus**, on Windows — select the device and the image, choose the **MBR** partition - scheme, and start. + - **Rufus**, on Windows: select the device and the image, choose the **MBR** partition + scheme, and start. -This erases everything already on the card. Once it finishes, eject it and put it in the -**Luckfox Lyra's own microSD slot** — not the PicoCalc's front-facing slot. The Lyra boots -from its own slot; the front slot is only reachable once MeshTerm (or anything else) is -already running on Linux. +4. When the write is complete, eject the card. +5. Put the card in the **Luckfox Lyra's own microSD slot**. Do not use the front slot + of the PicoCalc. The Lyra starts from its own slot. You can reach the front slot only + after MeshTerm (or another program) already runs on Linux. +6. Leave the front slot **empty** for the first boot. The instructions of Calculinux also + say this. A second card there that has the same partition labels makes the system + read-only. -Leave the front slot **empty** for the first boot, as Calculinux's own instructions warn: a -second card there with duplicate partition labels leaves the system read-only. +### Step 5: First boot -### Step 5 — First boot - -With the card in the Lyra, power the PicoCalc on. Give it 30–60 seconds — the first boot is -slower than later ones. You should see boot messages scroll on the PicoCalc's own screen, -ending in a `Calculinux GNU/Linux` login prompt. - -Log in: +1. With the card in the Lyra, turn the PicoCalc on. +2. Wait 30–60 seconds. The first boot is slower than the next boots. Boot messages scroll + on the display of the PicoCalc. They end with a `Calculinux GNU/Linux` login prompt. +3. Log in: ``` login: root Password: root ``` -Change the password immediately: +4. Change the password at once: ```bash # on the PicoCalc, as root passwd ``` -### Step 6 — Open the serial console +### Step 6: Open the serial console -The PicoCalc's own screen and keyboard work fine, so you can skip this step. The serial -console makes pasting the longer commands in this guide much easier, though, so it is -recommended. +The display and the keyboard of the PicoCalc work well, thus you can skip this step. But +the serial console makes it much easier to paste the longer commands in this guide. We +recommend it. -The PicoCalc mainboard's USB-C port carries a USB-to-UART bridge (a CH340 chip). Plug a -USB-C cable from that port into your PC. It shows up as: +The USB-C port of the PicoCalc mainboard has a USB-to-UART bridge (a CH340 chip). -- `/dev/ttyUSB0` on Linux (you may need to be in the `dialout` group), -- a `COM` port on Windows, -- `/dev/cu.usbserial-*` on macOS. +1. Connect a USB-C cable from that port to your PC. The port shows as: -Settings: **1500000 8N1**, no flow control. Using `pyserial`'s `miniterm`: + - `/dev/ttyUSB0` on Linux (you may need to be in the `dialout` group) + - a `COM` port on Windows + - `/dev/cu.usbserial-*` on macOS + +2. Open a terminal program with these settings: **1500000 8N1**, no flow control. This + example uses `miniterm` of `pyserial`: ```bash # on the PC @@ -398,16 +440,21 @@ pip install pyserial python3 -m serial.tools.miniterm /dev/ttyUSB0 1500000 ``` -minicom and PuTTY work too, at the same settings. To leave `miniterm`, press **Ctrl+]**. +minicom and PuTTY also work, with the same settings. To leave `miniterm`, press +**Ctrl+]**. -You should see the same login prompt you saw on the PicoCalc's screen, now in your PC's -terminal. +You must see the same login prompt that you saw on the display of the PicoCalc, now in the +terminal of your PC. -### Step 7 — Join Wi-Fi +### Step 7: Join Wi-Fi -Calculinux manages Wi-Fi with `iwd`. The easiest way is its text-mode picker: run `uwific`, -highlight your network, press Enter and type the passphrase (`Q` quits). Or do the same -from the command line: +Calculinux manages Wi-Fi with `iwd`. The easiest way is its text-mode picker. + +1. Run `uwific`. +2. Highlight your network and press Enter. +3. Type the passphrase. (`Q` quits.) + +You can do the same from the command line: ```bash # on the PicoCalc, as root @@ -416,31 +463,32 @@ iwctl station wlan0 get-networks iwctl station wlan0 connect "" ``` -It'll prompt for the passphrase. Check you're online: +The last command asks for the passphrase. Then check that you are online: ```bash ip addr show wlan0 ping -c 3 1.1.1.1 ``` -You should see an `inet` address on `wlan0`, and replies from the ping. `iwd` remembers -the network, so this is a one-time step; to change networks later, run `uwific` again. +You must see an `inet` address on `wlan0`, and replies from the ping. `iwd` remembers the +network. Thus you do this step one time. To change networks later, run `uwific` again. -### Step 8 — Copy the setup scripts to the device +### Step 8: Copy the setup scripts to the device -You need this repository's `scripts/` directory on the Lyra — its `picocalc-lyra/` folder is the -part the Lyra runs. Two ways to get it there, once Wi-Fi is up: +You need the `scripts/` directory of this repository on the Lyra. The Lyra runs the +`picocalc-lyra/` folder in it. When Wi-Fi is up, you can get the directory there in two +ways: -- **`scp` from your PC**. Find the Lyra's IP address with `ip addr show wlan0` on the - device, then from your PC: +- **`scp` from your PC.** On the Lyra, run `ip addr show wlan0` to find its IP address. + Then, on your PC, run: ```bash # on the PC, from your MeshTerm checkout scp -r scripts root@:/root/ ``` - If root is refused over SSH, copy as the `pico` user (password `calc`) instead, then - move the directory as root on the device: + If SSH refuses root, copy as the `pico` user (password `calc`). Then move the directory + as root on the Lyra: ```bash # on the PC @@ -449,13 +497,13 @@ part the Lyra runs. Two ways to get it there, once Wi-Fi is up: mv /home/pico/scripts /root/ ``` -- **`git clone` on the device.** `git` ships in the Calculinux base image, so as root you - can run `git clone https://github.com/jpmartineau/MeshTerm` and use the `scripts/` - folder inside the clone. +- **`git clone` on the Lyra.** The Calculinux base image includes `git`. As root, run + `git clone https://github.com/jpmartineau/MeshTerm`. Then use the `scripts/` folder in + the clone. -`scp` is the recommended route. +We recommend `scp`. -### Step 9 — Run calculinux-setup.sh +### Step 9: Run calculinux-setup.sh ```bash # on the PicoCalc, as root, with scripts/ copied over @@ -463,79 +511,84 @@ cd scripts/picocalc-lyra sh calculinux-setup.sh ``` -It's idempotent — safe to re-run — and it checks before it acts. It reads these -environment variables at the top of the script: +You can run the script again without a problem, and it checks before it acts. It reads +these environment variables at the top of the script: | Knob | Default | Effect | | --- | --- | --- | -| `DEPLOY_USER` | `meshterm` | the login MeshTerm runs under | -| `TIMEZONE` | `America/Toronto` | any IANA zone. Set it if you are not on Eastern time. | -| `REPO_URL` | the project's HTTPS URL | cloned when there's no deploy key | -| `REPO_SSH` | the project's SSH URL | cloned when there is one | -| `KEY_PATH` | `/home/$DEPLOY_USER/.ssh/id_ed25519` | a read-only GitHub deploy key, if you want SSH instead of HTTPS | -| `WIFI_SSID`, `WIFI_PSK` | unset | set both and the script writes `iwd`'s credentials for that network, as an alternative to Step 7 | +| `DEPLOY_USER` | `meshterm` | The login that MeshTerm runs under | +| `TIMEZONE` | `America/Toronto` | Any IANA zone. Set it if you are not on Eastern time. | +| `REPO_URL` | the HTTPS URL of the project | The script clones this URL when there is no deploy key. | +| `REPO_SSH` | the SSH URL of the project | The script clones this URL when there is a deploy key. | +| `KEY_PATH` | `/home/$DEPLOY_USER/.ssh/id_ed25519` | A read-only GitHub deploy key, if you want SSH and not HTTPS | +| `WIFI_SSID`, `WIFI_PSK` | unset | If you set both, the script writes the credentials of `iwd` for that network. This is an alternative to Step 7. | -The defaults clone MeshTerm's public repository over plain HTTPS, which needs no credential -at all. The one you are likely to need is `TIMEZONE`, if you live outside Eastern time: +The defaults clone the public repository of MeshTerm over plain HTTPS. This needs no +credential. You probably need only `TIMEZONE`, if you live outside Eastern time: ```bash # on the PicoCalc, as root TIMEZONE=America/Vancouver sh calculinux-setup.sh ``` -The script installs only the Americas time-zone data, so a zone elsewhere may not take. If -it cannot set the zone, it warns and the clock stays on UTC. +The script installs only the time-zone data for the Americas. Thus a zone in another region +may not work. If the script cannot set the zone, it prints a warning and the clock stays on +UTC. -What it does, one line per phase: +The script does these things, one for each phase: -- **opkg packages** — installs `python3-modules` and `python3-pip` (and `git` and `kbd`, - if the image lacks them), because Calculinux ships a stripped Python missing `pip`, - `venv` and several stdlib modules. -- **Wi-Fi kick** — installs a boot-time service that re-scans until the dongle joins the - network `iwd` already knows, because on a cold boot the dongle's firmware finishes - loading after `iwd`'s first scan and the link would otherwise never come up. -- **Time sync** — installs a boot-time service that sets the clock over the network, since - this board has no battery-backed real-time clock. -- **Timezone** — sets `$TIMEZONE` (`America/Toronto` by default). -- **Deploy user** — creates the `meshterm` login and puts it in the groups it needs. -- **Clone** — checks out MeshTerm to `/home/meshterm/MeshTerm`. -- **venv and install** — builds a Python virtual environment and `pip install -e .`s - MeshTerm into it. -- **Login PATH** — puts that venv's `bin` on the `meshterm` user's PATH. -- **Console font** — builds and installs the console font MeshTerm's UI needs (braille - charts, node glyphs, rounded corners). +- **opkg packages.** It installs `python3-modules` and `python3-pip`. It also installs + `git` and `kbd` if the image does not have them. Calculinux has a reduced Python that + does not have `pip`, `venv`, and several modules of the standard library. +- **Wi-Fi kick.** It installs a service for boot time that scans again until the dongle + joins the network that `iwd` already knows. On a cold boot, the firmware of the dongle + finishes loading after the first scan of `iwd`. Without the kick, the link does not come + up. +- **Time sync.** It installs a service for boot time that sets the clock over the network, + because this board has no battery-backed real-time clock. +- **Timezone.** It sets `$TIMEZONE` (`America/Toronto` by default). +- **Deploy user.** It creates the `meshterm` login and puts it in the groups that it needs. +- **Clone.** It checks out MeshTerm to `/home/meshterm/MeshTerm`. +- **venv and install.** It builds a Python virtual environment and runs `pip install -e .` + to install MeshTerm in it. +- **Login PATH.** It puts the `bin` of that venv on the PATH of the `meshterm` user. +- **Console font.** It builds and installs the console font that the UI of MeshTerm needs + (braille charts, node glyphs, and rounded corners). -This takes a few minutes on a good connection — most of it is `pip install` compiling -things. It ends with `done` and tells you to log in as `meshterm`. That user has no -password yet, so set one first: +This takes a few minutes on a good connection. Most of the time is `pip install`, which +compiles code. The script ends with `done` and tells you to log in as `meshterm`. That user +has no password yet. Thus set one first: ```bash # on the PicoCalc, as root passwd meshterm ``` -> **With no Wi-Fi dongle attached, the next boot can take up to about eleven minutes.** -> The Wi-Fi kick keeps trying for about six before it gives up, and the time sync then -> waits up to five more for a network route. Nothing is broken. If the device is going to -> live offline, see [Day-to-day](#day-to-day) for how to switch both off. +> **If no Wi-Fi dongle is attached, the next boot can take up to approximately eleven +> minutes.** The Wi-Fi kick tries for approximately six minutes before it stops. Then the +> time sync waits up to five more minutes for a network route. Nothing is broken. If the +> PicoCalc will stay offline, refer to [Day-to-day](#day-to-day) to switch both off. -### Step 10 — Log in as meshterm +### Step 10: Log in as meshterm -Log out of `root`, and log in as `meshterm` with the password you just set. Try the app: +1. Log out of `root`. +2. Log in as `meshterm` with the password that you just set. +3. Try the app: ```bash # on the PicoCalc, as meshterm meshterm --mock ``` -You should see MeshTerm's full-screen interface come up at **53 columns**, with a simulated -radio behind it, drawing braille charts and node glyphs cleanly — no empty tofu boxes. Along -the bottom you should see the **F-key lane**: five labelled chips instead of the footer hint -line a desktop terminal shows. +The full-screen interface of MeshTerm must start at **53 columns**, with a simulated radio +behind it. It must draw braille charts and node glyphs cleanly, with no empty boxes. Along +the bottom, you must see the **F-key lane**: five labelled chips, not the footer hint line +that a desktop terminal shows. -Have a look around. **Esc** backs out of any screen. To leave the app, choose **Quit** on -the main menu, or hold **Esc** for three seconds from any screen. After a second a box -shows a bar filling up, and letting go before it fills keeps you where you were. +Look around in the app. **Esc** goes back from any screen. To leave the app, choose **Quit** +on the main menu, or hold **Esc** for three seconds from any screen. After one second, a box +shows a bar that fills. If you release **Esc** before the bar is full, you stay where you +were. Then look at the whole visual language on one card: @@ -544,40 +597,40 @@ Then look at the whole visual language on one card: meshterm specimen ``` -There are two console fonts available — a 6×12 one (53 × 26) and a 6×8 one (53 × 40, -more rows but no Cyrillic glyphs). Try both from *Preferences → Display → Console font* — -picking one previews it immediately, and the screen repaints at the new row count so you can -see the choice before you keep it. +Two console fonts are available: a 6×12 font (53 × 26) and a 6×8 font (53 × 40, which has +more rows but no Cyrillic glyphs). Try both from *Preferences → Display → Console font*. +When you select a font, MeshTerm shows it at once. The screen is painted again with the new +row count, so you can see the result before you keep the choice. --- -## Phase 3 — Add the radio +## Phase 3: Add the radio -This phase can wait. Everything above already gives you a working MeshTerm in `--mock` -mode. Come back to this when you have the soldering iron out. +You can do this phase later. The steps above already give you a working MeshTerm in `--mock` +mode. Come back to this phase when you have the soldering iron out. -### Step 11 — Install PlatformIO +### Step 11: Install PlatformIO -On your PC: +On your PC, install PlatformIO: ```bash # on the PC pip install platformio ``` -(or use the +If you do not want to change the system Python, use the [official installer script](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html) -if you'd rather not touch your system Python). Confirm `pio` is on your PATH: +instead. Then make sure that `pio` is on your PATH: ```bash # on the PC pio --version ``` -The first firmware build downloads the nRF52 toolchain — several hundred megabytes — so do -this somewhere with a decent connection. +The first firmware build downloads the nRF52 toolchain, which is several hundred +megabytes. Do this where you have a good connection. -### Step 12 — Build the firmware +### Step 12: Build the firmware ```bash # on the PC (Git Bash or WSL on Windows) @@ -585,57 +638,58 @@ cd scripts/picocalc-lyra/xiao-radio sh build-firmware.sh ``` -You should see it clone MeshCore, apply a patch, build, and finish with: +The script clones MeshCore, applies a patch, and builds. It ends with this output: ``` DONE. Firmware: /path/to/scripts/picocalc-lyra/xiao-radio/meshcore-xiao-radio.uf2 Next: put the XIAO in bootloader (double-tap reset) and run: python flash.py ``` -Here's what it did, so you could do it by hand if you needed to: +These are the actions of the script. You can do them by hand if you must: -1. Cloned [MeshCore](https://github.com/meshcore-dev/MeshCore) into +1. Clone [MeshCore](https://github.com/meshcore-dev/MeshCore) into `scripts/picocalc-lyra/xiao-radio/_meshcore-build`. -2. Checked out the pinned commit `e9edfc8e` on the `dev` branch — the commit this patch is - known to apply cleanly to and build against. -3. Applied `meshcore-uart1.patch`. -4. Built the `Xiao_nrf52_companion_radio_serial` PlatformIO environment: +2. Check out the pinned commit `e9edfc8e` on the `dev` branch. This is the commit that the + patch applies to and builds against without a problem. +3. Apply `meshcore-uart1.patch`. +4. Build the `Xiao_nrf52_companion_radio_serial` PlatformIO environment: `pio run -e Xiao_nrf52_companion_radio_serial`. -5. Converted the resulting `.hex` to a `.uf2` with MeshCore's own converter, using the - nRF52840 UF2 family id: +5. Convert the resulting `.hex` file to a `.uf2` file with MeshCore's own converter. + Use the UF2 family id of the nRF52840: `python bin/uf2conv/uf2conv.py firmware.hex -c -f 0xADA52840 -o meshcore-xiao-radio.uf2`. -#### What the patch changes, and why it is still needed +#### What the patch changes, and why it is still necessary -`meshcore-uart1.patch` touches two files, and both hunks are required: +`meshcore-uart1.patch` changes two files. Both changes are necessary: -- **`examples/companion_radio/main.cpp`.** MeshCore's companion-over-serial code declares - `HardwareSerial companion_serial(1)` — a numbered constructor. On the Adafruit nRF52 core, - `HardwareSerial` is an abstract base class with no numbered constructor at all, so this - line simply doesn't compile for the XIAO nRF52840. The patch adds an `#if - defined(NRF52_PLATFORM)` branch that binds `companion_serial` to `Serial1` instead — the - concrete UART object the nRF52 core always provides. `Uart::setPins(rx, tx)`, called later - in the same file, takes the same arguments either way, so nothing else needs to change. -- **`variants/xiao_nrf52/platformio.ini`.** This adds a new - `Xiao_nrf52_companion_radio_serial` build environment that puts the companion serial link - on pins **D6 (TX)** and **D7 (RX)**. It also **moves I²C off those same two pads**, onto - two internal pins, 16 and 17, that nothing in this build uses. This second change is the one that matters: the - stock `Xiao_nrf52` environment maps I²C onto D6/D7 by default, and both the board's - `begin()` and the sensor code start the I²C bus on them automatically. Without the move, - I²C seizes the UART pins at boot — the wiring is correct, the firmware builds and runs, - and the serial link is still completely silent, because something else already owns the - pins. +- **`examples/companion_radio/main.cpp`.** The companion-over-serial code of MeshCore + declares `HardwareSerial companion_serial(1)`. This is a numbered constructor. On the + Adafruit nRF52 core, `HardwareSerial` is an abstract base class and has no numbered + constructor. Thus this line does not compile for the XIAO nRF52840. The patch adds an + `#if defined(NRF52_PLATFORM)` branch. This branch binds `companion_serial` to `Serial1` + instead. `Serial1` is the concrete UART object that the nRF52 core always provides. + `Uart::setPins(rx, tx)` is called later in the same file. It takes the same arguments in + both cases, thus nothing else needs to change. +- **`variants/xiao_nrf52/platformio.ini`.** This change adds a new + `Xiao_nrf52_companion_radio_serial` build environment. The environment puts the companion + serial link on pins **D6 (TX)** and **D7 (RX)**. It also **moves I²C off those same two + pads**, to two internal pins, 16 and 17, that nothing in this build uses. This second + change is the important one. The stock `Xiao_nrf52` environment maps I²C to D6/D7 by + default. The `begin()` of the board and the sensor code both start the I²C bus on these + pins automatically. Without the move, I²C takes the UART pins at boot. The wiring is + correct, and the firmware builds and runs. But the serial link is completely silent, + because another function already owns the pins. -Both changes have been submitted upstream to MeshCore, but as of this writing they are -**not merged**, and there's no sign that will happen soon — upstream `dev` still declares -the bare `HardwareSerial(1)` with no nRF52 branch, and the XIAO's `platformio.ini` still has -no `_serial` companion environment. Building with the patch is the normal route here, not a -stopgap; when the patch does land, the `git apply` step (and the patch file itself) can be -deleted. +We submitted both changes to MeshCore upstream. At the time of writing, they are **not +merged**, and there is no sign that this will happen soon. The upstream `dev` branch still +declares the bare `HardwareSerial(1)` with no nRF52 branch. The `platformio.ini` of the XIAO +still has no `_serial` companion environment. To build with the patch is the normal route +here, not a temporary measure. When the patch is merged, you can delete the `git apply` +step and the patch file. #### Building by hand -If you want to track newer MeshCore instead of the pinned commit: +If you want to follow a newer MeshCore and not the pinned commit, run: ```bash # on the PC @@ -648,8 +702,8 @@ python bin/uf2conv/uf2conv.py .pio/build/Xiao_nrf52_companion_radio_serial/firmw -c -f 0xADA52840 -o meshcore-xiao-radio.uf2 ``` -If `git apply` fails — upstream has moved and the patch context no longer lines up — the two -hunks are small enough to make by hand: +If `git apply` fails, upstream has changed and the context of the patch does not match. The +two hunks are small, so you can make them by hand: 1. In `examples/companion_radio/main.cpp`, find the line `HardwareSerial companion_serial(1);` and replace it with: @@ -691,101 +745,119 @@ hunks are small enough to make by hand: densaugeo/base64 @ ~1.4.0 ``` -Only the pinned commit (`e9edfc8e`) is known to build cleanly with this patch — a newer -`dev` may need small further adjustments. +Only the pinned commit (`e9edfc8e`) is known to build without a problem with this patch. A +newer `dev` can need small changes. -### Step 13 — Flash the XIAO +### Step 13: Flash the XIAO -Plug the **XIAO's own USB-C port** into your PC — it doesn't matter yet whether it's also -wired to the Lyra. Then: +CAUTION: Make sure that the XIAO is the board that you flash and that it has the S140 v7 +bootloader. A flash to the wrong board, or to the wrong SoftDevice, can leave a board that +does not start. `flash.py` checks both before it writes. + +1. Connect the **USB-C port of the XIAO itself** to your PC. It does not matter yet if the + XIAO is also wired to the Lyra. +2. Run the flash tool: ```bash # on the PC python flash.py ``` -It first tries a 1200-baud "touch" over USB serial to reset the XIAO into its bootloader — -this only ever targets Seeed's own USB vendor id, so it won't disturb another nRF52840 board -on the same bench. If that doesn't take, **double-tap the XIAO's reset button** and run it -again. +The tool first tries a 1200-baud "touch" over USB serial. This resets the XIAO into its +bootloader. The touch targets only the USB vendor id of Seeed. Thus it does not disturb +another nRF52840 board on the same bench. If the touch does not work, **double-tap the reset +button of the XIAO** and run the tool again. -Once it finds the bootloader, it checks two things before writing anything: +When the tool finds the bootloader, it checks two things before it writes anything: -- **`INFO_UF2.TXT`'s `Model` field** must say a XIAO — a drive letter isn't an identity, and - another board could have inherited the letter your XIAO just gave up. -- **`SoftDevice` must not be `6.1.1`.** This firmware links for SoftDevice S140 v7 (app at - `0x27000`); a S140 6.1.1 bootloader wants the app at `0x26000` and would flash successfully - and then simply never boot. +- **The `Model` field of `INFO_UF2.TXT`** must say a XIAO. A drive letter is not an + identity. Another board can have the letter that your XIAO just gave up. +- **`SoftDevice` must not be `6.1.1`.** This firmware links for SoftDevice S140 v7 (the app + is at `0x27000`). An S140 6.1.1 bootloader wants the app at `0x26000`. The flash then + succeeds, but the board never starts. -`--force` overrides both checks, if you're certain. A clean flash ends with: +If you are certain, `--force` overrides both checks. A good flash ends with this output: ``` DONE. XIAO flashed and rebooting into the radio firmware. ``` -**If no UF2 drive ever appears**, that's not necessarily wrong — some XIAO bootloaders -expose only a serial port, no mass-storage drive at all. `flash.py` recognises this case and -prints the fallback command instead of failing silently: +**If no UF2 drive ever shows**, this is not always an error. Some XIAO bootloaders show +only a serial port and no mass-storage drive. `flash.py` knows this case. It prints the +fallback command and does not fail silently: ```bash # on the PC, from your MeshCore checkout pio run -e Xiao_nrf52_companion_radio_serial -t upload --upload-port ``` -That command ends in `Device programmed.` and the XIAO reboots itself into the radio +This command ends with `Device programmed.` and the XIAO restarts itself into the radio firmware. -Afterwards, the XIAO's USB serial port goes **silent** to companion frames — the companion -protocol now lives on D6/D7, not on USB. That silence is correct; it doesn't mean the flash -failed. +After the flash, the USB serial port of the XIAO is **silent** to companion frames. The +companion protocol is now on D6/D7, not on USB. This silence is correct. It does not mean +that the flash failed. -### Step 14 — Wire the radio +### Step 14: Wire the radio -Four wires, TX and RX crossed: +You solder four wires. TX and RX are crossed: | XIAO pad | → | Lyra header | Physical pin | Carries | | --- | --- | --- | --- | --- | | **D7** (Serial1 RX) | → | **GP4** | pin **6** | Lyra UART1 **TX** → XIAO RX | | **D6** (Serial1 TX) | → | **GP5** | pin **7** | XIAO **TX** → Lyra UART1 RX | | **GND** | → | **GND** | pin **8** | common ground | -| **3V3** | → | **3V3 OUT** | pin **36** | power, so it runs without USB | +| **3V3** | → | **3V3 OUT** | pin **36** | power, so that it runs without USB | -> **The Lyra does not use Raspberry Pi Pico GPIO numbering.** The 40-pin header is -> physically Pico-shaped, but each pad's function is Luckfox's own RK3506 mapping: GP4 is -> `gpio0-0` and GP5 is `gpio0-1`. Don't reach for a Pico pinout for anything else on this -> header. Every signal here is 3.3 V, and there is **no 5 V rail** on this board — power the -> XIAO from 3V3 only. +> **The Lyra does not use the GPIO numbering of the Raspberry Pi Pico.** The 40-pin header +> has the physical shape of the Pico header. But the function of each pad is the RK3506 +> mapping of Luckfox. GP4 is `gpio0-0` and GP5 is `gpio0-1`. Do not use a Pico pinout for +> other pins on this header. Each signal here is 3.3 V, and this board has **no 5 V rail**. +> Power the XIAO from 3V3 only. -With the PicoCalc powered off and the back of the shell removed, solder the four wires -between the XIAO's pads and the Lyra's header pins as above. The Lyra can stay seated in -its socket: the tops of its header pins are reachable from inside the case, so solder to -those. Keep the iron on each pin only as long as it takes, and don't press down on the -board. Attach the LoRa antenna to the Wio-SX1262's u.FL connector before you -power anything back on — running the radio without an antenna attached can damage it. -Wrap the XIAO + Wio-SX1262 stack in Kapton tape so no pad can touch the Lyra, the -mainboard, or a battery, then tuck it into the shell wherever it fits without straining the -wires; a strip of foam tape holds it in place against the inside of the case. +1. Turn the PicoCalc off and remove the back of the shell. +2. Remove the batteries. -### Step 15 — Set up the Lyra +WARNING: Put the soldering iron in its stand when you do not use it. The tip is hot enough +to burn your skin. Solder in a place that has good airflow. -With the wiring done and the shell closed back up, copy `scripts/` to the device if you -haven't already (Step 8), then: +CAUTION: Keep the iron on each pin only as long as necessary, and do not press down on the +board. Too much heat or force can damage the Lyra. + +3. Solder the four wires between the pads of the XIAO and the header pins of the Lyra, as + the table shows. The Lyra can stay in its socket. The tops of its header pins are + accessible from inside the case, so solder to those. + +CAUTION: Attach the LoRa antenna to the u.FL connector of the Wio-SX1262 before you turn +any power on. If you run the radio without an antenna, you can damage it. + +4. Attach the LoRa antenna to the u.FL connector of the Wio-SX1262. +5. Wrap the XIAO + Wio-SX1262 stack in Kapton tape. Then no pad can touch the Lyra, the + mainboard, or a battery. +6. Put the stack in the shell, in a place where it fits without strain on the wires. +7. Use a strip of foam tape to hold it against the inside of the case. + +### Step 15: Set up the Lyra + +1. Close the shell. Put the batteries back in. +2. If you did not do Step 8, copy `scripts/` to the Lyra now. +3. Run the setup script: ```bash # on the PicoCalc, as root sh scripts/picocalc-lyra/xiao-radio/lyra-setup.sh # MT_USER=meshterm by default ``` -It does three idempotent things: +You can run this script again without a problem. It does three things: -1. Installs `uart1-mux.py` as `/usr/local/bin/uart1-radio-mux.py`, run by a oneshot - `uart1-radio-mux.service` on every boot. This is the part that actually makes - `/dev/ttyS1` reach the header pins — Calculinux enables the UART1 controller but never - wires it to a physical pad on its own, and the RK3506's flexible pin matrix needs a - direct register poke to route it onto GP4/GP5 instead. -2. Adds `$MT_USER` to the `dialout` group, so it can open the serial port. **This needs a - fresh login before it takes effect.** -3. Writes a MeshTerm profile, *only if none exists yet*, at +1. It installs `uart1-mux.py` as `/usr/local/bin/uart1-radio-mux.py`. A oneshot service, + `uart1-radio-mux.service`, runs it at each boot. This part makes `/dev/ttyS1` reach the + header pins. Calculinux enables the UART1 controller, but it never wires the controller + to a physical pad. The flexible pin matrix of the RK3506 needs a direct register poke to + route UART1 to GP4/GP5. +2. It adds `$MT_USER` to the `dialout` group, so that this user can open the serial port. + **This change takes effect only after a new login.** +3. It writes a MeshTerm profile, *only if none exists yet*, at `/home/$MT_USER/.meshterm/config.toml`: ```toml @@ -799,50 +871,52 @@ default_tx_power = 22 description = "XIAO nRF52840 + SX1262 via Lyra UART1 on GP4/GP5" ``` -If a `config.toml` already exists, it leaves your file untouched and prints only the -`default_profile` line, the `[profiles.picocalc]` heading and the `port`. Copy the whole +If a `config.toml` already exists, the script does not change your file. It prints only the +`default_profile` line, the `[profiles.picocalc]` heading, and the `port`. Copy the whole block above into your file yourself. -### Step 16 — Run it +### Step 16: Run it -Log out and back in as `meshterm` (so the `dialout` group membership takes effect), then: +1. Log out and log in again as `meshterm`. Then the `dialout` group membership takes + effect. +2. Start MeshTerm: ```bash # on the PicoCalc, as meshterm meshterm ``` -A bare `meshterm` opens on the **Select a companion device** splash, with the `picocalc` -profile listed as a row; pick it. To skip the splash, run `meshterm -p picocalc`. You -should see your node come up under whatever name the firmware advertises — both the device -page and the dashboard header show it. +A bare `meshterm` opens on the **Select a companion device** splash. The `picocalc` profile +is a row in the list. Select it. To skip the splash, run `meshterm -p picocalc`. Your node +must start, under the name that the firmware advertises. Both the device page and the +dashboard header show the name. -If you want to prove the wiring itself before suspecting anything else, there's a deeper -check: open `/dev/ttyS1` at 115200 as root and send a raw `APP_START` frame (`3c 0e 00 01`, -seven zero bytes, then a name) — a live radio answers with a framed `05`, `SELF_INFO`. The -command byte and the reply code are the companion protocol's; the `<`/`>` and -little-endian-length framing around them come from the firmware and are worth confirming -against it. It's a lower-level check than running MeshTerm and mostly useful for isolating -a wiring problem. +If you want to prove the wiring before you look for other causes, there is a deeper check. +Open `/dev/ttyS1` at 115200 as root. Send a raw `APP_START` frame (`3c 0e 00 01`, seven zero +bytes, then a name). A live radio answers with a framed `05`, `SELF_INFO`. The command byte +and the reply code are from the companion protocol. The `<` and `>` marks and the +little-endian length in the framing come from the firmware. Confirm them against the +firmware. This check is lower-level than a run of MeshTerm. It is mostly useful to find a +wiring problem. --- ## Day-to-day -**Charging.** Only through the mainboard's USB-C port on the side of the case. ⚠ Never -through the Lyra's own USB-C at the back — see +**Charging.** Charge only through the USB-C port of the mainboard on the side of the case. +⚠ WARNING: Never charge through the USB-C of the Lyra at the back. Refer to [Three ways to break it](#three-ways-to-break-it). -**Powering off.** Shut down cleanly rather than pulling the batteries: +**Powering off.** Shut down the system. Do not pull the batteries: ```bash # on the PicoCalc sudo poweroff ``` -**Updating MeshTerm.** The copy of `scripts/` you made in Step 8 goes stale as soon as -the checkout moves on, so run the scripts inside the checkout instead. Either pull and -re-run the setup script from root (it reinstalls and rebuilds the console font): +**Updating MeshTerm.** The copy of `scripts/` that you made in Step 8 becomes old as soon as +the checkout changes. Thus run the scripts inside the checkout. You can pull and run the +setup script again from root. This reinstalls MeshTerm and builds the console font again: ```bash # on the PicoCalc, as root @@ -850,27 +924,27 @@ su - meshterm -c 'git -C ~/MeshTerm pull' sh /home/meshterm/MeshTerm/scripts/picocalc-lyra/calculinux-setup.sh ``` -or, as the `meshterm` user, do just the update: +Or, as the `meshterm` user, do only the update: ```bash # on the PicoCalc, as meshterm cd ~/MeshTerm && git pull && TMPDIR=$HOME/tmp .venv/bin/pip install -e . ``` -If an update adds new glyphs to the console font, rebuild it on its own afterwards: +If an update adds new glyphs to the console font, build the font again afterward: ```bash # on the PicoCalc, as root sh /home/meshterm/MeshTerm/scripts/picocalc-lyra/calculinux-console-font-6x12.sh ``` -**Changing Wi-Fi.** Run `uwific` as root, as in [Step 7](#step-7--join-wi-fi). The boot-time -kick picks up the new network on its own; it only needs `iwd` to know it. +**Changing Wi-Fi.** Run `uwific` as root, as in [Step 7](#step-7-join-wi-fi). The kick at +boot time finds the new network by itself. It needs only that `iwd` knows the network. -**The slow boot without a dongle.** If the device is going to run permanently offline, -the Wi-Fi kick and the time sync are wasted time on every boot. Disabling the kick is not -enough, because the time sync pulls it back in, so mask it. Then disable the time sync, -which would otherwise still wait up to five minutes for a network: +**The slow boot without a dongle.** If the PicoCalc will always run offline, the Wi-Fi kick +and the time sync waste time at each boot. It is not enough to disable the kick, because the +time sync starts it again. Thus mask the kick. Then disable the time sync. Without this, the +time sync waits up to five minutes for a network: ```bash # on the PicoCalc, as root @@ -878,12 +952,12 @@ systemctl mask --now wifi-kick.service systemctl disable --now time-sync.service ``` -To undo it later, `systemctl unmask wifi-kick.service` and +To undo this later, run `systemctl unmask wifi-kick.service` and `systemctl enable time-sync.service`. -**The console font restores itself.** MeshTerm saves whatever font the console was using -before it starts, and puts it back when it exits — on a normal quit and on a crash alike. -You don't need to do anything to keep the shell's own font intact. +**The console font restores itself.** MeshTerm saves the font that the console used before +it starts. It puts that font back when it exits, after a normal quit and also after a crash. +You do not need to do anything to keep the shell's own font. --- @@ -891,57 +965,64 @@ You don't need to do anything to keep the shell's own font intact. | Symptom | Check | | --- | --- | -| Nothing on the PicoCalc's screen at all | Is the SD card in the **Lyra's own slot**, not the PicoCalc's front slot? Is a "Lyra B" (SPI NAND) erased? Is it the **128 MB RAM, Pico-form-factor** variant — other RAM sizes have incompatible pinouts? | -| Wi-Fi never comes up | Is the dongle one of the tested chipsets (RTL8192CU / R8712U / RTL8188EU)? Remember the Lyra's USB port is **3.3 V** — a 5 V dongle won't work. Give it the full six minutes on a cold boot before assuming it's stuck. | -| `pip install` fails with `ENOSPC` | `/tmp` is a small RAM disk. `calculinux-setup.sh` already sets `TMPDIR=$HOME/tmp` for its own install; if you're running `pip` by hand, do the same. | -| `meshterm: command not found` after setup | Log out and back in — the PATH line is added to `~/.profile`, which only takes effect on a fresh login shell. | -| Tofu boxes instead of braille charts or node glyphs | The console font script hasn't run, or didn't persist. Re-run `sh calculinux-console-font-6x12.sh`, and check `/etc/vconsole.conf` has a `FONT=` line. | -| `meshterm` can't open `/dev/ttyS1` | Is the `meshterm` user in `dialout` (`groups`)? Group changes need a fresh login to take effect. | -| Port opens but the radio never answers | Re-flash with the **`_serial`** environment, not `_ble` or `_usb` — only it defines `SERIAL_RX`/`SERIAL_TX`. The XIAO's USB serial should be silent once it's running the radio firmware; that's expected, not a fault. | -| Still silent with wiring confirmed | Confirm the firmware actually carries the I²C remap (`PIN_WIRE_SCL=16`, `PIN_WIRE_SDA=17`) — a build without the patch's second hunk looks identical until you check this. | -| Flashed fine, XIAO never comes back | Wrong board, or the wrong SoftDevice — `INFO_UF2.TXT` must report a XIAO and **S140 v7**, not `6.1.1`. `flash.py` refuses to flash either mismatch without `--force`. | -| `flash.py` finds no UF2 drive | Expected on a CDC-only bootloader — use the `pio … -t upload --upload-port` command it prints instead. | -| Nothing on `/dev/ttyS1` after a reboot | `systemctl status uart1-radio-mux`, then `journalctl -b -u uart1-radio-mux` — the pin-mux poke has to run every boot, and a failed oneshot says why. | +| Nothing on the display of the PicoCalc | Is the SD card in the **Lyra's own slot**, not the front slot of the PicoCalc? Is a "Lyra B" (SPI NAND) erased? Is the board the **128 MB RAM, Pico-form-factor** variant? Other RAM sizes have pinouts that do not fit. | +| Wi-Fi never comes up | Is the dongle one of the tested chipsets (RTL8192CU / R8712U / RTL8188EU)? The USB port of the Lyra is **3.3 V**, thus a 5 V dongle does not work. On a cold boot, wait the full six minutes before you decide that it is stuck. | +| `pip install` fails with `ENOSPC` | `/tmp` is a small RAM disk. `calculinux-setup.sh` already sets `TMPDIR=$HOME/tmp` for its own install. If you run `pip` by hand, set it in the same way. | +| `meshterm: command not found` after setup | Log out and log in again. The PATH line is in `~/.profile`, which a new login shell reads. | +| Empty boxes instead of braille charts or node glyphs | The console font script did not run, or its result did not stay. Run `sh calculinux-console-font-6x12.sh` again. Check that `/etc/vconsole.conf` has a `FONT=` line. | +| `meshterm` cannot open `/dev/ttyS1` | Is the `meshterm` user in `dialout` (`groups`)? A group change takes effect only after a new login. | +| The port opens but the radio never answers | Flash again with the **`_serial`** environment, not `_ble` or `_usb`. Only `_serial` defines `SERIAL_RX`/`SERIAL_TX`. When the XIAO runs the radio firmware, its USB serial must be silent. This is expected and is not a fault. | +| Still silent, and the wiring is correct | Make sure that the firmware has the I²C remap (`PIN_WIRE_SCL=16`, `PIN_WIRE_SDA=17`). A build without the second hunk of the patch looks the same until you check this. | +| The flash was good, but the XIAO never comes back | The board or the SoftDevice is wrong. `INFO_UF2.TXT` must report a XIAO and **S140 v7**, not `6.1.1`. `flash.py` refuses to flash with either mismatch unless you use `--force`. | +| `flash.py` finds no UF2 drive | This is expected on a bootloader that has only CDC. Use the `pio … -t upload --upload-port` command that the tool prints. | +| Nothing on `/dev/ttyS1` after a reboot | Run `systemctl status uart1-radio-mux`, then `journalctl -b -u uart1-radio-mux`. The pin-mux poke must run at each boot. If the oneshot fails, the log tells you why. | --- ## What has been tested -This whole path — from a stock PicoCalc kit through a running radio — has been exercised on -one maintainer's bench: one Luckfox Lyra, one PicoCalc, one XIAO + Wio-SX1262 pair. It -works. One part is also checked in code: a test (`tests/test_theme16.py`) parses the font -script and pins it to MeshTerm's glyph inventory. The pinned MeshCore commit, the need for -the patch's two hunks on `dev`, and the nRF52840 UF2 family id and XIAO bootloader ids -against Adafruit's board files were checked by hand when this was written. +The maintainer tested this whole path, from a stock PicoCalc kit to a running radio, on one +bench. The bench had one Luckfox Lyra, one PicoCalc, and one XIAO + Wio-SX1262 pair. The path +works. A test in code checks one part: `tests/test_theme16.py` parses the font script and +compares it to the glyph inventory of MeshTerm. When the author wrote this guide, the author +checked these items by hand: -The rest is one bench's findings, and a second device may disagree: +- the pinned MeshCore commit +- the need for the two hunks of the patch on `dev` +- the nRF52840 UF2 family id +- the XIAO bootloader ids, against the board files of Adafruit -- **The RK3506 register map** in `uart1-mux.py` — the two register blocks, the UART1 signal - ids, matrix mode, and that `gpio0-0`/`gpio0-1` are header GP4 and GP5 — was probed live - on one board, not read from a reference manual. +The other facts are the findings of one bench. A second device can give other results: + +- **The RK3506 register map** in `uart1-mux.py`. This covers the two register blocks, the + UART1 signal ids, the matrix mode, and the fact that `gpio0-0`/`gpio0-1` are header GP4 and + GP5. The author probed it live on one board. It is not from a reference manual. - **That the Calculinux kernel permits those `/dev/mem` writes**, and that the mux oneshot - really runs at every boot. -- **The header pin numbers** 6, 7, 8, and 36 for GP4, GP5, GND, and 3V3. The Pico-side - numbering is right; the carrier's wiring is a bench observation. -- **The MX1.25 socket's pin order.** Taken from the wire colours of Luckfox's own cable, - which is why [Step 3](#step-3--attach-the-wi-fi-dongle) has you check pin 1 with a meter. -- **The 5 V path through the Lyra's USB-C.** The mechanism is read off the mainboard - schematic and two forum measurements on the Pico; nobody has measured it with a Lyra. - Treat the warning as the safe assumption it is. -- **The Calculinux specifics the scripts assert**: the stripped Python and the package - names that fix it, which overlays are writable, that `/tmp` is a small RAM tmpfs, the - dongle's cold-boot race with `iwd`, the opkg feed's occasional 404, that the stock - Terminus console font is installed, and that `kbd` brings `setvtrgb`. -- **That this board has no RTC.** Only the script says so; the clock-sync phase follows. -- **The XIAO bootloader that exposed no mass storage**, and the S140 v7 / `0x27000` - requirement — public references agree, but neither is a primary source. -- **Whether the firmware patch still applies at MeshCore `dev` HEAD.** Only the pinned + really runs at each boot. +- **The header pin numbers** 6, 7, 8, and 36 for GP4, GP5, GND, and 3V3. The numbering on the + Pico side is correct. The wiring of the carrier is an observation from the bench. +- **The pin order of the MX1.25 socket.** It comes from the wire colours of Luckfox's own cable. + For this reason, [Step 3](#step-3-attach-the-wi-fi-dongle) tells you to check + pin 1 with a meter. +- **The 5 V path through the USB-C of the Lyra.** The author read the mechanism from the + mainboard schematic and from two forum measurements on the Pico. Nobody measured it with a + Lyra. Use the warning as the safe assumption that it is. +- **The facts of Calculinux that the scripts assert.** These are the reduced Python and the + package names that repair it, which overlays are writable, that `/tmp` is a small RAM + tmpfs, the race of the dongle with `iwd` at cold boot, the occasional 404 of the opkg feed, + that the stock Terminus console font is installed, and that `kbd` brings `setvtrgb`. +- **That this board has no RTC.** Only the script says this. The clock-sync phase follows + from it. +- **The XIAO bootloader that showed no mass storage**, and the S140 v7 / `0x27000` + requirement. Public references agree, but none is a primary source. +- **Whether the firmware patch still applies at the HEAD of MeshCore `dev`.** Only the pinned commit is known to build. -- **MeshCore's serial framing** around the raw `APP_START` check in - [Step 16](#step-16--run-it): the command byte and reply code are from the protocol - documentation; the framing bytes are not. -- **The framebuffer console's 512-glyph cap.** Well known, taken as given, not re-measured. +- **The framing of MeshCore's serial protocol** around the raw `APP_START` check in + [Step 16](#step-16-run-it). The command byte and the reply code come from the protocol + documentation. The framing bytes do not. +- **The 512-glyph limit of the framebuffer console.** It is well known. The author accepted + it and did not measure it again. -If you build this and something disagrees with what's written here, that's worth -reporting — a second board is the only way to learn which parts of this were about the -hardware and which were about the one unit it was built on. +If you build this machine and a result is different from this guide, please report it. A +second board is the only way to learn which parts are about the hardware in general and which +parts are about the one unit that the author used. diff --git a/docs/devices/uconsole.md b/docs/devices/uconsole.md index 03d1816..03e258d 100644 --- a/docs/devices/uconsole.md +++ b/docs/devices/uconsole.md @@ -1,38 +1,39 @@ # MeshTerm on the uConsole A ClockworkPi uConsole with the hackergadgets AIO LoRa board has no companion -microcontroller and no firmware to flash — the LoRa chip sits straight on the host's SPI -bus. MeshTerm can drive it directly: it starts a mesh node as a child process the moment -you connect, talks to it over the standard companion protocol on a loopback port, and ends -it when you disconnect or quit. No separate service, no TCP port to remember — just -`meshterm --spi`. +microcontroller and no firmware to flash. The LoRa chip is directly on the SPI bus of the +host. MeshTerm can drive the chip directly. When you connect, MeshTerm starts a mesh node +as a child process. It talks to the node over the standard companion protocol on a loopback +port. When you disconnect or quit, MeshTerm ends the node. There is no separate service and +no TCP port to remember. You only run `meshterm --spi`. -If you'd rather the node stayed on the mesh while MeshTerm is closed, a small bridge can -run it as a background service instead, fronting the same chip as a network companion. -That's [further down](#always-on-the-bridge) — most people want the section above it. +If you want the node to stay on the mesh while MeshTerm is closed, you can use a small +bridge. The bridge runs the node as a background service. It presents the same chip as a +network companion. The bridge is [further down](#always-on-the-bridge). Most users must +use the direct connection that is described before it. -> ⚠ **Read this first.** Either route edits your uConsole's boot configuration, adds your -> user to hardware groups, and then keys a LoRa transmitter. It assumes you are comfortable -> at a Linux shell and know which frequency plan and power limits apply where you live — -> what the radio does on air is your responsibility, not MeshTerm's. There is a real risk -> of leaving the system, the radio, or the mesh around you in a worse state if a step goes -> wrong. You do this at your own risk. +> ⚠ **WARNING: Read this first.** Each route edits the boot configuration of your +> uConsole, adds your user to hardware groups, and then keys a LoRa transmitter. You must +> know a Linux shell well. You must also know which frequency plan and which power limits +> apply where you live. What the radio does on the air is your responsibility, not the +> responsibility of MeshTerm. If a step goes wrong, the system, the radio, or the mesh +> around you can be in a worse state. You do this at your own risk. > -> **Read the whole manual through once before you start.** Which install route you take -> depends on how you're running MeshTerm, and that's easier to get right knowing the later -> steps. +> **Read the whole manual one time before you start.** The install route that you take +> depends on how you run MeshTerm. You can choose the correct route more easily when you +> know the later steps. > -> This is a guide, not a prescription. Despite the author's best efforts it may contain -> errors, and parts of it will go out of date as packages, runtimes, and boards change. It -> is up to you to check every fact in it against your own hardware and the current sources -> before you act on it, and every decision along the way is yours. The author accepts no -> responsibility for any damage, loss, or injury that results from following this guide. +> This is a guide, not a prescription. The author tried to make it correct, but it can +> have errors. Parts of it will become out of date when packages, runtimes, and boards +> change. You must check each fact in it against your own hardware and the current sources +> before you act on it. Each decision is yours. The author accepts no responsibility for +> damage, loss, or injury that results from this guide. - [What you need](#what-you-need) -- [Step 1 — Prepare the system](#step-1--prepare-the-system) -- [Step 2 — Install the radio library](#step-2--install-the-radio-library) -- [Step 3 — Install MeshTerm](#step-3--install-meshterm) -- [Step 4 — Connect](#step-4--connect) +- [Step 1: Prepare the system](#step-1-prepare-the-system) +- [Step 2: Install the radio library](#step-2-install-the-radio-library) +- [Step 3: Install MeshTerm](#step-3-install-meshterm) +- [Step 4: Connect](#step-4-connect) - [Moving over from the bridge (0.9.0 and earlier)](#moving-over-from-the-bridge-090-and-earlier) - [Board wiring](#board-wiring) - [Which GPIO chip](#which-gpio-chip) @@ -48,111 +49,119 @@ That's [further down](#always-on-the-bridge) — most people want the section ab | Part | Notes | | --- | --- | -| ClockworkPi **uConsole** with a Raspberry Pi **CM4 or CM5** | Both cores work the same way; MeshTerm finds the GPIO chip behind the header on either (see [Which GPIO chip](#which-gpio-chip)). ClockworkPi's own A-04, A-06 and R-01 cores are not supported. | -| hackergadgets **AIO** expansion board | The board this guide is written for and the one we recommend: an SX1262 on SPI bus 1, plus GPS, an RTL-SDR and a USB hub you don't need here. MeshTerm's SPI defaults are its wiring (AIO v1; the v2 needs one extra setting — see [Board wiring](#board-wiring)). | -| An antenna for your region's LoRa band | Required. | +| ClockworkPi **uConsole** with a Raspberry Pi **CM4 or CM5** | Both cores work in the same way. MeshTerm finds the GPIO chip behind the header on each core (refer to [Which GPIO chip](#which-gpio-chip)). ClockworkPi's A-04, A-06 and R-01 cores are not supported. | +| hackergadgets **AIO** expansion board | This guide is written for this board, and we recommend it. It has an SX1262 on SPI bus 1. It also has a GPS, an RTL-SDR, and a USB hub that you do not need here. MeshTerm's SPI defaults are the wiring of this board (AIO v1). The v2 needs one more setting (refer to [Board wiring](#board-wiring)). | +| An antenna for the LoRa band of your region | Necessary. | -Installing the AIO board into the uConsole is out of scope here — follow -[hackergadgets' own setup guide](https://hackergadgets.com/pages/hackergadgets-uconsole-rtl-sdr-lora-gps-rtc-usb-hub-all-in-one-extension-board-setup-guide) -for that part. +This guide does not describe how to install the AIO board in the uConsole. For that part, +follow +[the setup guide of hackergadgets](https://hackergadgets.com/pages/hackergadgets-uconsole-rtl-sdr-lora-gps-rtc-usb-hub-all-in-one-extension-board-setup-guide). **Software:** -- Raspberry Pi OS / Debian **bookworm** or **trixie** on the uConsole, 64-bit. This is - Linux-only — driving a radio on the host's own SPI bus is not something Windows or macOS - can do. -- A `/dev/spidev*` node. This needs `dtoverlay=spi1-1cs` in `/boot/firmware/config.txt`. -- The GPIO character devices (`/dev/gpiochip*`), and membership of the `spi` and `gpio` - groups so you can read and write both without root. -- The radio library, **`openhop-core[hardware]` 1.1.3 or newer** — [Step - 2](#step-2--install-the-radio-library) gets you there. It doesn't have to be in - MeshTerm's own Python; MeshTerm looks in several places for it. -- MeshTerm itself, on the uConsole — [Step 3](#step-3--install-meshterm). +- Raspberry Pi OS / Debian **bookworm** or **trixie** on the uConsole, 64-bit. This is for + Linux only. Windows and macOS cannot drive a radio on the SPI bus of the host. +- A `/dev/spidev*` device file. This needs `dtoverlay=spi1-1cs` in + `/boot/firmware/config.txt`. +- The GPIO character device files (`/dev/gpiochip*`). You must also be a member of the + `spi` and `gpio` groups, so that you can read and write both without root. +- The radio library, **`openhop-core[hardware]` 1.1.3 or newer**. [Step + 2](#step-2-install-the-radio-library) tells you how to get it. It does not have to be + in MeshTerm's own Python. MeshTerm looks for it in several places. +- MeshTerm itself, on the uConsole ([Step 3](#step-3-install-meshterm)). -A node's own radio settings — frequency, bandwidth, spreading factor, coding rate, TX -power — are not part of this setup. They're the node's, saved by the node, and you change -them the same way you would on any companion: MeshTerm's Device config page. A brand-new -node starts on MeshCore's US/Canada preset (910.525 MHz, 62.5 kHz, SF 7, CR 4/5, 22 dBm) -until you change it. +The radio settings of a node are not part of this setup. These settings are the frequency, +the bandwidth, the spreading factor, the coding rate, and the TX power. The node owns them +and saves them. You change them in the same way as on any companion, on the Device config +page of MeshTerm. A new node starts with the US/Canada preset of MeshCore (910.525 MHz, +62.5 kHz, SF 7, CR 4/5, 22 dBm) until you change it. --- -## Step 1 — Prepare the system +## Step 1: Prepare the system -Enable the SPI overlay. Edit `/boot/firmware/config.txt` and add: +1. Enable the SPI overlay. Edit `/boot/firmware/config.txt` and add this line: ``` dtoverlay=spi1-1cs ``` -Add yourself to the groups that own the SPI and GPIO device nodes: +2. Add yourself to the groups that own the SPI and GPIO device files: ```bash sudo usermod -aG spi,gpio $USER ``` -Reboot so both the overlay and the group membership take effect: +3. Reboot. The overlay and the group membership take effect after the reboot: ```bash sudo reboot ``` -After it comes back, check the device nodes exist and that you can use them: +4. After the uConsole starts again, check that the device files exist and that you can use + them: ```bash ls -l /dev/spidev* /dev/gpiochip* groups ``` -You should see `/dev/spidev1.0` (or another `spidev*` node) and at least one `gpiochip` -listed, and `spi` and `gpio` in your `groups` output. Which `gpiochip` carries the radio's -pins depends on your core and your kernel; MeshTerm works that out itself (see [Which GPIO -chip](#which-gpio-chip)). +You must see `/dev/spidev1.0` (or another `spidev*` file) and at least one `gpiochip` in +the list. You must also see `spi` and `gpio` in the output of `groups`. The `gpiochip` that +has the pins of the radio depends on your core and your kernel. MeshTerm finds it itself +(refer to [Which GPIO chip](#which-gpio-chip)). --- -## Step 2 — Install the radio library +## Step 2: Install the radio library -MeshTerm needs `openhop-core[hardware]` 1.1.3 or newer importable by *some* Python on the -machine — not necessarily MeshTerm's own. It looks, in order: a `python` named in the -profile's `spi` table; MeshTerm's own interpreter; the bridge's venv at -`~/.local/share/meshterm-spi-bridge/venv/bin/python`; `/opt/venvs/meshcore-uconsole/bin/python`; -then `python3` on your `PATH`. Whichever route you take below, that's the interpreter it -lands in. +MeshTerm needs `openhop-core[hardware]` 1.1.3 or newer. Some Python on the machine must be +able to import it. It does not have to be MeshTerm's own Python. MeshTerm looks in +this order: -- **Installed MeshTerm with `pipx`?** Inject it into MeshTerm's own environment: +1. A `python` that is named in the `spi` table of the profile. +2. MeshTerm's own interpreter. +3. The venv of the bridge, at `~/.local/share/meshterm-spi-bridge/venv/bin/python`. +4. `/opt/venvs/meshcore-uconsole/bin/python`. +5. `python3` on your `PATH`. + +Whichever route you take below, the library goes into one of these interpreters. Select +the route that agrees with how you installed MeshTerm. + +- **Did you install MeshTerm with `pipx`?** Inject the library into MeshTerm's own environment: ```bash pipx inject mesh-term 'openhop-core[hardware]' ``` -- **Installed with `pip`?** Pull in the `spi` extra when you install MeshTerm (see - [Step 3](#step-3--install-meshterm)): +- **Did you install MeshTerm with `pip`?** Add the `spi` extra when you install MeshTerm + (refer to [Step 3](#step-3-install-meshterm)): ```bash pip install 'mesh-term[spi]' ``` -- **Running the one-file Linux ARM64 build?** It's a single frozen binary and can't carry - the library, so give it a venv of its own — the same one the bridge guide uses, so it's - shared if you ever run both: +- **Do you run the one-file Linux ARM64 build?** It is a single frozen binary, and it + cannot hold the library. Thus give it a venv of its own. This is the same venv that the + bridge guide uses, so you can share it if you run both: ```bash python3 -m venv ~/.local/share/meshterm-spi-bridge/venv ~/.local/share/meshterm-spi-bridge/venv/bin/pip install "openhop-core[hardware]" ``` - MeshTerm finds it there automatically; no configuration needed. + MeshTerm finds the venv there automatically. It needs no configuration. -The older `pymc_core` runtime (the library's name before 2026) is not supported for a -direct connection — only the standalone bridge still carries the compatibility shims it -needs. +A direct connection does not support the older `pymc_core` runtime (the name of the library +before 2026). Only the standalone bridge still has the compatibility shims that this +runtime needs. --- -## Step 3 — Install MeshTerm +## Step 3: Install MeshTerm -Same as any other machine — the simplest way is the Linux ARM64 one-file release: +The installation is the same as on any other machine. The simplest way is the Linux ARM64 +one-file release: ```bash curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-linux-arm64 @@ -160,7 +169,7 @@ chmod +x meshterm sudo mv meshterm /usr/local/bin/meshterm ``` -Or install it from the repository with `pipx`: +You can also install it from the repository with `pipx`: ```bash sudo apt install pipx @@ -169,7 +178,7 @@ pipx ensurepath pipx inject mesh-term 'openhop-core[hardware]' ``` -Or with `pip`, pulling in the radio library as its own extra in one line: +Or use `pip`, which adds the radio library as its own extra in one line: ```bash pip install 'mesh-term[spi]' @@ -177,36 +186,43 @@ pip install 'mesh-term[spi]' --- -## Step 4 — Connect +## Step 4: Connect ```bash meshterm --spi ``` -`--spi` finds the AIO's radio at `/dev/spidev1.0` and starts a node on it. The startup -device picker also lists it — a row reading **uConsole AIO (/dev/spidev1.0)** with a 📍 -icon — whenever that device node exists, so `meshterm` with no flags gets you there too. -MeshTerm only calls it that once four things all say it's on a uConsole: the uConsole's -screen, power chip, and backlight as ClockworkPi's uConsole overlay declares them, and the -uConsole's own keyboard on USB. If any one is missing, the row reads **SPI radio** instead. +`--spi` finds the radio of the AIO at `/dev/spidev1.0` and starts a node on it. The startup +device picker also lists the radio, when that device file exists. The row reads **uConsole +AIO (/dev/spidev1.0)** and has a 📍 icon. Thus `meshterm` with no flags also gets you there. -You're connected when **Device info** or the dashboard shows a node name instead of a -connection error. MeshTerm reports the device model as **uConsole AIO** (or **MeshTerm SPI -node** where it isn't sure it's on a uConsole), and the header's connection label reads -**SPI**. +MeshTerm uses that name only when four things show that it is on a uConsole: -**What happens underneath.** MeshTerm starts the node when it connects and ends it when -you disconnect or quit — including a crash: the node exits with MeshTerm, and the kernel -frees the GPIO lines and SPI handle the moment its process does. The trade-off is the one -this implies: **the node is off the mesh while MeshTerm is closed.** If you want it on all -the time, that's what [the bridge](#always-on-the-bridge) is for. +- the screen of the uConsole, as the overlay of ClockworkPi for the uConsole declares it +- the power chip, as the same overlay declares it +- the backlight, as the same overlay declares it +- the uConsole's own keyboard, on USB -**Where its state lives.** Each radio keeps its identity, preferences, channels, and -contacts under `~/.meshterm/radio/spidev1.0/` — `identity.key`, `prefs.json`, -`channels.json`, `contacts.json` — plus a running log, `node.log` (the previous run's is -kept as `node.log.1`). All of it survives a restart. +If one of these is missing, the row reads **SPI radio**. -**A profile, if you want a name for it:** +You are connected when **Device info** or the dashboard shows a node name and not a +connection error. MeshTerm shows the device model as **uConsole AIO**. If it is not sure +that it is on a uConsole, it shows **MeshTerm SPI node**. The connection label in the +header reads **SPI**. + +**What happens underneath.** MeshTerm starts the node when it connects. It ends the node +when you disconnect or quit. This includes a crash: the node exits with MeshTerm, and the +kernel frees the GPIO lines and the SPI handle when the process ends. The result is that +**the node is off the mesh while MeshTerm is closed.** If you want the node on the mesh +all the time, use [the bridge](#always-on-the-bridge). + +**Where the state is.** Each radio keeps its identity, preferences, channels, and contacts +in `~/.meshterm/radio/spidev1.0/`. The files are `identity.key`, `prefs.json`, +`channels.json`, and `contacts.json`. The folder also has a running log, `node.log`. +MeshTerm keeps the log of the previous run as `node.log.1`. All of these files stay after +a restart. + +**A profile, if you want a name for the connection:** ```toml [profiles.aio] @@ -217,58 +233,62 @@ transport = "spi" meshterm -p aio ``` -The AIO's wiring is the default, so this profile needs nothing beyond `transport = "spi"`. -[Board wiring](#board-wiring) is for when yours differs. +The AIO's wiring is the default. Thus this profile needs only `transport = "spi"`. +Read [Board wiring](#board-wiring) if the wiring of your board is different. --- ## Moving over from the bridge (0.9.0 and earlier) -> **Only if you set up the bridge with MeshTerm 0.9.0 or earlier.** Before 0.10.0 the bridge -> was the only way to use the uConsole's radio, so an older setup is almost certainly -> running one. On a fresh install of 0.10.0 or newer there is nothing to move over — skip -> ahead to [Board wiring](#board-wiring). +> **Read this section only if you set up the bridge with MeshTerm 0.9.0 or earlier.** Before +> 0.10.0, the bridge was the only way to use the uConsole's radio. Thus an older setup +> almost always runs a bridge. On a new install of 0.10.0 or newer, there is nothing to move +> over. Go to [Board wiring](#board-wiring). -Upgraded from 0.9.0 or earlier with the bridge still installed? The first time you connect -with `--spi` (or a profile, or the picker row), MeshTerm carries the bridge's node over — -**copying**, never moving, so the bridge still works afterwards if you go back to it: +Did you upgrade from 0.9.0 or earlier, and is the bridge still installed? The first time +that you connect with `--spi` (or with a profile, or with the picker row), MeshTerm carries +the bridge's node over. MeshTerm **copies** the data and never moves it. Thus the +bridge still works if you go back to it. MeshTerm copies these items: -- the identity key — from the `meshcore-console` GUI's copy first - (`~/.local/share/meshcore-uconsole/identity.key`), the way the bridge itself looks for - it, else the bridge's own (`~/.local/share/meshterm-spi-bridge/identity.key`); -- the bridge's `contacts.json`; -- the name `uConsole`; -- the bridge's radio settings — frequency, bandwidth, spreading factor, coding rate, TX - power — read out of the bridge service's `MESHCORE_*` environment. +- The identity key. MeshTerm looks first for the copy of the `meshcore-console` GUI + (`~/.local/share/meshcore-uconsole/identity.key`), as the bridge itself does. If it is + not there, MeshTerm uses the bridge's own key + (`~/.local/share/meshterm-spi-bridge/identity.key`). +- The bridge's `contacts.json`. +- The name `uConsole`. +- The bridge's radio settings: the frequency, the bandwidth, the spreading factor, the + coding rate, and the TX power. MeshTerm reads them from the `MESHCORE_*` environment of + the bridge service. -This only happens once, while `~/.meshterm/radio/spidev1.0/` has no `identity.key` yet — so -your node keeps its public key, and with it its place in everyone else's contact list. +This happens only one time, while `~/.meshterm/radio/spidev1.0/` has no `identity.key`. +Thus your node keeps its public key, and it keeps its place in the contact list of each +other node. -**Wiring is not carried over.** If your board needed `MESHCORE_EN_PINS` or another wiring -variable for the bridge, put the equivalent in the profile's `spi` table (see -[Board wiring](#board-wiring)) — the direct connection reads wiring from there, never from -the bridge's environment. +**MeshTerm does not copy the wiring.** Your board can need `MESHCORE_EN_PINS` or another +wiring variable for the bridge. If it does, put the equivalent in the `spi` table of the +profile (refer to [Board wiring](#board-wiring)). The direct connection reads the wiring +from there, and never from the bridge's environment. -**Stop the bridge before switching over**, since only one program may hold the radio at a -time: +Only one program can hold the radio at a time. Thus stop the bridge before you switch over: ```bash systemctl --user stop meshterm-spi-bridge systemctl --user disable meshterm-spi-bridge # keep it stopped across reboots ``` -If MeshTerm sees the bridge service running, it refuses to start the node and tells you the -same two commands. +If MeshTerm sees that the bridge service runs, it refuses to start the node. It then shows +you the same two commands. --- ## Board wiring -The defaults match the hackergadgets uConsole AIO **v1**, which needs no `spi` table at -all. State only what differs, under `[profiles..spi]`. For a board that isn't an -AIO — a LoRa HAT on a Raspberry Pi, say — [Adding a radio on the SPI -bus](../guide/configuration.md#adding-a-radio-on-the-spi-bus) walks through a whole profile, including -how to read the pins off the board's `meshtasticd` configuration: +The defaults agree with the hackergadgets uConsole AIO **v1**. This board needs no `spi` +table. State only what is different, under `[profiles..spi]`. Your board can be +something other than an AIO, for example a LoRa HAT on a Raspberry Pi. In that case, +[Adding a radio on the SPI +bus](../guide/configuration.md#adding-a-radio-on-the-spi-bus) shows a whole profile. It +also shows how to read the pins from the `meshtasticd` configuration of the board: ```toml [profiles.aio2] @@ -280,47 +300,48 @@ en_pins = [27] # the AIO v2 needs this | Setting | Default | What it is | | --- | --- | --- | -| `bus_id`, `cs_id` | `1`, `0` | Which SPI bus and chip-select the radio is on (`/dev/spidev.`). | -| `cs_pin` | `-1` | A GPIO driven as chip select by hand, instead of the bus's own. | -| `gpio_chip` | `-1` | Which `/dev/gpiochip` the pins below are on. `-1` finds the chip behind the 40-pin header by its label — see [Which GPIO chip](#which-gpio-chip). | +| `bus_id`, `cs_id` | `1`, `0` | The SPI bus and the chip select of the radio (`/dev/spidev.`). | +| `cs_pin` | `-1` | A GPIO that MeshTerm drives by hand as the chip select, instead of the chip select of the bus. | +| `gpio_chip` | `-1` | The `/dev/gpiochip` that has the pins below. `-1` finds the chip behind the 40-pin header by its label (refer to [Which GPIO chip](#which-gpio-chip)). | | `use_gpiod_backend` | `false` | Drive the pins through `gpiod` instead of `python-periphery`. | -| `reset_pin`, `busy_pin`, `irq_pin` | `25`, `24`, `26` | The chip's reset, busy and interrupt (DIO1) lines. | -| `txen_pin`, `rxen_pin` | `-1`, `-1` | An external RF switch's transmit/receive-enable lines, if the board has one. | -| `en_pins` | `[]` | Power-enable lines to raise before the chip is touched. The **AIO v2 needs `[27]`.** | -| `leds` | `[]` | Switches the board's device tree exposes as LEDs (under `/sys/class/leds/`), each written as `"name=brightness"`. They are set before the chip is touched and put back as they were when MeshTerm lets go. The Cardputer Zero powers its expansion header this way; its Cap needs no profile, but this is what one would say: `["ext_5v_out=1", "ext_usb_gpio_fun=0"]`. | -| `pi4io_bus`, `pi4io_address`, `pi4io_high` | `-1`, `0x43`, `[]` | A PI4IOE5V6408 I/O expander that switches the radio's antenna path: its I2C bus (`-1` for none), its address, and the pins to drive high. | -| `use_dio2_rf`, `use_dio3_tcxo` | `true`, `true` | Whether DIO2 drives the RF switch and DIO3 a TCXO (both on for the AIO). | -| `is_waveshare` | `false` | The Waveshare HAT's wiring quirks, which the radio library knows about. | -| `python` | `""` (empty) | An interpreter to run the node under, when the one MeshTerm would find on its own isn't the one you want. Empty lets MeshTerm look. | -| `gps_port`, `gps_baud` | `""`, `9600` | A GPS receiver on the same board: its serial port (empty for none) and its speed. The node runs it the way MeshCore firmware runs a board's GPS, switched on from Device config's **GPS** row, and the device screen stops listing the port as a companion. The Cardputer Zero's Cap needs no profile, but this is what one would say: `"/dev/serial0"`, `115200`. | +| `reset_pin`, `busy_pin`, `irq_pin` | `25`, `24`, `26` | The reset line, the busy line, and the interrupt line (DIO1) of the chip. | +| `txen_pin`, `rxen_pin` | `-1`, `-1` | The transmit-enable and receive-enable lines of an external RF switch, if the board has one. | +| `en_pins` | `[]` | The power-enable lines that go high before MeshTerm touches the chip. The **AIO v2 needs `[27]`.** | +| `leds` | `[]` | Switches that the device tree of the board shows as LEDs (under `/sys/class/leds/`). Write each one as `"name=brightness"`. MeshTerm sets them before it touches the chip. It puts them back as they were when it releases the radio. The Cardputer Zero powers its expansion header in this way. Its Cap needs no profile, but this is what you would write: `["ext_5v_out=1", "ext_usb_gpio_fun=0"]`. | +| `pi4io_bus`, `pi4io_address`, `pi4io_high` | `-1`, `0x43`, `[]` | A PI4IOE5V6408 I/O expander that switches the antenna path of the radio. These are its I2C bus (`-1` for none), its address, and the pins that go high. | +| `use_dio2_rf`, `use_dio3_tcxo` | `true`, `true` | If DIO2 drives the RF switch, and if DIO3 drives a TCXO (both are on for the AIO). | +| `is_waveshare` | `false` | The wiring quirks of the Waveshare HAT, which the radio library knows. | +| `python` | `""` (empty) | An interpreter to run the node under. Use it when the interpreter that MeshTerm finds is not the one that you want. If it is empty, MeshTerm looks for one. | +| `gps_port`, `gps_baud` | `""`, `9600` | A GPS receiver on the same board: its serial port (empty for none) and its speed. The node runs the GPS as MeshCore firmware runs the GPS of a board. You switch it on with the **GPS** row on Device config. The device screen then stops listing the port as a companion. The Cap of the Cardputer Zero needs no profile, but this is what you would write: `"/dev/serial0"`, `115200`. | -An unknown key, or one of the wrong type, stops MeshTerm at startup with the profile -named — a misspelt pin is never silently ignored. +If a key is unknown or has the wrong type, MeshTerm stops at startup and names the profile. +MeshTerm never ignores a misspelt pin without a message. -Radio settings — frequency, bandwidth, spreading factor, coding rate, TX power — are -**not** in this table. They're the node's own, on Device config, as noted in -[What you need](#what-you-need). +The radio settings are **not** in this table. These are the frequency, the bandwidth, the +spreading factor, the coding rate, and the TX power. The node owns them. You change them on +Device config, as [What you need](#what-you-need) says. ### Which GPIO chip -The AIO's reset, busy and interrupt lines are GPIO 25, 24 and 26 on either core — the pin -numbers don't change. What changes is *which* `/dev/gpiochip` Linux puts them on: +The reset line, the busy line, and the interrupt line of the AIO are GPIO 25, 24, and 26 on +each core. The pin numbers do not change. What changes is the `/dev/gpiochip` that Linux +puts them on: -| Core | The header's GPIO controller | Its chip number | +| Core | The GPIO controller of the header | Its chip number | | --- | --- | --- | | CM4 | `pinctrl-bcm2711` | `0` | | CM5, early kernels | `pinctrl-rp1` | `4` | -| CM5, kernels since Raspberry Pi aliased the RP1 to chip 0 ([raspberrypi/linux#6144](https://github.com/raspberrypi/linux/pull/6144)) | `pinctrl-rp1` | `0` | -| CM5, some later kernels | `pinctrl-rp1` | another number again, as the order the drivers load in moved it | +| CM5, kernels since Raspberry Pi made the RP1 an alias of chip 0 ([raspberrypi/linux#6144](https://github.com/raspberrypi/linux/pull/6144)) | `pinctrl-rp1` | `0` | +| CM5, some later kernels | `pinctrl-rp1` | another number, because the load order of the drivers moved it | -So a chip number is not a fact about the board. **MeshTerm looks the chip up by its label -instead**: with `gpio_chip` left at its default of `-1`, it asks each `/dev/gpiochip` for -its label and uses the one called `pinctrl-rp1`, `pinctrl-bcm2711` or `pinctrl-bcm2835`, so -the same profile works on a CM4, a CM5, and across kernel updates. Where no chip carries one -of those labels, it falls back to chip `0`. +Thus a chip number is not a fixed fact about the board. **MeshTerm looks for the chip by its +label.** If `gpio_chip` has its default of `-1`, MeshTerm asks each `/dev/gpiochip` for +its label. It uses the chip that is called `pinctrl-rp1`, `pinctrl-bcm2711`, or +`pinctrl-bcm2835`. Thus the same profile works on a CM4, on a CM5, and after kernel +updates. If no chip has one of these labels, MeshTerm uses chip `0`. -To see what your system has, `gpiodetect` (from the `gpiod` package) lists every chip with -its label: +To see what chips your system has, run `gpiodetect` (from the `gpiod` package). It lists +each chip with its label: ``` gpiochip0 [pinctrl-rp1] (54 lines) @@ -328,10 +349,10 @@ gpiochip10 [gpio-brcmstb@107d508500] (32 lines) … ``` -Set `gpio_chip` to a number only to override the lookup — on a board whose header -controller has some other label, say. The [bridge](#always-on-the-bridge) does not look -chips up by label: on a CM5 it needs `MESHCORE_GPIO_CHIP` set to whatever `gpiodetect` shows -for `pinctrl-rp1`, if that isn't `0`. +Set `gpio_chip` to a number only to replace the lookup. For example, do this on a board +whose header controller has some other label. The [bridge](#always-on-the-bridge) does not +look for chips by label. On a CM5, set `MESHCORE_GPIO_CHIP` to the number that `gpiodetect` +shows for `pinctrl-rp1`, if that number is not `0`. --- @@ -339,125 +360,127 @@ for `pinctrl-rp1`, if that isn't `0`. | Symptom | Fix | | --- | --- | -| The radio library isn't found | Follow [Step 2](#step-2--install-the-radio-library) for however you installed MeshTerm, or set `python` in the profile's `spi` table to an interpreter that has `openhop-core[hardware]`. | +| MeshTerm does not find the radio library | Follow [Step 2](#step-2-install-the-radio-library) for the way that you installed MeshTerm. Or set `python` in the `spi` table of the profile to an interpreter that has `openhop-core[hardware]`. | | No `/dev/spidev1.0` | Add `dtoverlay=spi1-1cs` to `/boot/firmware/config.txt` and reboot. | -| Permission denied opening the SPI or GPIO device | `sudo usermod -aG spi,gpio $USER`, then log out and back in. | -| "the meshterm-spi-bridge service has the radio" | Stop it: `systemctl --user stop meshterm-spi-bridge` (and `disable` it to keep it stopped). | +| Permission denied when MeshTerm opens the SPI or GPIO device | Run `sudo usermod -aG spi,gpio $USER`, then log out and log in again. | +| "the meshterm-spi-bridge service has the radio" | Stop it with `systemctl --user stop meshterm-spi-bridge`. Also `disable` it to keep it stopped. | | "meshcore-console has the radio open" | Close the GUI and connect again. | -| Some other program has the radio | The message names the chip: `sudo lsof /dev/gpiochip` on it shows which program; only one may hold the radio at a time. | -| The node starts but never answers | Check its log: `~/.meshterm/radio/spidev1.0/node.log`. | -| An AIO v2 radio never comes up | It needs `en_pins = [27]` in the profile's `spi` table — see [Board wiring](#board-wiring). | +| Some other program has the radio | The message names the chip. Run `sudo lsof /dev/gpiochip` on it to see which program has it. Only one program can hold the radio at a time. | +| The node starts but never answers | Examine its log: `~/.meshterm/radio/spidev1.0/node.log`. | +| An AIO v2 radio never starts | It needs `en_pins = [27]` in the `spi` table of the profile (refer to [Board wiring](#board-wiring)). | --- ## Always on: the bridge -Most people should use the direct connection above. Use the bridge instead if you want the -node reachable on the mesh **while MeshTerm is closed** — it runs as a `systemd --user` -service, on its own, independent of whether MeshTerm is even installed. The trade-off runs -the other way from the direct connection: once the bridge is running, it holds the radio's -pins for good, so no other LoRa program — including a direct `meshterm --spi` — can use -them until you stop it. +Most users must use the direct connection that is described above. Use the bridge if you +want the node to be on the mesh **while MeshTerm is closed**. The bridge runs as a +`systemd --user` service, on its own. It does not need MeshTerm to be installed. -The bridge puts the standard MeshCore companion protocol in front of the same SPI-attached -chip, on a local TCP port. MeshTerm then connects to that port like it would any network -companion. +The bridge has a disadvantage that the direct connection does not have. When the bridge +runs, it holds the pins of the radio all the time. No other LoRa program can use them until +you stop the bridge. This includes a direct `meshterm --spi` connection. -- [Bridge step 1 — Install the radio runtime](#bridge-step-1--install-the-radio-runtime) -- [Bridge step 2 — Get the bridge](#bridge-step-2--get-the-bridge) -- [Bridge step 3 — Run the preflight](#bridge-step-3--run-the-preflight) -- [Bridge step 4 — Run it once in the foreground](#bridge-step-4--run-it-once-in-the-foreground) -- [Bridge step 5 — Install it as a service](#bridge-step-5--install-it-as-a-service) -- [Bridge step 6 — Connect MeshTerm to it](#bridge-step-6--connect-meshterm-to-it) +The bridge puts the standard MeshCore companion protocol in front of the same chip on the +SPI bus, on a local TCP port. MeshTerm then connects to that port as it connects to any +network companion. + +- [Bridge step 1: Install the radio runtime](#bridge-step-1-install-the-radio-runtime) +- [Bridge step 2: Get the bridge](#bridge-step-2-get-the-bridge) +- [Bridge step 3: Run the preflight](#bridge-step-3-run-the-preflight) +- [Bridge step 4: Run it once in the foreground](#bridge-step-4-run-it-once-in-the-foreground) +- [Bridge step 5: Install it as a service](#bridge-step-5-install-it-as-a-service) +- [Bridge step 6: Connect MeshTerm to it](#bridge-step-6-connect-meshterm-to-it) - [Updating and uninstalling the bridge](#updating-and-uninstalling-the-bridge) - [Bridge hardware knobs](#bridge-hardware-knobs) - [Bridge troubleshooting](#bridge-troubleshooting) -The bridge's own wiring and radio defaults are the same AIO v1 numbers as the direct -connection's — see the table in [Board wiring](#board-wiring) — just set through -`MESHCORE_*` environment variables instead of a profile's `spi` table. +The wiring and radio defaults of the bridge are the same AIO v1 numbers as the defaults of +the direct connection (refer to the table in [Board wiring](#board-wiring)). But you set +them with `MESHCORE_*` environment variables, not with the `spi` table of a profile. -The radio library that actually drives the chip was renamed in 2026: `pymc_core` became -`openhop_core`. PyPI's `pymc-core` stopped at 1.0.12; `openhop-core` carries on from 1.1.x. -The bridge supports both and prefers the newer one when it finds both installed. +In 2026, the project renamed the radio library that drives the chip: `pymc_core` became +`openhop_core`. The `pymc-core` package on PyPI stopped at 1.0.12. `openhop-core` continues +from 1.1.x. The bridge supports both libraries. When both are installed, it prefers the +newer one. -### Bridge step 1 — Install the radio runtime +### Bridge step 1: Install the radio runtime -Which route you take depends on your Debian release: +The route that you take depends on your Debian release: - **trixie**, with the `meshcore-uconsole` package (by cwill747) installed: you already - have a working runtime (Python 3.13-based). Skip to - [step 2](#bridge-step-2--get-the-bridge). + have a working runtime (based on Python 3.13). Go to + [step 2](#bridge-step-2-get-the-bridge). - **bookworm**: the trixie `.deb` of `meshcore-uconsole` 1.12.0 is built on Python 3.13 - and needs glibc 2.38, so it will not install on bookworm, and the APT repository offers - nothing for bookworm. Upstream's GitHub Releases also carry a separate bookworm `.deb` - built on Python 3.11; it has not been tested with the bridge. The documented route is to - give the bridge a runtime of its own: + and needs glibc 2.38. Thus it does not install on bookworm, and the APT repository has + nothing for bookworm. The GitHub Releases of the upstream project also have a separate + bookworm `.deb`, built on Python 3.11. It was not tested with the bridge. The documented + route is to give the bridge a runtime of its own: ```bash python3 -m venv ~/.local/share/meshterm-spi-bridge/venv ~/.local/share/meshterm-spi-bridge/venv/bin/pip install "openhop-core[hardware]" ``` -This is also the route to take if you'd rather not install the `meshcore-uconsole` package -at all. +Use this route also if you do not want to install the `meshcore-uconsole` package. -You'll confirm which runtime the bridge actually found in -[step 3](#bridge-step-3--run-the-preflight) — its first check line names the interpreter -and the runtime. +In [step 3](#bridge-step-3-run-the-preflight), you can confirm which runtime the bridge +found. The first check line names the interpreter and the runtime. -### Bridge step 2 — Get the bridge +### Bridge step 2: Get the bridge -`scripts/uconsole/meshterm-spi-bridge` is a single, self-contained Python file — no install step of -its own. Either clone the MeshTerm repo: +`scripts/uconsole/meshterm-spi-bridge` is one self-contained Python file. It has no install +step of its own. Get it in one of two ways. + +Clone the MeshTerm repository: ```bash git clone https://github.com/jpmartineau/MeshTerm cd MeshTerm/scripts/uconsole ``` -or download just the one file: +Or download only the one file: ```bash curl -L -o meshterm-spi-bridge \ https://raw.githubusercontent.com/jpmartineau/MeshTerm/main/scripts/uconsole/meshterm-spi-bridge ``` -Make it executable: +Then make the file executable: ```bash chmod +x meshterm-spi-bridge ``` -### Bridge step 3 — Run the preflight +### Bridge step 3: Run the preflight ```bash ./meshterm-spi-bridge --check ``` -This prints a `Prerequisite check:` block with one line per check. What each line means and -what to do if it fails: +This command prints a `Prerequisite check:` block with one line for each check. The table +tells you what each line means and what to do if the check fails: | Check | Meaning if it fails | Fix | | --- | --- | --- | -| `node runtime` | Neither `openhop_core` nor `pymc_core` is importable by any interpreter the bridge tried. | Build the venv from [step 1](#bridge-step-1--install-the-radio-runtime), or set `MESHTERM_PYMC_PYTHON` to a python that has one. | -| `SPI device` | No `/dev/spidev*` node exists, or it exists but you can't read/write it. | No node at all: add `dtoverlay=spi1-1cs` to `/boot/firmware/config.txt` and reboot. Node present but no permission: `sudo usermod -aG spi $USER`, then log out and back in. | -| `GPIO chip` | `/dev/gpiochip0` (or the chip `MESHCORE_GPIO_CHIP` names) is missing or not read/write for you. | `sudo usermod -aG gpio $USER`, then log out and back in. On a CM5, check [which chip](#which-gpio-chip) the header is on. | -| `bridge service` / `radio in use` / `port 5000 busy` / `radio is free` | Tells you whether something already holds the radio: the bridge's own boot service, a running `meshcore-console`, or something else already listening on the TCP port. | Only one program may use the radio at a time — see [step 6](#bridge-step-6--connect-meshterm-to-it). If `meshcore-console` is running, close it before running the bridge. | +| `node runtime` | None of the interpreters that the bridge tried can import `openhop_core` or `pymc_core`. | Build the venv from [step 1](#bridge-step-1-install-the-radio-runtime). Or set `MESHTERM_PYMC_PYTHON` to a python that has one of them. | +| `SPI device` | There is no `/dev/spidev*` file, or you cannot read and write it. | If there is no file, add `dtoverlay=spi1-1cs` to `/boot/firmware/config.txt` and reboot. If the file exists but you have no permission, run `sudo usermod -aG spi $USER`, then log out and log in again. | +| `GPIO chip` | `/dev/gpiochip0` (or the chip that `MESHCORE_GPIO_CHIP` names) is missing, or you cannot read and write it. | Run `sudo usermod -aG gpio $USER`, then log out and log in again. On a CM5, check [which chip](#which-gpio-chip) has the header. | +| `bridge service` / `radio in use` / `port 5000 busy` / `radio is free` | These lines tell you if a program already holds the radio. It can be the boot service of the bridge, a running `meshcore-console`, or another program that listens on the TCP port. | Only one program can use the radio at a time (refer to [step 6](#bridge-step-6-connect-meshterm-to-it)). If `meshcore-console` runs, close it before you run the bridge. | -If the first three checks all pass, you're ready to run it. +If the first three checks pass, you are ready to run the bridge. -### Bridge step 4 — Run it once in the foreground +### Bridge step 4: Run it once in the foreground ```bash ./meshterm-spi-bridge --run ``` -This runs the preflight again and prints it. Before it starts the bridge, it asks whether -to add a `bridge` profile to your MeshTerm config +This command runs the preflight again and prints it. Before it starts the bridge, it asks +if it must add a `bridge` profile to your MeshTerm config (`` Add a 'bridge' profile to /home/you/.meshterm/config.toml so you can just run `meshterm`? [y/N] ``). -Either answer is fine; step 5 asks again if you say no. Then it starts the bridge in this -terminal. A healthy startup logs something like: +You can give either answer. If you answer no, step 5 asks again. Then the command starts +the bridge in this terminal. A healthy startup logs lines like these: ``` node runtime openhop_core 1.1.3 (/home/you/.local/share/meshterm-spi-bridge/venv/bin/python) @@ -468,44 +491,45 @@ node identity ab12cd34ef567890… (hash 0xab, name 'uConsole') READY — connect with: meshterm --tcp 127.0.0.1:5000 ``` -The `radio config` line lists every setting as `key=value`, so the one above is -shortened; yours is longer. +The `radio config` line lists each setting as `key=value`. The line above is shortened. +Your line is longer. -You can test the bridge this way: leave it running, and in another terminal on the same -machine run: +To test the bridge, leave it running. In another terminal on the same machine, run: ```bash meshterm --tcp 127.0.0.1:5000 ``` -Back in the bridge's terminal, press **Ctrl+C** to stop it. +Then go to the terminal of the bridge and press **Ctrl+C** to stop it. -### Bridge step 5 — Install it as a service +### Bridge step 5: Install it as a service -Run the bridge with no flags to get the interactive menu: +1. Run the bridge with no flags to get the interactive menu: ```bash ./meshterm-spi-bridge ``` -Choose **2) Install as a service**. This: +2. Choose **2) Install as a service**. -- copies the script to `~/.local/bin/meshterm-spi-bridge`, -- writes a `systemd --user` unit at - `~/.config/systemd/user/meshterm-spi-bridge.service` that restarts on failure and starts - with your session, -- enables and starts it now, -- tries to enable lingering for your user, so the service comes up at boot even before you - log in. If it can't, it prints the command to run yourself: +This choice does these things: + +- It copies the script to `~/.local/bin/meshterm-spi-bridge`. +- It writes a `systemd --user` unit at + `~/.config/systemd/user/meshterm-spi-bridge.service`. The unit restarts on a failure and + starts with your session. +- It enables the unit and starts it now. +- It tries to enable lingering for your user. Then the service starts at boot, before you + log in. If lingering fails, the script prints a command for you to run: ```bash sudo loginctl enable-linger $USER ``` -It also offers to add a `[profiles.bridge]` block to your MeshTerm `config.toml` — say yes, -it's the profile you'll use next. +The script also offers to add a `[profiles.bridge]` block to your MeshTerm `config.toml`. +Answer yes. You use this profile next. -Check on it any time: +To check the service at any time, run: ```bash systemctl --user status meshterm-spi-bridge.service @@ -514,14 +538,14 @@ journalctl --user -u meshterm-spi-bridge.service -f Menu option **4) Show status** prints the same information from inside the script. -### Bridge step 6 — Connect MeshTerm to it +### Bridge step 6: Connect MeshTerm to it -Install MeshTerm on the uConsole the same way as [Step 3](#step-3--install-meshterm) -above — the bridge doesn't need the `spi` extra or `openhop-core[hardware]` in MeshTerm's -own Python, since the bridge is the one running the node. +Install MeshTerm on the uConsole in the same way as in [Step 3](#step-3-install-meshterm) +above. The bridge runs the node. Thus MeshTerm's own Python does not need the `spi` extra or +`openhop-core[hardware]`. -If you accepted the profile in step 4 or 5, `~/.meshterm/config.toml` now has this block -(the bridge always writes to that path, even if you set `MESHTERM_HOME`): +If you accepted the profile in step 4 or step 5, `~/.meshterm/config.toml` now has this +block. The bridge always writes to that path, also if you set `MESHTERM_HOME`: ```toml [profiles.bridge] @@ -536,128 +560,134 @@ Connect with: meshterm -p bridge ``` -or, without a profile: +Or connect without a profile: ```bash meshterm --tcp 127.0.0.1:5000 ``` -You're connected when **Device info** or the dashboard shows a node name (`uConsole` by -default) instead of a connection error. MeshTerm reports the device model as -`pyMC-spi-bridge` — that's how you tell a bridged node from a real companion or a direct -SPI connection. +You are connected when **Device info** or the dashboard shows a node name (`uConsole` by +default) and not a connection error. MeshTerm shows the device model as `pyMC-spi-bridge`. +With this model name, you can tell a node on the bridge from a real companion or from a +direct SPI connection. -**Identity.** If the `meshcore-console` GUI's package is present, the bridge loads *that* -package's identity key, so MeshTerm and the GUI share the same public key. Contacts and -radio settings are the bridge's own: it keeps contacts in -`~/.local/share/meshterm-spi-bridge/contacts.json`, its channels in -`~/.local/share/meshterm-spi-bridge/channels.json` (both survive a restart), and builds its -radio settings from the `MESHCORE_*` variables and its defaults, never from the GUI's saved -settings. Set the `MESHCORE_*` variables to match what the GUI uses. On a library-only -install, the bridge mints and keeps its own identity key under +**Identity.** If the package of the `meshcore-console` GUI is present, the bridge loads the +identity key of *that* package. Thus MeshTerm and the GUI have the same public key. The +contacts and the radio settings belong to the bridge: + +- The bridge keeps its contacts in `~/.local/share/meshterm-spi-bridge/contacts.json`. +- The bridge keeps its channels in `~/.local/share/meshterm-spi-bridge/channels.json`. + Both files stay after a restart. +- The bridge builds its radio settings from the `MESHCORE_*` variables and its defaults. + It never uses the saved settings of the GUI. Set the `MESHCORE_*` variables to match the + values that the GUI uses. + +On an install that has only the library, the bridge makes and keeps its own identity key in `~/.local/share/meshterm-spi-bridge/identity.key`. -**Only one program may use the radio at a time.** Never run the bridge and the -`meshcore-console` GUI at the same time — whichever started second will fail to open the -radio. The same goes for a direct `meshterm --spi` connection while the bridge is running. +**Only one program can use the radio at a time.** Never run the bridge and the +`meshcore-console` GUI at the same time. The program that starts second fails to open the +radio. The same is true of a direct `meshterm --spi` connection while the bridge runs. ### Updating and uninstalling the bridge -To update, pull or re-download `meshterm-spi-bridge` and reinstall the service (menu option -2 again — it overwrites the copy in `~/.local/bin/`). The running service keeps the old -code until you restart it: +To update the bridge, pull or download `meshterm-spi-bridge` again. Then install the service +again (menu option 2). This overwrites the copy in `~/.local/bin/`. The running service +keeps the old code until you restart it: ```bash systemctl --user restart meshterm-spi-bridge.service ``` -Option 2 also rewrites the unit file, so any `Environment=` lines you added to it by hand -are lost. Put them in a drop-in instead, as [Bridge hardware knobs](#bridge-hardware-knobs) -shows. +Option 2 also writes the unit file again. Thus it removes each `Environment=` line that you +added to the file by hand. Put these lines in a drop-in instead, as +[Bridge hardware knobs](#bridge-hardware-knobs) shows. -To remove the bridge, run the menu and choose **3) Uninstall the service**. This stops and -removes the `systemd --user` unit and the installed script copy. It does **not** touch -`meshcore-console`, the radio library, your node identity, or your saved contacts and -channels — those stay put. If you'd added `[profiles.bridge]` to your MeshTerm config, it -tells you to remove that block by hand if you no longer want it. +To remove the bridge, run the menu and choose **3) Uninstall the service**. This choice +stops the `systemd --user` unit and removes it. It also removes the installed copy of the +script. It does **not** change `meshcore-console`, the radio library, the identity of your +node, or your saved contacts and channels. These stay as they are. If you added +`[profiles.bridge]` to your MeshTerm config, the script tells you to remove that block by +hand when you no longer want it. ### Bridge hardware knobs -The pin defaults match the hackergadgets uConsole AIO v1, which needs none of these. The -**AIO v2 needs `MESHCORE_EN_PINS=27`**, whether or not the `meshcore-console` package is -installed. The radio settings default to the US/Canada preset. For a foreground run, export -what differs before launching the bridge. For the service, add a drop-in, which survives a -reinstall: +The pin defaults agree with the hackergadgets uConsole AIO v1. This board needs none of +these variables. The **AIO v2 needs `MESHCORE_EN_PINS=27`**, whether or not the +`meshcore-console` package is installed. The radio settings default to the US/Canada +preset. To run the bridge in the foreground, export the variables that are different before +you start it. For the service, add a drop-in. A drop-in stays after a reinstall: ```bash systemctl --user edit meshterm-spi-bridge.service ``` -and put the variables under a `[Service]` heading, one `Environment=` line each: +Put the variables under a `[Service]` heading, with one `Environment=` line for each: ```ini [Service] Environment=MESHCORE_EN_PINS=27 ``` -Then `systemctl --user restart meshterm-spi-bridge.service`. +Then run `systemctl --user restart meshterm-spi-bridge.service`. | Variable | What it sets | | --- | --- | -| `MESHCORE_BUS_ID`, `MESHCORE_CS_ID`, `MESHCORE_CS_PIN` | which SPI bus, device, and chip-select pin the radio is on | -| `MESHCORE_RESET_PIN`, `MESHCORE_BUSY_PIN`, `MESHCORE_IRQ_PIN` | the three control lines | -| `MESHCORE_FREQUENCY`, `MESHCORE_TX_POWER` | frequency in Hz, power in dBm | -| `MESHCORE_SPREADING_FACTOR`, `MESHCORE_BANDWIDTH`, `MESHCORE_CODING_RATE` | the modem preset — all three must match the mesh you are joining. Whole numbers only: bandwidth in Hz (`62500`), coding rate as the denominator (`5` for 4/5). A decimal or `4/5` stops the bridge at startup. | -| `MESHCORE_TXEN_PIN`, `MESHCORE_RXEN_PIN`, `MESHCORE_EN_PINS` | the RF-switch and power-enable lines a board may need (`-1` for none; `EN_PINS` is a comma list — the AIO v2 wants `27`) | -| `MESHCORE_USE_DIO2_RF`, `MESHCORE_USE_DIO3_TCXO`, `MESHCORE_IS_WAVESHARE` | whether DIO2 drives the RF switch and DIO3 the TCXO (both on for the AIO), and a flag for a different vendor's wiring the runtime knows about | -| `MESHCORE_GPIO_CHIP`, `MESHCORE_USE_GPIOD_BACKEND`, `MESHCORE_PREAMBLE_LENGTH` | which gpiochip (`0` unless set — the bridge doesn't look it up by label, so on a CM5 see [Which GPIO chip](#which-gpio-chip)), whether to drive it through `gpiod`, and the LoRa preamble (leave it unset: the bridge follows MeshCore, 32 symbols up to SF8 and 16 above, and a shorter one leaves the radio deaf to most of the mesh) | -| `MESHTERM_PYMC_PYTHON` | force a specific interpreter instead of letting the bridge discover one | +| `MESHCORE_BUS_ID`, `MESHCORE_CS_ID`, `MESHCORE_CS_PIN` | The SPI bus, the SPI device, and the chip-select pin of the radio | +| `MESHCORE_RESET_PIN`, `MESHCORE_BUSY_PIN`, `MESHCORE_IRQ_PIN` | The three control lines | +| `MESHCORE_FREQUENCY`, `MESHCORE_TX_POWER` | The frequency in Hz and the power in dBm | +| `MESHCORE_SPREADING_FACTOR`, `MESHCORE_BANDWIDTH`, `MESHCORE_CODING_RATE` | The modem preset. All three must match the mesh that you join. Use whole numbers only: the bandwidth in Hz (`62500`), and the coding rate as the denominator (`5` for 4/5). A decimal or `4/5` stops the bridge at startup. | +| `MESHCORE_TXEN_PIN`, `MESHCORE_RXEN_PIN`, `MESHCORE_EN_PINS` | The RF-switch lines and the power-enable lines that a board can need (`-1` for none). `EN_PINS` is a comma list. The AIO v2 needs `27`. | +| `MESHCORE_USE_DIO2_RF`, `MESHCORE_USE_DIO3_TCXO`, `MESHCORE_IS_WAVESHARE` | If DIO2 drives the RF switch, and if DIO3 drives the TCXO (both are on for the AIO). The last one is a flag for the wiring of a different vendor that the runtime knows. | +| `MESHCORE_GPIO_CHIP`, `MESHCORE_USE_GPIOD_BACKEND`, `MESHCORE_PREAMBLE_LENGTH` | The gpiochip (`0` unless you set it). The bridge does not look for it by label. On a CM5, refer to [Which GPIO chip](#which-gpio-chip). Then the choice to drive the pins through `gpiod`. Then the LoRa preamble. Leave the preamble unset. The bridge follows MeshCore: 32 symbols up to SF8, and 16 symbols above SF8. A shorter preamble makes the radio deaf to most of the mesh. | +| `MESHTERM_PYMC_PYTHON` | Forces a specific interpreter, so that the bridge does not look for one | -The bridge passes a knob only when the runtime's radio constructor accepts it, so the same -environment works under `pymc_core` 1.0.x, which lacks the newer ones. Where the -`meshcore-console` package is installed, the bridge builds the radio through that -package's environment reader, which reads the same names plus a few more of its own (see -that package's documentation) and ignores the GUI's saved presets. On a library-only -install the table above is the whole of it. Either way, an AIO v2 without -`MESHCORE_EN_PINS=27` will not power its radio. +The bridge passes a knob only when the radio constructor of the runtime accepts it. Thus +the same environment works under `pymc_core` 1.0.x, which does not have the newer knobs. +If the `meshcore-console` package is installed, the bridge builds the radio through the +environment reader of that package. This reader reads the same names and a few more of its +own (refer to the documentation of that package). It ignores the saved presets of the GUI. +On an install that has only the library, the table above is complete. In each case, an AIO +v2 without `MESHCORE_EN_PINS=27` does not power its radio. ### Bridge troubleshooting | Symptom | Check | | --- | --- | -| `node runtime` fails in the preflight | Did you build the venv in [step 1](#bridge-step-1--install-the-radio-runtime)? Is `MESHTERM_PYMC_PYTHON` pointing at the right interpreter, if set? | -| `SPI device` shows "no /dev/spidev* found" | Is `dtoverlay=spi1-1cs` in `/boot/firmware/config.txt`? Did you reboot after adding it? | -| `SPI device` or `GPIO chip` shows a permission error | Are you in the `spi` and `gpio` groups (`groups`)? Did you log out and back in after `usermod`? | -| Preflight warns `radio in use meshcore-console is running:` | Close the GUI before running or installing the bridge. | -| Preflight warns `port 5000 busy something is already on 127.0.0.1:5000` | Something other than the bridge service is listening there (an active service is reported as `bridge service running` instead). Usually it is a foreground `--run` in another terminal; stop that one. | -| Bridge exits with `bridge failed: GPIO pins still busy after retries` | The bridge retries `radio.begin()` four times only when the GPIO lines are busy. They may still be held by a session that just closed — wait a few seconds and try again. | -| MeshTerm connects but direct messages to an old contact say "not found" | Contacts are restored from the bridge's snapshot file at startup. If the contact is not in it, wait until the contact is heard again. | -| Reboot, factory reset, or setting a device PIN silently does nothing | Expected — the radio library has no handler for these three, whatever runtime you're on. | -| Traces or the live feed don't show up | These rely on compatibility shims the bridge installs (or, under `openhop_core`, on the library's own equivalents). Check the bridge's log for lines starting `compat:` to confirm they're wired. | +| `node runtime` fails in the preflight | Did you build the venv in [step 1](#bridge-step-1-install-the-radio-runtime)? If `MESHTERM_PYMC_PYTHON` is set, does it point to the correct interpreter? | +| `SPI device` shows "no /dev/spidev* found" | Is `dtoverlay=spi1-1cs` in `/boot/firmware/config.txt`? Did you reboot after you added it? | +| `SPI device` or `GPIO chip` shows a permission error | Are you in the `spi` and `gpio` groups (`groups`)? Did you log out and log in again after `usermod`? | +| Preflight warns `radio in use meshcore-console is running:` | Close the GUI before you run or install the bridge. | +| Preflight warns `port 5000 busy something is already on 127.0.0.1:5000` | A program other than the bridge service listens there. (An active service shows as `bridge service running` instead.) Usually it is a foreground `--run` in another terminal. Stop that one. | +| Bridge exits with `bridge failed: GPIO pins still busy after retries` | The bridge tries `radio.begin()` four times, only when the GPIO lines are busy. A session that just closed can still hold them. Wait a few seconds and try again. | +| MeshTerm connects but direct messages to an old contact say "not found" | The bridge restores contacts from its snapshot file at startup. If the contact is not in that file, wait until the node hears the contact again. | +| A reboot, a factory reset, or a device PIN change does nothing, and there is no message | This is expected. The radio library has no handler for these three actions, with each runtime. | +| Traces or the live feed do not show | These use the compatibility shims that the bridge installs. (Under `openhop_core`, they use the equivalents of the library.) Look in the log of the bridge for lines that start with `compat:`. These lines show that the shims are wired. | --- ## What this has been tested on **The direct connection** has run on a bookworm uConsole with a **CM5** and an AIO v1 -since 2026-09-24, on MeshTerm's defaults. It has also been tested against the real radio -library (`openhop-core`) with a simulated radio. A CM4 has not been tried with it; its -header chip is found by the same label lookup (see [Which GPIO chip](#which-gpio-chip)). +since 2026-09-24, with the defaults of MeshTerm. It was also tested against the real radio +library (`openhop-core`) with a simulated radio. A CM4 was not tried with it. MeshTerm finds +the header chip of a CM4 with the same label lookup (refer to +[Which GPIO chip](#which-gpio-chip)). -**The bridge** has been verified on a bookworm uConsole with `openhop-core` 1.1.3: the -radio comes up, contacts are restored across a restart, and `info` and `contacts` answer -correctly over the TCP connection. The older `pymc-core` 1.0.x path, with all three of the -bridge's compatibility shims active, has been exercised in full, including traces and the -live feed. MeshTerm's test suite does not cover the bridge. +**The bridge** was verified on a bookworm uConsole with `openhop-core` 1.1.3. The radio +starts, contacts are restored across a restart, and `info` and `contacts` answer correctly +over the TCP connection. The older `pymc-core` 1.0.x path was tested in full, with all +three compatibility shims of the bridge active. This includes traces and the live feed. +The test suite of MeshTerm does not cover the bridge. -What has not been confirmed: +What is not confirmed: -- **The direct connection on a CM4** — only the CM5 has run it. -- **The bridge under `openhop_core` beyond `info` and `contacts`.** Startup, identity, - contact restore and those two reads were exercised; a trace, a message and the live feed - under the new runtime were not, though the library's own trace push is what the bridge - now relies on for the first. -- **Everything on-air** — the per-hop trace semantics the bridge reassembles and the - acknowledgement bytes newer firmware appends after the CRC. The code is internally - consistent with the library's API; the wire behaviour was seen on one mesh, against the - radios in range of one bench. +- **The direct connection on a CM4.** Only the CM5 has run it. +- **The bridge under `openhop_core`, beyond `info` and `contacts`.** The tests covered + startup, identity, contact restore, and those two reads. They did not cover a trace, a + message, or the live feed under the new runtime. For traces, the bridge now uses the + trace push of the library itself. +- **Everything on the air.** This includes the trace semantics for each hop that the bridge + puts together again. It also includes the acknowledgement bytes that newer firmware adds + after the CRC. The code is consistent with the API of the library. The behaviour on the + wire was seen on one mesh, with the radios in range of one bench. diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 1ae9c9d..c059173 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -1,14 +1,17 @@ # Configuring MeshTerm -Preferences, device profiles, and where everything is kept. You need none of it to run -MeshTerm: it discovers a companion, lets you pick one, and remembers the choice. +This page covers preferences, device profiles, and the place where MeshTerm keeps each +file. You do not need any of it to run MeshTerm. MeshTerm discovers a companion, lets you +select one, and remembers your choice. -Two files, two jobs — and you need neither to run. +MeshTerm has two files for two jobs. You need neither file to run MeshTerm. -**Preferences** are how MeshTerm behaves: the pause it leaves between transmissions, how -hard it retries a message, how far back it keeps history, how a map frames itself. Every one has a built-in default, so change them only where you disagree — from -the **Preferences** page in the menu (grouped, staged, saved by one action at the bottom), -or from a shell: +**Preferences** are how MeshTerm behaves. Examples are the pause between transmissions, how +many times MeshTerm tries to send a message again, how far back it keeps history, and how +the map frames itself. Each preference has a built-in default. Change a preference only +where you do not agree with the default. You can change preferences on the **Preferences** +page in the menu. The page stages your changes, and you save them with one action at the +bottom. You can also change them from a shell: ```console $ meshterm preferences show # every preference, its value, its default @@ -16,16 +19,21 @@ $ meshterm preferences set history_days 90 $ meshterm preferences reset --yes # back to the built-in defaults ``` -They are kept in `~/.meshterm/preferences.toml`, which lists only what you have changed; -delete a line and the default takes over again. +MeshTerm keeps the preferences in `~/.meshterm/preferences.toml`. This file lists only the +preferences that you changed. If you delete a line, the default applies again. -**Config** is where things live and which device to talk to. MeshTerm runs with none of it -— it discovers attached serial devices and nearby Bluetooth companions, lets you pick one, -and remembers the last good default. A network (TCP) companion isn't discoverable, so reach -it with `--tcp host:port`, a TCP profile, or the picker's "add a network device" prompt. -A LoRa radio on this machine's own SPI bus is listed on the picker when its device node -exists, or reached with `--spi`; see [the uConsole manual](../devices/uconsole.md). -Write a config file only to give your hardware stable aliases. +A preference is not a setting. A **setting** is a value of the radio. The device keeps it, +and you edit it on Device config. + +**Config** is the setup of the machine: where files are, and which device to talk to. +MeshTerm runs without it. It discovers the serial devices that are attached and the +Bluetooth companions that are near. It lets you select one, and it remembers the last good +default. A network (TCP) companion cannot be discovered. To reach one, use `--tcp +host:port`, a TCP profile, or the "add a network device" prompt of the picker. The picker +lists a LoRa radio on the SPI bus of this machine when its device file exists. You can also +reach it with `--spi`. For more information, refer to [the uConsole manual](../devices/uconsole.md). +Write a config file only to give your hardware stable aliases. You edit this file with a +text editor. Copy [`config.example.toml`](../../config.example.toml) to `~/.meshterm/config.toml`: @@ -58,79 +66,86 @@ transport = "spi" # en_pins = [27] # the AIO v2 powers its radio from pin 27 ``` -Both live in `~/.meshterm`, along with everything else MeshTerm remembers: the SQLite -history, the outbox, the contact and channel caches, stored admin passwords, and the log. -**`$MESHTERM_HOME` moves the whole directory**, which is the way to run a second radio — -or a `--mock` session — without touching the one you use every day. `--db` moves the -database alone; the rest stays where it was. A `--mock` run that names neither writes to -`meshterm-mock.db` beside the real history, so the simulator's invented nodes never enter -it. [`docs/cli.md`](../cli/README.md#where-meshterm-keeps-its-state) -lists every file. +Both files are in `~/.meshterm`. MeshTerm also keeps everything else that it remembers +there: the SQLite history, the outbox, the caches of contacts and channels, the stored +admin passwords, and the log. -Timestamps are stored as UTC and rendered in your local time. Heard packets older than the -`history_days` preference are deleted when monitoring starts: once in each menu session, -and at the start of each `meshterm monitor` run. Nothing else is pruned. +**`$MESHTERM_HOME` moves the whole directory.** Use it to run a second radio, or a `--mock` +session, without a change to the files that you use each day. `--db` moves only the +database. The other files stay where they were. A `--mock` run that has neither option +writes to `meshterm-mock.db` beside the real history. Thus the invented nodes of the +simulator never enter the real history. +[`docs/cli.md`](../cli/README.md#where-meshterm-keeps-its-state) lists each file. + +MeshTerm stores timestamps as UTC and shows them in your local time. MeshTerm deletes +heard packets that are older than the `history_days` preference when monitoring starts. +This happens one time in each menu session, and at the start of each `meshterm monitor` +run. MeshTerm deletes no other data in this way. ## Adding a radio on the SPI bus -Some boards put the LoRa chip straight on the computer's SPI bus, with no firmware in front -of it: the uConsole's AIO board, and LoRa HATs for the Raspberry Pi. MeshTerm drives these -itself — it runs the node while it is connected and lets go of the radio when it quits. -It needs Linux and the radio library; [the uConsole manual](../devices/uconsole.md#step-2--install-the-radio-library) -shows how to install that. +Some boards put the LoRa chip directly on the SPI bus of the computer, with no firmware in +front of it. Examples are the AIO board of the uConsole, and LoRa HATs for the Raspberry +Pi. MeshTerm drives these radios itself. It runs the node while it is connected, and it +releases the radio when it quits. MeshTerm needs Linux and the radio library. +[The uConsole manual](../devices/uconsole.md#step-2-install-the-radio-library) shows how +to install the library. -**Two boards need no configuration.** An AIO v1's wiring is MeshTerm's default, so -whenever `/dev/spidev1.0` exists the device screen lists it: as **uConsole AIO** on a -uConsole, and as **SPI radio** on any other machine. M5Stack's -Cap LoRa-1262 on a Cardputer Zero ships with MeshTerm too, and is listed as **Cap -LoRa-1262** on a Cardputer Zero (see [the Cardputer Zero page](../devices/cardputer-zero.md#the-cap-lora-1262)). -Either way, `meshterm --spi` reaches it. Any other board is added with a profile that -states how it is wired, and a profile on the same `/dev/spidev*` replaces the shipped -wiring. +**Two boards need no configuration.** The wiring of an AIO v1 is the MeshTerm default. +Thus, when `/dev/spidev1.0` exists, the device screen lists it. The name is **uConsole AIO** +on a uConsole, and **SPI radio** on any other machine. MeshTerm also includes the Cap +LoRa-1262 of M5Stack on a Cardputer Zero. The device screen lists it as **Cap LoRa-1262** +on a Cardputer Zero (refer to [the Cardputer Zero page](../devices/cardputer-zero.md#the-cap-lora-1262)). +For either board, `meshterm --spi` reaches the radio. To add any other board, write a +profile that states how the board is wired. A profile on the same `/dev/spidev*` replaces +the wiring that MeshTerm includes. -**Why a profile, and not a prompt on the device screen.** A USB companion describes itself: -plug it in and there is nothing to ask. An SPI radio says nothing about how it is wired, and -the wiring is a dozen facts — the bus, the chip select, the reset, busy, and interrupt pins, -the antenna switch, the TCXO — any one of which, wrong, leaves a radio that comes up deaf -rather than failing. Those are facts to look up once and write down, not to type into a -dialog. And the radio doesn't come and go the way a cable does: it is soldered, stacked on -a header, or built into the case, so its description belongs somewhere as lasting as the -hardware is. A file you write once, and can read back when something is off, is that place. +**Why a profile, and not a prompt on the device screen.** A USB companion describes itself. +When you connect it, MeshTerm has nothing to ask. An SPI radio does not tell how it is +wired. The wiring is a dozen facts: the bus, the chip select, the reset, busy, and +interrupt pins, the antenna switch, and the TCXO. If any one of these is wrong, the radio +starts but cannot hear. It does not fail with an error. You look up these facts one time +and write them down. You do not type them into a dialog. Also, an SPI radio does not come +and go as a cable does. It is soldered, stacked on a header, or built into the case. Thus +its description must last as long as the hardware lasts. A file that you write one time, +and read again when something is wrong, is the correct place. ### 1. Find your board's wiring -The easiest place to find it is the board's `meshtasticd` configuration, which most vendors -publish (usually a `config.yaml` or a `config.d/` fragment with a `Lora:` section). Its keys -map one to one: +The easiest place to find the wiring is the `meshtasticd` configuration of the board. Most +vendors publish it. It is usually a `config.yaml` or a fragment in `config.d/` that has a +`Lora:` section. The keys map one to one: | `meshtasticd` (`Lora:`) | MeshTerm (`[profiles..spi]`) | | --- | --- | -| `spidev: spidev0.0` | `bus_id = 0` and `cs_id = 0` — the two numbers of `spidevB.C` | +| `spidev: spidev0.0` | `bus_id = 0` and `cs_id = 0`, the two numbers of `spidevB.C` | | `CS: 21` | `cs_pin = 21` (only when the board drives chip select from a GPIO) | | `Reset`, `Busy`, `IRQ` | `reset_pin`, `busy_pin`, `irq_pin` | | `TXen`, `RXen` | `txen_pin`, `rxen_pin` | | `DIO2_AS_RF_SWITCH: true` | `use_dio2_rf = true` | | `DIO3_TCXO_VOLTAGE: true` | `use_dio3_tcxo = true` (1.8 V) | -| `gpiochip: 4` | usually nothing — see below; `gpio_chip = 4` to pin it | +| `gpiochip: 4` | usually nothing (refer to the text below), or `gpio_chip = 4` to set it | -`Module` must be `sx1262`; that is the chip MeshTerm's node drives. **Leave `gpio_chip` out** -unless you need to override it: a `meshtasticd` file pins a number because Meshtastic can't -look the chip up, but the number that carries a Raspberry Pi's 40-pin header differs -between a Pi 4/CM4 and a Pi 5/CM5 and has moved between kernel releases on the latter. -Left out, it defaults to `-1`, and MeshTerm finds the header's chip by its label -(`pinctrl-rp1`, `pinctrl-bcm2711`, `pinctrl-bcm2835`) — falling back to chip `0` on a board -whose chips carry none of them. `gpiodetect` lists every chip with its label. The +`Module` must be `sx1262`, because the node of MeshTerm drives that chip. **Leave out +`gpio_chip`** unless you must override it. A `meshtasticd` file sets a number because +Meshtastic cannot look up the chip. But the number of the chip that has the 40-pin header +of a Raspberry Pi is not the same on a Pi 4/CM4 and a Pi 5/CM5. On the Pi 5/CM5 it also +changed between kernel releases. When you leave it out, it defaults to `-1`. Then MeshTerm +finds the chip of the header by its label (`pinctrl-rp1`, `pinctrl-bcm2711`, or +`pinctrl-bcm2835`). If a board has no chip with one of these labels, MeshTerm uses chip +`0`. `gpiodetect` lists each chip with its label. The [uConsole guide](../devices/uconsole.md#which-gpio-chip) has the whole story. -**State every pin and both switches.** Where a `meshtasticd` file leaves a key out, it means -"none" or "off". Where the `spi` table leaves a key out, it means *the AIO's value* — IRQ on -26, DIO2 and DIO3 on — which is wrong for almost any other board, and a radio wired wrong -comes up deaf rather than failing. +**State each pin and both switches.** In a `meshtasticd` file, a key that is left out means +"none" or "off". In the `spi` table, a key that is left out means the value of the AIO. +For example, IRQ is on pin 26, and DIO2 and DIO3 are on. These values are wrong for almost +any other board. A radio with the wrong wiring starts but cannot hear. It does not fail +with an error. ### 2. Write the profile In `~/.meshterm/config.toml`, give the radio a name and its wiring. The values below are an -illustration, not any particular board's — take yours from step 1: +example. They are not the values of a particular board. Take your values from step 1: ```toml [profiles.hat] @@ -148,11 +163,11 @@ use_dio2_rf = false # …so DIO2 doesn't use_dio3_tcxo = false # no TCXO on this board ``` -A `spi` table is enough to make it an SPI profile; `transport = "spi"` says so outright and -is what an AIO profile with no table uses. Every key, with the AIO defaults, is in -[the uConsole manual's wiring table](../devices/uconsole.md#board-wiring). A key MeshTerm doesn't know, -or a value of the wrong type, stops it at startup with the profile named — a misspelt pin -is never quietly ignored. +A `spi` table is enough to make the profile an SPI profile. `transport = "spi"` says so +directly. An AIO profile that has no table uses it. [The wiring table in the uConsole +manual](../devices/uconsole.md#board-wiring) has each key with its AIO default. If a key +is not known to MeshTerm, or a value has the wrong type, MeshTerm stops at startup and +names the profile. MeshTerm never ignores a misspelt pin without telling you. ### 3. Check it @@ -160,22 +175,30 @@ is never quietly ignored. meshterm -p hat info ``` -The first run starts the node, which mints the radio's identity. If something is wrong, the -error says what to fix — a missing `/dev/spidev*` (enable SPI and reboot), a permission -problem (join the `spi` and `gpio` groups), a missing radio library, or another program -already holding the radio. A node that starts but never answers leaves its log in -`~/.meshterm/radio/spidev0.0/node.log`. Once it answers, the device screen lists the radio -under the profile's name. +The first run starts the node, which creates the identity of the radio. If something is +wrong, the error message tells you what to correct. These are the possible problems: -### What the profile doesn't hold +- A `/dev/spidev*` file is missing. Enable SPI and reboot. +- A permission problem. Join the `spi` and `gpio` groups. +- The radio library is missing. +- Another program already holds the radio. -- **Radio settings.** Frequency, bandwidth, spreading factor, coding rate, and TX power are - the node's own settings, saved by the node and changed on **Device config** like any - companion's. A new node starts on MeshCore's **US/Canada preset** (910.525 MHz, 62.5 kHz, - SF7, CR 4/5, 22 dBm): anywhere else, change it on Device config before you send anything. -- **The preamble.** The node follows MeshCore's own rule — 32 symbols up to SF8, 16 above — - and a receiver that expects anything else misses most of the mesh. `preamble_length` was - once a wiring key and is refused now; delete the line if an old profile has it. -- **Identity.** The node's key, contacts, and channels live in - `~/.meshterm/radio//`, filed by the device node rather than the profile, so - renaming a profile, or reaching the same radio from the device screen, keeps the same node. +If the node starts but never answers, it writes its log to +`~/.meshterm/radio/spidev0.0/node.log`. When the node answers, the device screen lists the +radio under the name of the profile. + +### What the profile does not hold + +- **Radio settings.** The frequency, bandwidth, spreading factor, coding rate, and TX power + are settings of the node. The node saves them, and you change them on **Device config**, + as for any companion. A new node starts on the **US/Canada preset** of MeshCore + (910.525 MHz, 62.5 kHz, SF7, CR 4/5, 22 dBm). In any other region, change the preset on + Device config before you send anything. +- **The preamble.** The node follows the rule of MeshCore: 32 symbols up to SF8, and 16 + symbols above SF8. A receiver that expects a different length misses most of the mesh. + `preamble_length` was once a wiring key. MeshTerm now refuses it. If an old profile has + this key, delete the line. +- **Identity.** The key, contacts, and channels of the node are in + `~/.meshterm/radio//`. MeshTerm files them under the device file, not the + profile. Thus, if you rename a profile, or you reach the same radio from the device + screen, you keep the same node. diff --git a/docs/guide/connecting.md b/docs/guide/connecting.md index decdad3..cb5d050 100644 --- a/docs/guide/connecting.md +++ b/docs/guide/connecting.md @@ -1,30 +1,30 @@ -# When a companion won't connect +# When a companion does not connect -MeshTerm talks to a MeshCore companion over USB, Bluetooth, or TCP, or runs the node itself -on a radio on the SPI bus. When a connection fails, MeshTerm says which step failed and what -to do about it. This page collects those messages, so you can look one up before you meet -it, or search for the one you have. +MeshTerm talks to a MeshCore companion over USB, Bluetooth, or TCP. It can also run the +node itself on a radio on the SPI bus. When a connection fails, MeshTerm tells you which +step failed and what to do. This page lists these messages. Use it to look up a message +before you see it, or to search for the message that you have. You see the same message wherever the connection fails: -- **In the device picker**, when you choose a device. +- **In the device picker**, when you select a device. - **When MeshTerm starts**, if you named the device on the command line (`--port`, `--ble`, - `--tcp`, or `--profile`). MeshTerm shows the message, then opens the menu without a + `--tcp`, or `--profile`). MeshTerm shows the message. Then it opens the menu without a radio. A tool that needs the radio tries to connect again when you open it. -- **While MeshTerm waits for a device to come back**, after it was unplugged or rebooted. - If the device comes back but still won't connect, the message appears under the - spinner, so you are not left waiting for something that will not happen. Fix the - problem, and MeshTerm connects without you doing anything else. +- **While MeshTerm waits for a device to come back**, after you unplugged the device or it + rebooted. If the device comes back but still does not connect, the message shows under + the spinner. Thus you do not wait for something that will not happen. Correct the + problem. Then MeshTerm connects, and you do nothing more. - **On the command line**, as the error message. -Every failure is also written to the log file, `~/.meshterm/meshterm.log` (see -[Where MeshTerm keeps its state](../cli/README.md#where-meshterm-keeps-its-state)). Attach it to a -bug report. For more detail, set the **Log detail** preference to `DEBUG` (`meshterm -preferences set log_level DEBUG`) and run it again. +MeshTerm also writes each failure to the log file, `~/.meshterm/meshterm.log` (refer to +[Where MeshTerm keeps its state](../cli/README.md#where-meshterm-keeps-its-state)). Attach +the log file to a bug report. To get more detail, set the **Log detail** preference to +`DEBUG` (`meshterm preferences set log_level DEBUG`), and run the command again. -Sometimes MeshTerm meets a failure it has no name for. Then the message is the error in -its own words, and a second line says where the log file is. The log holds the full -error, which is what a bug report needs. +Sometimes MeshTerm meets a failure that it has no name for. Then the message is the error +in its own words, and a second line tells where the log file is. The log has the full +error, which a bug report needs. - [Bluetooth](#bluetooth) - [Who does the pairing](#who-does-the-pairing) @@ -39,77 +39,81 @@ error, which is what a bug report needs. ## Bluetooth -**A companion takes one Bluetooth connection at a time.** If your phone's MeshCore app is -connected to it, MeshTerm cannot connect until the phone lets go, and the companion stops -advertising while it is connected, so MeshTerm cannot even see it. Disconnect the phone -first, or turn its Bluetooth off. +**A companion takes one Bluetooth connection at a time.** If the MeshCore app on your phone +is connected to the companion, MeshTerm cannot connect until the phone disconnects. While +the companion is connected, it stops its BLE advertisement, so MeshTerm cannot see it. +Disconnect the phone first, or turn its Bluetooth off. ### Who does the pairing -A PIN-protected companion must be *paired* before it will talk: the computer and the -companion exchange keys once, using the six-digit PIN, and remember them. Who runs that -exchange depends on the system. +A companion that has a PIN must be *paired* before it will talk. The computer and the +companion exchange keys one time, with the six-digit PIN, and they remember the keys. The +system decides who does this exchange. | System | Who pairs | What you do | | --- | --- | --- | -| Windows | MeshTerm | Give the PIN when MeshTerm asks for it (the picker's PIN dialog, or `--ble-pin` on the command line). You do not need to pair in Settings first. | -| Linux | MeshTerm | The same. MeshTerm answers the pairing itself, so it works over SSH and on machines with no desktop, and you do not need `bluetoothctl` or the desktop's Bluetooth settings. | -| macOS | macOS | macOS shows its own dialog asking for the code; type the PIN there. `--ble-pin` has no effect on macOS. | +| Windows | MeshTerm | Give the PIN when MeshTerm asks for it (in the PIN dialog of the picker, or with `--ble-pin` on the command line). You do not pair in Settings first. | +| Linux | MeshTerm | Do the same as on Windows. MeshTerm answers the pairing itself. Thus it works over SSH and on machines that have no desktop. You do not need `bluetoothctl` or the Bluetooth settings of the desktop. | +| macOS | macOS | macOS shows its own dialog that asks for the code. Type the PIN there. `--ble-pin` has no effect on macOS. | -Once paired, the system remembers it, and later connections need no PIN. +After the pairing, the system remembers it. Later connections do not need the PIN. -**On macOS, the terminal needs Bluetooth permission.** macOS grants Bluetooth to the app -MeshTerm runs in (Terminal, iTerm2, Ghostty), not to MeshTerm itself. If no pairing dialog -appears, or no Bluetooth devices are listed, check System Settings > Privacy & Security > -Bluetooth. **Over SSH, macOS denies Bluetooth outright** and never asks, so run MeshTerm in -a terminal on the Mac itself. +**On macOS, the terminal needs permission for Bluetooth.** macOS gives Bluetooth permission +to the app that MeshTerm runs in (Terminal, iTerm2, or Ghostty). It does not give it to +MeshTerm. If no pairing dialog appears, or no Bluetooth devices are listed, check System +Settings > Privacy & Security > Bluetooth. **Over SSH, macOS denies Bluetooth completely** +and never asks. Thus you must run MeshTerm in a terminal on the Mac itself. ### Finding the PIN -The PIN is shown on the companion's screen, or in the MeshCore app's settings for it. A -companion with no screen (a T1000-E, for instance) can tell you over USB instead: +The PIN is on the screen of the companion, or in the settings for it in the MeshCore app. +If the companion has no screen (a T1000-E, for example), you can get the PIN over USB: ```console $ meshterm --port /dev/ttyACM0 config get device_pin 123456 ``` -`meshterm config set device_pin ` changes it, and so does the **Device PIN** row on -the menu's Device config page. A changed PIN makes every existing pairing with that -companion out of date. See [a stale pairing](#what-each-bluetooth-message-means) below. +To change the PIN, run `meshterm config set device_pin `. You can also use the +**Device PIN** row on the Device config page of the menu. When you change the PIN, each +existing pairing with that companion is out of date. Refer to +[a stale pairing](#what-each-bluetooth-message-means) below. ### What each Bluetooth message means -In the device picker, a pairing problem opens the PIN dialog with a one-line reason under -the question. On the command line, it is the error message. Both come from the same -diagnosis. +In the device picker, a pairing problem opens the PIN dialog. The dialog shows a one-line +reason under the question. On the command line, the reason is the error message. Both come +from the same diagnosis. | The message starts with… | What happened | What to do | | --- | --- | --- | -| **couldn't open a Bluetooth link** | The companion was never reached. | Move closer, check it is powered on, and make sure no phone or other computer is connected to it. | -| **requires a Bluetooth pairing PIN** | The companion is PIN-protected and not paired with this computer. | Give the PIN: the picker asks for it; on the command line, add `--ble-pin `. | -| **Windows has … paired** / **Linux has … paired, but the device refused that pairing** | Your computer remembers a pairing that the companion no longer accepts. It was reflashed, reset, or given a new PIN, or it was paired *without* a PIN back when it did not have one. | Give the PIN, and MeshTerm removes the old pairing and pairs again. Or [forget the pairing](#forgetting-a-pairing) yourself and reconnect. | -| **rejected the Bluetooth PIN** | The PIN was wrong. | Check the code on the device or in the MeshCore app, and try again. | -| **refused to pair** | The companion turned the pairing down. It is usually connected to a phone, or still holds an old pairing for this computer. | Disconnect it from anything else, or restart it, and try again. | -| **Windows couldn't pair** / **Linux couldn't pair** | The pairing failed for another reason, named in brackets. | Restart the companion, [forget the pairing](#forgetting-a-pairing), and try again. | -| **Windows couldn't reach** / **Linux couldn't reach … to pair it** | The system could not find the companion when it came to pair. | As for a link that never opened: range, power, or another connection. | -| **paired with …, but the device still refuses the connection** | Pairing succeeded, yet the companion still refuses. It is holding an old pairing for this computer. | Restart the companion, [forget the pairing](#forgetting-a-pairing), and try again. | -| **was not paired. macOS asks for the pairing code in its own dialog** | macOS had not finished pairing. | Type the PIN into the macOS dialog when it appears. If none appears, check the terminal's Bluetooth permission (above). | -| **connecting … took longer than** / **connecting … timed out** | The link, the pairing, or the service discovery stalled. | Move closer, restart the companion, and try again. | -| **connected …, but it didn't answer the identity query** | The link came up and the companion went quiet. | It may be busy with another app. Restart it and try again. | -| **connected …, but it never answered as a MeshCore companion** | Something answered, but not as a companion. | It may be running repeater or room server firmware, which has no companion interface. | +| **"couldn't open a Bluetooth link"** | MeshTerm did not reach the companion. | Move closer. Make sure that the companion is on. Make sure that no phone or other computer is connected to it. | +| **"requires a Bluetooth pairing PIN"** | The companion has a PIN, and it is not paired with this computer. | Give the PIN. The picker asks for it. On the command line, add `--ble-pin `. | +| **"Windows has … paired"** / **"Linux has … paired, but the device refused that pairing"** | Your computer remembers a pairing that the companion does not accept now. The companion was reflashed, reset, or given a new PIN. Or you paired it *without* a PIN when it had no PIN. | Give the PIN. MeshTerm removes the old pairing and pairs again. Or [forget the pairing](#forgetting-a-pairing) yourself, and connect again. | +| **"rejected the Bluetooth PIN"** | The PIN was wrong. | Check the code on the device or in the MeshCore app. Then try again. | +| **"refused to pair"** | The companion did not accept the pairing. Usually it is connected to a phone, or it still has an old pairing for this computer. | Disconnect the companion from anything else, or restart it. Then try again. | +| **"Windows couldn't pair"** / **"Linux couldn't pair"** | The pairing failed for another reason. The message names the reason in brackets. | Restart the companion. [Forget the pairing](#forgetting-a-pairing). Then try again. | +| **"Windows couldn't reach"** / **"Linux couldn't reach … to pair it"** | The system could not find the companion when it tried to pair. | Do the same as for a link that did not open: check the range, the power, and other connections. | +| **"paired with …, but the device still refuses the connection"** | The pairing worked, but the companion still refuses the connection. It has an old pairing for this computer. | Restart the companion. [Forget the pairing](#forgetting-a-pairing). Then try again. | +| **"was not paired. macOS asks for the pairing code in its own dialog"** | macOS did not finish the pairing. | Type the PIN in the macOS dialog when it appears. If no dialog appears, check the Bluetooth permission of the terminal (refer to the text above). | +| **"connecting … took longer than"** / **"connecting … timed out"** | The link, the pairing, or the service discovery stopped. | Move closer. Restart the companion. Then try again. | +| **"connected …, but it didn't answer the identity query"** | The link came up, and then the companion did not answer. | Make sure that no other app uses the companion, because it can be busy. Restart it and try again. | +| **"connected …, but it never answered as a MeshCore companion"** | Something answered, but it did not answer as a companion. | Find out if the device has the firmware of a repeater or a room server. These have no companion interface. | ### Forgetting a pairing -MeshTerm fixes a stale pairing itself when you give it the PIN. To remove one by hand: +When you give MeshTerm the PIN, it repairs a stale pairing itself. To remove a pairing by +hand, do the step for your system: -- **Windows:** Settings > Bluetooth & devices, open the companion, and choose *Remove device*. -- **Linux:** `bluetoothctl remove AA:BB:CC:DD:EE:FF`, with your companion's address. -- **macOS:** System Settings > Bluetooth, the companion's ⓘ button, then *Forget This Device*. +- **Windows:** Go to Settings > Bluetooth & devices. Open the companion, and select *Remove + device*. +- **Linux:** Run `bluetoothctl remove AA:BB:CC:DD:EE:FF`. Use the address of your companion. +- **macOS:** Go to System Settings > Bluetooth. Select the ⓘ button of the companion, then + *Forget This Device*. -On Windows and Linux, MeshTerm's quit dialog also offers **Unpair & quit** while a paired -Bluetooth companion is connected. It forgets the pairing on the way out, so the next -connection asks for the PIN again. +On Windows and Linux, the quit dialog of MeshTerm also has **Unpair & quit** while a paired +Bluetooth companion is connected. This choice forgets the pairing as MeshTerm leaves. Thus +the next connection asks for the PIN again. --- @@ -117,35 +121,36 @@ connection asks for the PIN again. | The message starts with… | What happened | What to do | | --- | --- | --- | -| **no permission to open** | Your user may not open the port. On Linux, serial ports belong to a group, `dialout` on Ubuntu and Debian. | `sudo usermod -aG dialout $USER`, then log out and back in. The new group applies only to a new login. | -| **… is in use by another program** | Something else has the port open: the MeshCore app, a firmware flasher, or a serial monitor. On Windows this is reported as *Access is denied*. | Close the other program. On Linux, ModemManager probes new USB serial devices and can hold one: `sudo systemctl stop ModemManager`, and try again. | -| **… isn't there** | The port does not exist. The device was unplugged, or came back under another name (`/dev/ttyACM0` becoming `/dev/ttyACM1`, `COM5` becoming `COM6`). | `meshterm devices` lists what is attached now. | -| **no response from a MeshCore companion on …** | The port opened, but nothing answered as a companion. | It may not be a MeshCore companion (repeater firmware, or another device), or it is powered off or busy. On Linux, ModemManager may be probing it: `sudo systemctl stop ModemManager`, and try again. | +| **"no permission to open"** | Your user may not open the port. On Linux, a group owns the serial ports. It is `dialout` on Ubuntu and Debian. | Run `sudo usermod -aG dialout $USER`. Then log out and log in again. The new group applies only to a new login. | +| **"… is in use by another program"** | Another program has the port open: the MeshCore app, a firmware flasher, or a serial monitor. On Windows, the system reports this as *Access is denied*. | Close the other program. On Linux, ModemManager probes new USB serial devices, and it can hold one. Run `sudo systemctl stop ModemManager`, and try again. | +| **"… isn't there"** | The port does not exist. The device was unplugged, or it came back under another name (`/dev/ttyACM0` became `/dev/ttyACM1`, or `COM5` became `COM6`). | Run `meshterm devices` to list what is attached now. | +| **"no response from a MeshCore companion on …"** | The port opened, but nothing answered as a companion. | Make sure that the device is a MeshCore companion. It can have the firmware of a repeater, or it can be another device. It can also be off or busy. On Linux, ModemManager can be probing it. Run `sudo systemctl stop ModemManager`, and try again. | -To stop ModemManager for good rather than for one boot, `sudo systemctl disable --now -ModemManager`, if nothing else on the machine needs it. Nothing does unless you have a -cellular modem. +To stop ModemManager for good, and not only for one boot, run `sudo systemctl disable --now +ModemManager`. Do this only if nothing else on the machine needs ModemManager. Nothing +needs it unless you have a cellular modem. --- ## TCP -Like Bluetooth, a network companion serves one client at a time. MeshTerm's default port -is `5000`. +A network companion serves one client at a time, as a Bluetooth companion does. The default +port of MeshTerm is `5000`. | The message starts with… | What happened | What to do | | --- | --- | --- | -| **couldn't find a host named** | The name didn't lead to any address. | Check the spelling, or use the companion's IP address instead. | -| **nothing is accepting connections on port** | The computer was reached, but nothing is listening on that port. | Check the port number, and that the companion, or the bridge in front of it, is running. | -| **there's no route to** | Your computer can't reach that address at all. | Check the address, and that your computer is on the companion's network (or its VPN). | -| **no answer from … on port** | Nothing answered in time. | Check that the companion is powered on and on your network. A firewall can also hold a connection without refusing it. | -| **… closed the connection as it opened** | The companion hung up straight away. | Another client is probably connected to it. Disconnect that one first. | -| **the connection to … opened, but nothing answered as a MeshCore companion** | Something is listening on the port, but it isn't a companion. | Check the port number. If it is right, the companion may be busy with another client. | +| **"couldn't find a host named"** | The name did not lead to an address. | Check the spelling, or use the IP address of the companion. | +| **"nothing is accepting connections on port"** | MeshTerm reached the computer, but nothing listens on that port. | Check the port number. Make sure that the companion, or the bridge in front of it, is running. | +| **"there's no route to"** | Your computer cannot reach that address. | Check the address. Make sure that your computer is on the network of the companion (or on its VPN). | +| **"no answer from … on port"** | Nothing answered in time. | Make sure that the companion is on and is on your network. A firewall can hold a connection without a refusal. | +| **"… closed the connection as it opened"** | The companion hung up at once. | Disconnect the other client first. Probably another client is connected to the companion. | +| **"the connection to … opened, but nothing answered as a MeshCore companion"** | Something listens on the port, but it is not a companion. | Check the port number. If the number is correct, the companion can be busy with another client. | --- ## A radio on the SPI bus -A radio MeshTerm drives itself (`--spi`) is not a companion. It has no pairing and no port, -and its failures are about the node MeshTerm starts. Its own guides cover them: -[the uConsole](../devices/uconsole.md) and [adding a radio on the SPI bus](configuration.md#adding-a-radio-on-the-spi-bus). +A radio that MeshTerm drives itself (`--spi`) is not a companion. It has no pairing and no +port. Its failures are about the node that MeshTerm starts. Its own guides cover these +failures: [the uConsole](../devices/uconsole.md) and +[adding a radio on the SPI bus](configuration.md#adding-a-radio-on-the-spi-bus). diff --git a/docs/guide/features.md b/docs/guide/features.md index e5c551f..cf305aa 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -1,94 +1,97 @@ # What MeshTerm does -Every screen the menu offers, and the command that does the same job without it. This is -the catalogue; [the command line](../cli/README.md) is the manual for the scripted half of it. +This page lists each screen that the menu has, and the command that does the same job +without the menu. It is the catalogue. [The command line](../cli/README.md) is the manual +for the scripted half. -MeshTerm's menu asks one question — *what would you like to do?* — and its six sections -answer it in two halves. The first three name a **doing**: Message, Watch, Explore. The -last three name **whose** a setting is: This node, the radio in your hand; -Other nodes, someone else's over the mesh; and This app, the program in front of you. +The main menu asks one question: "What would you like to do?" It has six sections. The first +three sections name an action: Message, Watch, and Explore. The last three sections name +whose setting it is. "This node" is the radio in your hand. "Other nodes" are radios of +other persons, which you reach over the mesh. "This app" is the program in front of you. -Every interactive screen is listed here with its scripted equivalent, where one exists. +This page lists each interactive screen with its scripted equivalent, where one exists. -## 💬 Message — the people on the other end +## 💬 Message: the people on the other end | Feature | What it does | Scripted | | --- | --- | --- | -| **💬 Chat** | Live full-screen channel and direct messaging — a scrolling transcript with a pinned input line where sent and received messages stream together. The rooms you've joined are here too, each post shown under whoever wrote it. Every message is logged; unread counts show in the menu header. | `meshterm chat send / history / list / listen` | -| **📻 Channels** | Create, join, reorder, mute, and share mesh channels — with QR codes and `meshcore://` share links. Give a channel a **send scope** and its messages are flooded into one region, relayed only by the repeaters that carry it. | `meshterm channels list / add / join / import / share / clear / scope` | -| **📌 Rooms** | Every room server your radio knows. A room server is a message board on a radio that keeps the latest posts and sends you the ones you missed. Join one with its password; when a room doesn't answer, MeshTerm explains what it knows and offers the next thing to try, such as resending by flood when the route your radio learned has gone stale. | `meshterm rooms list / join / forget` | -| **📨 Courier** | Store-and-forward outbox for contacts that aren't reachable yet. Queue a message; it goes out (with ack tracking and polite exponential backoff) the moment the contact is next heard, or at a scheduled time. | `meshterm courier queue / list / send / cancel / clear` | -| **👥 Contacts** | This node and its known contacts — a recency heat-map, overheard packet counts, and full public keys with the path-hash prefix highlighted. The scripted listing is the contacts alone (this node is `meshterm info`'s answer, in far more detail). | `meshterm contacts` | +| **💬 Chat** | Live full-screen messages on channels and to contacts. The transcript scrolls, the input line stays at the bottom, and sent and received messages appear together. The rooms that you joined are also here. Each post shows under the name of its author. MeshTerm stores each message. Unread counts show in the menu header. | `meshterm chat send / history / list / listen` | +| **📻 Channels** | Create, join, reorder, mute, and share mesh channels. You can share a channel with a QR code or a `meshcore://` link. If you give a channel a **send scope**, its messages are flooded into one region. Only the repeaters that carry that region relay them. | `meshterm channels list / add / join / import / share / clear / scope` | +| **📌 Rooms** | Each room server that your radio knows. A room server is a message board on a radio. It keeps the latest posts and sends you the posts that you missed. Join a room with its password. If a room does not answer, MeshTerm tells you what it knows and offers the next step to try. For example, it can send again by flood when the route that your radio learned is no longer good. | `meshterm rooms list / join / forget` | +| **📨 Courier** | A store-and-forward outbox for contacts that you cannot reach yet. Queue a message. MeshTerm sends it when the contact is next heard, or at a time that you set. It tracks the acks, and it waits longer between each try (exponential backoff). | `meshterm courier queue / list / send / cancel / clear` | +| **👥 Contacts** | This node and its known contacts. The screen shows a heat map of how recently each contact was heard, the counts of overheard packets, and the full public keys. The hash of each key is highlighted. The scripted listing shows only the contacts. For this node, run `meshterm info`, which gives much more detail. | `meshterm contacts` | -## 📊 Watch — what the mesh is doing, and what it did +## 📊 Watch: what the mesh is doing, and what it did | Feature | What it does | Scripted | | --- | --- | --- | -| **📊 Dashboard** | The live mesh overview: a two-hour all-packet activity chart with a pulse line, session traffic tallies by packet class, and window RF health beside the radio's own live numbers. Repaints every second. | — | -| **📰 Live feed** | Every packet as it arrives, newest first: time, what the frame *is* (its parsed payload class), and what it is *about* — the node for an advert, the channel for a channel text, sender → recipient for a direct message or a request, the tag for a trace — with reception quality beside it, and the region a scoped flood was sent into. Enter opens any row in the packet viewer, route graph and all. | — | -| **🚨 Watchtower** | A passive sentinel over the nodes you star: silence alarms when a watched node goes quiet, SNR-sag warnings when reception degrades, recovery notes when it returns, and a heads-up when a node is heard for the first time. Runs in the background all session; unacked alerts show as a header badge. | — | -| **⏳ Time machine** | Everything the recorder ever heard, as braille charts: reception volume, the median-SNR band, hour-of-day rhythm, packets and nodes per day, and first-ever arrivals — over a switchable 24h / 7d / 30d / all-time window (on the PicoCalc the windows stop at 30d), per node or mesh-wide. | — | -| **🎧 Monitor** | Passive recording is always on in the app. On the CLI, capture a bounded foreground window, tailing each overheard packet and summarising when it ends. It transmits nothing — it only listens. | `meshterm monitor --seconds 60` | +| **📊 Dashboard** | The live overview of the mesh. It has a two-hour chart of the activity of all packets with a pulse line, the traffic counts of the session for each packet class, and window RF health next to the live numbers of the radio. The screen updates each second. | none | +| **📰 Live feed** | Each packet as it arrives, newest first. A row shows the time, the type of the packet (its parsed payload class), and what the packet is about. For an advert, that is the node. For a channel text, it is the channel. For a direct message or a request, it is sender → recipient. For a trace, it is the tag. The row also shows the reception quality and, for a scoped flood, the region of the flood. Press Enter to open a row in the packet viewer, with its route graph. | none | +| **🚨 Watchtower** | A passive watcher of the nodes that you star. It gives a silence alarm when a watched node goes quiet. It warns you when the SNR of a node gets worse, and it tells you when the node returns. It also tells you when a node is heard for the first time. It runs in the background during the whole session. Alerts that you did not acknowledge show as a badge in the header. | none | +| **⏳ Time machine** | Everything that the recorder ever heard, as braille charts: reception volume, the median-SNR band, the rhythm of the hours of the day, packets and nodes for each day, and first-ever arrivals. You can switch the time window between 24h, 7d, 30d, and all time. On the PicoCalc, the windows stop at 30d. You can look at one node or at the whole mesh. | none | +| **🎧 Monitor** | The app always records passively. On the command line, this command captures a window of a fixed length in the foreground. It shows each overheard packet as it arrives, and it gives a summary at the end. It transmits nothing. It only listens. | `meshterm monitor --seconds 60` | -## 🧭 Explore — where the nodes are and how they reach each other +## 🧭 Explore: where the nodes are and how they reach each other | Feature | What it does | Scripted | | --- | --- | --- | -| **🌍 Map** | Located nodes plotted over a real OpenStreetMap street basemap rendered as Unicode braille (streets, rivers, place names). Pannable and zoomable; repeaters highlighted and drawn on top. Falls back to a blank grid offline. | — (a map is a picture; `meshterm contacts` lists the same nodes) | -| **🌐 Mesh walk** | The mesh's *observed shape*, walked one node at a time: an evidence graph built from trace walks, firmware routes, overheard relay chains, and repeater neighbour tables. SNR-coloured braille edges, quality bars, Enter to walk, ⌫ to backtrack, type to find any node. The map answers *where*; the walk answers *how it hangs together*. | — | -| **🎯 Trace target** | A live trace screen: pick a target and watch each trace stream in hop by hop, with running per-hop medians and reliability. Compose or force a route through specific repeaters. A trace transmits **exactly once** (repeaters can blacklist nodes that burst) — sample more by running it again. | `meshterm trace --target …` | -| **👣 Trace path** | The other half of tracing: compose the whole circuit by hand — out and back whichever way you choose — and walk it. The path composer suggests each next hop from the links actually observed, strongest first, and can fetch a repeater's neighbour table over the mesh when you hold its admin password. | `meshterm trace-path --path …` | -| **🏆 Trophy case** | Every trace that comes home is scored, on seven boards: longest distance, farthest node, longest single leg, most nodes (with and without revisits), weakest surviving link, and biggest enclosed loop. Records are kept per hash width, and a record walk must be a *trail* — no link crossed twice the same way. | `meshterm records` | +| **🌍 Map** | The nodes that have a position, plotted on a real OpenStreetMap street map. The map is drawn as Unicode braille (streets, rivers, and place names). You can pan and zoom. Repeaters are highlighted and drawn on top. When you are offline, the map shows a blank grid. | none (a map is a picture, but `meshterm contacts` lists the same nodes) | +| **🌐 Mesh walk** | The observed shape of the mesh, which you walk one node at a time. It is a graph of evidence from trace walks, firmware routes, overheard relay chains, and the neighbour tables of repeaters. It has braille edges that are coloured by SNR, and quality bars. Press Enter to walk to a node. Press ⌫ to go back. Type to find any node. The map shows where the nodes are. The walk shows how the nodes connect. | none | +| **🎯 Trace target** | A live trace screen. Select a target, and each trace streams in, hop by hop. The screen shows the running median and the reliability of each hop. You can compose a route through specific repeaters, or force one. A trace transmits **exactly once**, because repeaters can blacklist nodes that send bursts. To get more samples, run the trace again. | `meshterm trace --target …` | +| **👣 Trace path** | The other half of tracing. You compose the whole circuit by hand, out and back, by the way that you choose. Then you walk it. The path composer suggests each next hop from the links that MeshTerm observed, strongest first. If you have the admin password of a repeater, the composer can also get the neighbour table of that repeater over the mesh. | `meshterm trace-path --path …` | +| **🏆 Trophy case** | MeshTerm scores each trace that comes home on seven boards: longest distance, farthest node, longest single leg, most nodes (with and without revisits), weakest surviving link, and biggest enclosed loop. It keeps the records for each hash width. A record walk must be a trail, which means that no link is crossed twice in the same direction. | `meshterm records` | -## 🔧 This node — the radio in your hand +## 🔧 This node: the radio in your hand | Feature | What it does | Scripted | | --- | --- | --- | -| **📋 Device info** | The connected companion's identity and full radio configuration at a glance. | `meshterm info` | -| **🔧 Device config** | Every setting the companion firmware exposes — name, radio, client repeat, auto-add, telemetry, custom variables — *staged* for review and applied in place, plus the operations on the box itself: clock sync, TOML backup/restore, the identity key, reboot, and factory reset, each acting the moment it's confirmed, with destructive ops gated. Laid out like Repeater admin, so the radio in your hand and one over the mesh are configured the same way. | `meshterm config` / `config set ` / `config backup` / `config reboot` / … | -| **📡 Send advert** | Announce this node to the mesh — a zero-hop or flood advertisement, or share this node's contact card as a QR code. | `meshterm config advert` / `config share` | -| **🔌 Devices** | Not a menu screen. The companion is picked on the startup splash, which enumerates serial *and* Bluetooth LE companions (or auto-selects the only one present), adds a network (TCP) companion by host:port, and remembers the last good default. A dropped link is detected live and offers to reconnect. `meshterm devices` lists what discovery finds. | `meshterm devices` | +| **📋 Device info** | The identity of the connected companion and its full radio configuration, on one screen. | `meshterm info` | +| **🔧 Device config** | Each setting that the companion firmware has: name, radio, client repeat, auto-add, telemetry, and custom variables. MeshTerm stages your changes for review and then applies them in place. The screen also has operations on the device itself: clock sync, TOML backup and restore, the identity key, reboot, and factory reset. Each operation acts when you confirm it, and the destructive operations need a confirm step. The screen has the same layout as Repeater admin. Thus you configure the radio in your hand and a radio over the mesh in the same way. | `meshterm config` / `config set ` / `config backup` / `config reboot` / … | +| **📡 Send advert** | Announce this node to the mesh with a zero-hop advert or a flood advert. You can also share the contact card of this node as a QR code. | `meshterm config advert` / `config share` | +| **🔌 Devices** | This is not a menu screen. You select the companion on the startup splash. The splash lists the serial companions and the Bluetooth LE companions. If only one companion is present, MeshTerm selects it. You can add a network (TCP) companion with host:port. MeshTerm remembers the last good default. If the link drops, MeshTerm detects it at once and offers to reconnect. `meshterm devices` lists what discovery finds. | `meshterm devices` | -## 🗼 Other nodes — someone else's radio, over the mesh +## 🗼 Other nodes: the radio of another person, over the mesh | Feature | What it does | Scripted | | --- | --- | --- | -| **🗼 Repeater admin** | Set up remote repeaters and room servers over the mesh: log in (remembered or prompted password), then a config-style editor speaking the node's text CLI — including repeater-only knobs (TX delay, airtime factor, advert intervals) — plus one-shot actions and a readline remote command line. A **Regions** page edits the repeater's region tree: which regions it relays floods for, whether it relays unscoped floods, its home and default scope. | `meshterm repeater-admin ` | -| **🔖 Regions** | Not a menu screen. A repeater's node page lists the regions it carries and asks it once, without logging in (it answers only a neighbour, or over a known route); the packet viewer, message paths and live feed name the region a flood was scoped to. | `meshterm regions ` | -| **📶 TX optimize** | Sweep a remote node's transmit power live — coarse, then refine, then verify — watch each level land, and decide whether to apply the winner. The scripted form applies the winner unless told not to. | `meshterm tx-optimize --path … [--no-apply]` | +| **🗼 Repeater admin** | Set up remote repeaters and room servers over the mesh. First, log in with a remembered password or a password that MeshTerm asks for. Then use an editor in the style of Device config, which speaks the text CLI of the node. It includes the settings that only repeaters have (TX delay, airtime factor, and advert intervals). It also has one-shot actions and a remote command line with readline. A **Regions** page edits the region tree of the repeater: the regions for which it relays floods, and whether it relays unscoped floods. It also edits the home scope and the default scope. | `meshterm repeater-admin ` | +| **🔖 Regions** | This is not a menu screen. The node page of a repeater lists the regions that the repeater carries. MeshTerm asks the repeater one time, without a login. The repeater answers only a neighbour, or a request over a known route. The packet viewer, the message paths, and the live feed show the region of a scoped flood. | `meshterm regions ` | +| **📶 TX optimize** | Sweep the transmit power of a remote node while you watch. The sweep has three steps: coarse, then refine, then verify. You see each level as it lands, and you decide whether to apply the best level. The scripted form applies the best level unless you tell it not to. | `meshterm tx-optimize --path … [--no-apply]` | -## ⚙ This app — the program in front of you +## ⚙ This app: the program in front of you -The last of the three, and the one nothing on the mesh can answer. Preferences leads it because -it is the only row here that *changes* MeshTerm rather than describing it; the written -pages close it. +This is the last of the three sections. Nothing on the mesh can answer for it. Preferences +is the first row because it is the only row here that changes MeshTerm. The other rows +only describe it. The written pages are at the end. | Feature | What it does | Scripted | | --- | --- | --- | -| **⚙ Preferences** | How MeshTerm behaves, in eight groups: Device, Sending, TX optimize, Watchtower, Map, History, Display, and Diagnostics. Grouped and staged, saved by one action at the bottom, and written to `preferences.toml` as only the values you changed. [Configuring MeshTerm](configuration.md) has the detail. | `meshterm preferences show / set / reset` | -| **🩺 Diagnostics** | Everything a bug report opens with, in one block: which build this is and how it was installed, the OS, the terminal and its size, the boot-time verdicts that show on no screen (icons, powerline, platform flavour, and why), the radio and its link, and how much history is stored. Nothing private — no keys, passwords, positions, or contact names; the mesh is described as per-table counts and the span they cover. `s` saves it beside the log for attaching. | `meshterm diagnostics [--out PATH]` | -| **📖 About MeshTerm** | What MeshTerm is, where it came from, and the terms it ships under — including the map and font credits and the licence URL. | `meshterm about` | -| **👤 About the author** | The person behind MeshTerm, on the mesh and off it. | `meshterm about-author` | -| **🔗 Join Discord** | The community server: one invite link, and a QR of it for a phone to read. | `meshterm discord` | -| **💰 Support MeshTerm** | What keeps MeshTerm going, and the ways — paid and unpaid — to help it along. | `meshterm support` | +| **⚙ Preferences** | How MeshTerm behaves, in eight groups: Device, Sending, TX optimize, Watchtower, Map, History, Display, and Diagnostics. The page stages your changes, and you save them with one action at the bottom. MeshTerm writes only the values that you changed to `preferences.toml`. [Configuring MeshTerm](configuration.md) has the detail. | `meshterm preferences show / set / reset` | +| **🩺 Diagnostics** | One block of information that each bug report starts with. It shows which build you have and how you installed it, the operating system, the terminal and its size, and the radio and its link. It also shows the verdicts from boot time that no other screen shows (icons, powerline, platform flavour, and the reasons), and how much history MeshTerm stored. It has nothing private: no keys, passwords, positions, or contact names. It describes the mesh only as counts for each table and the span of time that the tables cover. Press `s` to save the block beside the log, to attach to a report. | `meshterm diagnostics [--out PATH]` | +| **📖 About MeshTerm** | What MeshTerm is, where it came from, and the terms of its licence. It includes the map credit, the font credit, and the licence URL. | `meshterm about` | +| **👤 About the author** | The person behind MeshTerm, on the mesh and away from it. | `meshterm about-author` | +| **🔗 Join Discord** | The community server. The page has one invite link and a QR code of the link, which a phone can read. | `meshterm discord` | +| **💰 Support MeshTerm** | What keeps MeshTerm going, and the ways to help it, with money and without money. | `meshterm support` | ## Leaving MeshTerm -**Quit** at the bottom of the main menu leaves, after asking. **Ctrl+Q** asks the same -question from any screen, and pressing it again while the question is up leaves at once. +**Quit** at the bottom of the main menu leaves MeshTerm, after it asks you to confirm. +**Ctrl+Q** asks the same question from any screen. If you press it again while the question +is open, MeshTerm leaves at once. -You can also hold **Esc**. After a second, a box says *Hold Esc to quit*, with a bar that -fills over the next two seconds. When it's full, MeshTerm closes, the same way Quit closes -it. Let go before then and you're back where you were. While the box is up, MeshTerm -doesn't start sending anything new over the radio, so a message that's going out finishes -first. A quick press of **Esc** still goes back a screen, as always. +You can also hold **Esc**. After one second, a box shows "Hold Esc to quit". A bar in the +box fills during the next two seconds. When the bar is full, MeshTerm closes, in the same +way as with Quit. If you let go before the bar is full, you return to where you were. +While the box is open, MeshTerm does not start to send anything new over the radio. A +message that is already going out finishes first. A quick press of **Esc** still goes back +one screen, as always. -Holding Esc only works where MeshTerm can tell that a key is being held down, and a -terminal never says when a key comes back up. These can tell: +Holding Esc works only where MeshTerm can tell that a key is down. A terminal does not +tell a program when a key comes up. MeshTerm can tell on these systems: - Windows. -- Linux, where you can read your keyboard's input device. That includes the PicoCalc, and - a uConsole or any other Linux computer where your user is in the `input` group. +- Linux, where MeshTerm can read the input device of your keyboard. This includes the + PicoCalc, and a uConsole or other Linux computer where your user is in the `input` group. - The Cardputer Zero, and the [emulator](../devices/cardputer-zero.md#trying-it-in-the-emulator) window for either handheld. diff --git a/docs/guide/terminals.md b/docs/guide/terminals.md index 58b25c6..26db79a 100644 --- a/docs/guide/terminals.md +++ b/docs/guide/terminals.md @@ -1,37 +1,42 @@ # Terminals, icons, and the Windows console -Why MeshTerm looks right in some terminals and draws empty boxes in others, what it does -about it on Windows, and how to turn all of that off. +This page explains why MeshTerm looks correct in some terminals and draws empty boxes in +others. It also tells what MeshTerm does about this on Windows, and how to turn that off. -MeshTerm is drawn with emoji icons, braille charts, and powerline path chips. Whether you -see them is up to your *terminal*, not really your font: a modern terminal, asked for a -character its font doesn't have, quietly borrows it from another font on the machine. -That's why the app looks right in Windows Terminal, in VS Code's terminal, and on macOS -and Linux — usually with a font that contains almost none of it. The exception is macOS's -Terminal app, where the charts may draw misaligned: no Mac monospace font has braille, so -it is borrowed from a proportional one at the wrong width. +MeshTerm draws emoji icons, braille charts, and powerline path chips. Your terminal decides +if you see them. The font is less important. When a modern terminal must draw a character +that its font does not have, it takes the character from another font on the machine. Thus +the app looks correct in Windows Terminal, in the terminal of VS Code, and on macOS and +Linux. This is usually true also when the font has almost none of these characters. -The classic Windows console — the black `cmd.exe` window a double-click opens on -Windows 10 — doesn't borrow. (Windows 11, 22H2 and later, opens Windows Terminal by -default instead.) It draws what its one font holds and empty boxes for everything else, and -no font fixes the icons there: the only two fonts on a Windows machine with emoji in them -are proportional, and a console won't take a proportional font. +The Terminal app of macOS is the exception. The charts can be misaligned there. No Mac +monospace font has braille, so the terminal takes it from a proportional font, at the wrong +width. -So when MeshTerm lands in that console it just **moves to Windows Terminal** — it says -so, and opens there. Nothing is installed and nothing is changed; it's one window instead -of another, and it's the whole app exactly as the screenshots show it. Windows Terminal is -already on every Windows 11 machine and is a free install on Windows 10. +The classic Windows console does not take characters from other fonts. This is the black +`cmd.exe` window that a double-click opens on Windows 10. (Windows 11, version 22H2 and +later, opens Windows Terminal by default.) The console draws what its one font has, and +empty boxes for all other characters. No font can correct the icons there. Only two fonts +on a Windows machine have emoji, and both are proportional. A console does not accept a +proportional font. -Where there's no Windows Terminal to move to, and the console's font can't already draw -the charts, MeshTerm offers the next best thing instead — the charts and marks, without -the icons. It ships -[Cascadia Mono PL](https://github.com/microsoft/cascadia-code), Microsoft's own console -font and one of the very few monospace faces that carries braille at all, and will install -it just for you: no administrator rights, nothing downloaded. If you already have a font -that can draw the charts (Cascadia, or another such as Iosevka), it offers to switch to -that one instead. Either way it asks first (`[y/N]`), and the answer defaults to no. +When MeshTerm starts in this console, it **moves to Windows Terminal**. It tells you, and +it opens the app there. MeshTerm does not install or change anything. You get one window +instead of another, and the whole app looks exactly as the screenshots show. Windows +Terminal is already on each Windows 11 machine. On Windows 10 it is a free install. -Preferences → Display → Console setup turns all of this off if you'd rather stay put. +Sometimes there is no Windows Terminal to move to, and the font of the console cannot +draw the charts. Then MeshTerm offers the next best choice: the charts and marks, without +the icons. MeshTerm includes +[Cascadia Mono PL](https://github.com/microsoft/cascadia-code), the console font of +Microsoft. It is one of the very few monospace fonts that have braille. MeshTerm can +install it for your user account only. This needs no administrator rights and no download. If you +already have a font that can draw the charts (Cascadia, or another such as Iosevka), +MeshTerm offers to switch to that font instead. In each case, MeshTerm asks first +(`[y/N]`), and the default answer is no. -Where MeshTerm keeps its state, and how to point a trial run somewhere else, is in -[Configuring MeshTerm](configuration.md). +To keep the console that you have, turn all of this off. Go to Preferences → Display → +Console setup. + +[Configuring MeshTerm](configuration.md) tells where MeshTerm keeps its state, and how to +point a trial run to another place.