variants/linux: local CLI console over a PTY - #25
Closed
rgrizzell wants to merge 17 commits into
Closed
Conversation
* variants: add scaffolding for linux native * address review comments * address review comments
…dev#2) * variants: allow linux repeater to be configured at runtime * address review comments * address review comments * address review comments
* replace portduino with ardulinux * replace portduino with ardulinux * replace portduino with ardulinux * Remove flags now owned by the ardulinux platform framework ARDULINUX_LINUX_HARDWARE, -lgpiod, and -li2c are detected and injected by builder/frameworks/arduino.py via pkg-config. Hardcoding them here caused linker failures on machines without libgpiod even though the framework would have gracefully omitted them. Also switch variants/ardulinux to the git+ platform URL (dropping the platform-native + platform_packages indirection), update the linux variant board name, and add arduino to the frameworks list in linux.json. * address review comments * Remove stale portduino branding note The startup string was already fixed in l5yth/ardulinux — main.cpp says "An application written with ardulinux". Remove the pending-fix note and update the description to match the current behaviour. * replace portduino with ardulinux * Wire up ardulinux platform and fix SPI/VFS/printf for Linux target variants/ardulinux/platformio.ini: revert local symlink:// back to git+ URL — the symlink only works in a co-located checkout and would break CI. variants/linux/LinuxBoard.cpp: - Add empty ardulinuxSetup() to satisfy the weak symbol; without it the default prints a noisy "No ardulinuxSetup() found" message on startup. - Replace Serial.printf with printf — Serial.printf is not available until after Serial.begin(); using stdio printf is safe at this init-time call site. - Pass 2MHz frequency to SPI.begin() to match the expected SPI clock. variants/linux/target.cpp: fix spiTransfer — ArduLinux's SPI only has a 2-arg transfer(buf, len) that operates in-place; copy out→in first then call the 2-arg form. examples/simple_repeater/main.cpp: fix arduLinuxVFS → ardulinuxVFS (case was wrong; symbol is defined as ardulinuxVFS in ArduLinuxFS.cpp). * set app info to meshcored * fix linux sx1262 wrapper * address review: fix GPIO hardware guard, document spiTransfer, use printf * docs: fix linux variant README (binary name, deps, SPI setup, config keys; use install(1))
meshcore-dev#8) * variants/linux: on-hardware smoke-test fixes (rx_boost, dio2, --fsdir, docs) * address review comments
* proofread README * proofread README
* variants/linux: use linux_base for platformio * variants/linux: use linux_base for platformio
* pin ardulinux to wire-fix branch, document pkg-config * pin ardulinux 0.2.1
…meshcore-dev#14) * variants/linux: fail loud when libgpiod is missing or pin claim fails * variants/linux: exit when configured GPIO pins fail to bind
…re-dev#15) * variants/linux: make lora_gpiochip configurable, refresh docs * ci: variants/linux: add lora_gpiochip hint to base meshcored.ini too * bump ardulinux to 0.2.2
…shcore-dev#24) `mesh::MainBoard::sleep()` is a no-op by default and `LinuxBoard` never overrode it, so `board.sleep(0)`/`sleep(30)` returned immediately and the repeater loop spun a core at 100%. Only `NRF52Board` and `ESP32Board` implement it. Implement it with `sleep()`/`usleep()`, and add a `delay(1)` in the main loop's non-powersaving branch so platforms without power management do not spin either. Reported and fixed by brianhealey, measured at 99% -> 0-9% CPU on a Pi Compute Module 5 with an SX1262. Cherry-picked from l5yth#21, which could not be merged. (cherry picked from commit 11d6ddf) Co-authored-by: brianhealey <brian.healey@gmail.com>
The linux repeater's CLI reads Serial, but on ardulinux Serial is a
stdout-only stub, so the CLI was unreachable — the node could only be
configured over the mesh, with no way to fix a bad radio preset locally
without a second node.
Add a console on a pseudo-terminal (PTY), in MeshCore application code
(not the ardulinux platform layer, so it survives a platform swap) with
logs and the CLI on separate streams. A PTY rather than a socket so
meshcore-cli's repeater mode can attach directly:
meshcore-cli -r -s /run/meshcored/console
(meshcore-cli -r drives a raw-text serial CLI via pyserial, which needs
a tty.)
- src/helpers/PtyConsole.{h,cpp}: pure-POSIX PTY engine (no Arduino
dependency, so it is host-unit-testable). posix_openpt + a stable
symlink to /dev/pts/N; raw termios; maps '\n'->'\r' (1:1) so tools that
send newline work with the CR-terminated CLI. The pts device is chmod
0600 -- the unauthenticated local CLI's access gate. The master
persists across client attach/detach, so there is no accept/reap and a
write to a closed peer returns EIO (never SIGPIPE).
- src/helpers/LinuxConsole.h: thin Arduino Stream adapter; write()
mirrors to stdout (so commands/replies reach journald) and the PTY.
- variants/linux: LinuxConfig.console_path INI key (default: a per-user
path); systemd unit + README document /run/meshcored/console and
meshcore-cli. User docs avoid the PTY/serial internals.
- examples/simple_repeater: route the CLI loop to the console stream;
logs (MESH_DEBUG) and the boot banner stay on Serial. MCU targets keep
console == &Serial, so their behavior is unchanged.
- test/test_console: googletest for the engine (client I/O, newline map,
peek, reconnect, write-after-close, 0600 char-device perms). Not wired
into [env:native] to avoid disturbing CI; run via a local override.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Sep 7, 2026
mmmorks
added a commit
to mmmorks/meshcore-linux
that referenced
this pull request
Sep 7, 2026
Builds on l5yth#25's PTY console (PtyConsole/LinuxConsole) and keeps its shape: a pseudo-terminal published as a stable symlink, so meshcore-cli -r -s and any serial tool attach directly, and the CLI's traffic mirrored to stdout so journald records what was run. Four things change underneath it. The daemon holds a descriptor of its own on the slave. Without one the master reports POLLHUP and read() fails with EIO from the moment the last client closes until the next one opens -- a level condition the poll() loop introduced in the previous commit could only throttle, not clear, so one detached client cost a wake-up per millisecond for the rest of the run. With it a detached console is idle: poll() sleeps, read() says "nothing yet", and the next client attaches as if the first had never left. The master and holder are registered with the event loop as permanent descriptors, so the daemon wakes on console input. Every descriptor is close-on-exec and reboot() unpublishes the console before re-exec. An inherited master would keep the old /dev/pts/N alive with nobody reading it; a symlink left behind would point at a number the exec has just freed, which the next image can only guess about. begin() now tries candidates in order -- console_path, /run/meshcored/console when the unit's RuntimeDirectory is present (so the packaged service needs no INI edit), $XDG_RUNTIME_DIR/meshcore/console, /tmp/meshcore-<uid>/console -- and declines one that a live console holds (its symlink resolves to an existing device) or that is not a symlink at all, since the path is operator input and unlink() does not care what it removes. A dangling symlink from a crashed daemon is reclaimed. stdin is a second door onto the same CLI when meshcored runs in a terminal: raw, non-blocking, no kernel echo (the CLI echoes), restored on end(). Replies follow the command to its source -- always to stdout, and to the PTY only when the command arrived there -- so a foreground session's output does not queue in the PTY to greet the next client. The PTY engine joins the native test build under PIO_UNIT_TESTING, which is what l5yth#25 wanted and reverted: the "SIGHUP" its author saw is PlatformIO's runner mapping a plain exit status 1 (a failed assertion) to signal 1. The suite covers the symlink and its mode, both directions and the newline map, the holder (a detached client leaves poll() sleeping and the event loop timing out cleanly), close-on-exec for every descriptor begin() opens, the live/stale/non-symlink candidate rules, the XDG default, stdin routing with a real terminal on fd 0, and end()/begin() across a simulated reboot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EXSCjgNEbJfHwLjD2WSHW4
l5yth
pushed a commit
that referenced
this pull request
Sep 9, 2026
The linux repeater's CLI reads Serial, but on ardulinux Serial is a
stdout-only stub, so the CLI was unreachable — the node could only be
configured over the mesh, with no way to fix a bad radio preset locally
without a second node.
Add a console on a pseudo-terminal (PTY), in MeshCore application code
(not the ardulinux platform layer, so it survives a platform swap) with
logs and the CLI on separate streams. A PTY rather than a socket so
meshcore-cli's repeater mode can attach directly:
meshcore-cli -r -s /run/meshcored/console
(meshcore-cli -r drives a raw-text serial CLI via pyserial, which needs
a tty.)
- src/helpers/PtyConsole.{h,cpp}: pure-POSIX PTY engine (no Arduino
dependency, so it is host-unit-testable). posix_openpt + a stable
symlink to /dev/pts/N; raw termios; maps '\n'->'\r' (1:1) so tools that
send newline work with the CR-terminated CLI. The pts device is chmod
0600 -- the unauthenticated local CLI's access gate. The master
persists across client attach/detach, so there is no accept/reap and a
write to a closed peer returns EIO (never SIGPIPE).
- src/helpers/LinuxConsole.h: thin Arduino Stream adapter; write()
mirrors to stdout (so commands/replies reach journald) and the PTY.
- variants/linux: LinuxConfig.console_path INI key (default: a per-user
path); systemd unit + README document /run/meshcored/console and
meshcore-cli. User docs avoid the PTY/serial internals.
- examples/simple_repeater: route the CLI loop to the console stream;
logs (MESH_DEBUG) and the boot banner stay on Serial. MCU targets keep
console == &Serial, so their behavior is unchanged.
- platformio.ini [env:native]: define MESHCORE_HOST_TEST and add
PtyConsole.cpp to build_src_filter. PtyConsole is compiled out without
the macro and the suite does not link without the source, so both are
required for CI to build test_console at all.
- test/test_console: 18 googletest cases for the engine. Client I/O,
newline mapping, peek, reconnect, write-after-close and the 0600
char-device perms; begin() idempotency; default link resolution via
XDG_RUNTIME_DIR and the /tmp/meshcore-<uid> fallback; path() falling
back to the pts device when the symlink cannot be published;
available() accounting for a peeked byte alongside the queue; end()
idempotency and post-close inertness; destructor cleanup. All three
begin() failure paths are exercised: posix_openpt via RLIMIT_NOFILE,
and grantpt/ptsname_r (unreachable once posix_openpt has succeeded)
via strong definitions that forward to libc unless a test arms them.
Refs #25
Owner
|
I rebased and merged this in da0e692 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds a console on a pseudo-terminal (PTY) for configuring Repeaters. Ardulinux Serial is a stdout-only stub, so the CLI was unreachable — the node could only be configured over the mesh or via config file (if the option was supported). A PTY is used rather than a socket so that
meshcore-clican be used in repeater mode:meshcore-cli -r -s $/meshcored/consoleThe
console_pathcan be set viameshcore.ini. If unset, it defaults to$XDG_RUNTIME_DIR/meshcore/consoleor/tmp/meshcore-<uid>/console.If
meshcore-cliis not installed,screenandminicomwill work too. This can be useful for devices that don't have enough RAM to support Python.Commands and replies are logged to
stdout.