Skip to content

Commit bb86cf4

Browse files
committed
Merge remote-tracking branch 'origin/master' into develop
2 parents 6ea0d5e + 41f5317 commit bb86cf4

21 files changed

Lines changed: 1068 additions & 78 deletions

File tree

.github/copilot-instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,8 @@ Grouped by purpose. Full argument shapes in `mcp-server/README.md`; a few high-v
575575

576576
`confirm=True` is a tool-level gate on top of whatever permission prompt your MCP host shows. **Don't bypass it** by asking the host to auto-approve — it exists specifically because MCP hosts sometimes remember "always allow this tool" and that's dangerous for `factory_reset`, `erase_and_flash`, `uhubctl_power(action='off')`, and `uhubctl_cycle`.
577577

578+
**TCP / native-host nodes.** Setting `MESHTASTIC_MCP_TCP_HOST=<host[:port]>` makes `list_devices` surface a `meshtasticd` daemon (e.g. the `native-macos` build) as a synthetic `tcp://host:port` entry, and `connect()` routes through `meshtastic.tcp_interface.TCPInterface` instead of `SerialInterface`. Every read/write/admin tool that flows through `connect()` works against the daemon transparently. USB-only tools (`pio_flash`, `erase_and_flash`, `update_flash`, `touch_1200bps`, `serial_open`, `esptool_*`, `nrfutil_*`, `picotool_*`) raise a clear `ConnectionError` when handed a `tcp://` port; `pio_flash` against a `native*` env raises a `FlashError` (no upload step — use `build` and run the binary directly). The pytest harness still assumes USB-attached devices per role; TCP-aware fixtures are deferred. See `mcp-server/README.md` § "TCP / native-host nodes".
579+
578580
### Hardware test suite (`mcp-server/run-tests.sh`)
579581

580582
The wrapper auto-detects connected devices (VID → role map: `0x239A``nrf52`, `0x303A`/`0x10C4``esp32s3`), maps each role to a PlatformIO env (`nrf52``rak4631`, `esp32s3``heltec-v3`, overridable via `MESHTASTIC_MCP_ENV_<ROLE>`), then invokes pytest. Zero pre-flight config needed from the operator.

.github/workflows/build_debian_src.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@ jobs:
3232
shell: bash
3333
working-directory: meshtasticd
3434
run: |
35+
# Build-tools (notably platformio) come from the Meshtastic project
36+
# on the OpenSUSE Build Service:
37+
# https://build.opensuse.org/project/show/network:Meshtastic:build-tools
38+
echo 'deb http://download.opensuse.org/repositories/network:/Meshtastic:/build-tools/xUbuntu_24.04/ /' \
39+
| sudo tee /etc/apt/sources.list.d/network:Meshtastic:build-tools.list
40+
curl -fsSL https://download.opensuse.org/repositories/network:Meshtastic:build-tools/xUbuntu_24.04/Release.key \
41+
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/network_Meshtastic_build-tools.gpg >/dev/null
3542
sudo apt-get update -y --fix-missing
36-
sudo apt-get install -y software-properties-common build-essential devscripts equivs
37-
sudo add-apt-repository ppa:meshtastic/build-tools -y
38-
sudo apt-get update -y --fix-missing
43+
sudo apt-get install -y build-essential devscripts equivs
3944
sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
4045
4146
- name: Import GPG key

.github/workflows/build_macos_bin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
shell: bash
2525
run: |
2626
brew update
27-
brew install platformio yaml-cpp libuv openssl@3 libusb argp-standalone pkg-config
27+
brew install platformio yaml-cpp libuv openssl@3 libusb argp-standalone pkg-config ulfius
2828
2929
- name: Get release version string
3030
run: |

.trunk/trunk.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ plugins:
88
uri: https://github.com/trunk-io/plugins
99
lint:
1010
enabled:
11-
- checkov@3.2.525
11+
- checkov@3.2.526
1212
- renovate@43.150.0
1313
- prettier@3.8.3
1414
- trufflehog@3.95.2

AGENTS.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,17 @@ Sequence these; don't parallelize on the same port.
126126

127127
## Environment variables (test harness)
128128

129-
| Var | Purpose |
130-
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
131-
| `MESHTASTIC_MCP_ENV_<ROLE>` | Override PlatformIO env for a role (e.g. `MESHTASTIC_MCP_ENV_NRF52=rak4631-dap`). Default map: `nrf52→rak4631`, `esp32s3→heltec-v3`. |
132-
| `MESHTASTIC_MCP_SEED` | PSK seed for the session test profile. Defaults to `mcp-<user>-<host>`. |
133-
| `MESHTASTIC_MCP_FLASH_LOG` | File path to tee pio/esptool/nrfutil/picotool output. `run-tests.sh` sets this to `tests/flash.log` so the TUI can stream live flash progress. |
134-
| `MESHTASTIC_UHUBCTL_BIN` | Absolute path to `uhubctl` binary. Default: PATH lookup. |
135-
| `MESHTASTIC_UHUBCTL_LOCATION_<ROLE>` | Pin a role to a specific uhubctl hub location (e.g. `1-1.3`). Wins over VID auto-detection — use when multiple devices share a VID. |
136-
| `MESHTASTIC_UHUBCTL_PORT_<ROLE>` | Pin a role to a specific hub port number. Required alongside `LOCATION_<ROLE>`. |
137-
| `MESHTASTIC_UI_CAMERA_BACKEND` | Camera backend for UI tier + `capture_screen` tool: `opencv` / `ffmpeg` / `null` / `auto` (default). |
138-
| `MESHTASTIC_UI_CAMERA_DEVICE` | Generic camera device (index or path). Used by the UI tier when no per-role var is set. |
139-
| `MESHTASTIC_UI_CAMERA_DEVICE_<ROLE>` | Per-role camera pinning (e.g. `MESHTASTIC_UI_CAMERA_DEVICE_ESP32S3=0` for the OLED-bearing heltec-v3). |
140-
| `MESHTASTIC_UI_OCR_BACKEND` | OCR engine selection: `easyocr` / `pytesseract` / `null` / `auto` (default). |
141-
| `MESHTASTIC_UI_TUI_CAMERA` | Set to `1` to mount the live camera-feed panel in `meshtastic-mcp-test-tui`. |
129+
| Var | Purpose |
130+
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131+
| `MESHTASTIC_MCP_ENV_<ROLE>` | Override PlatformIO env for a role (e.g. `MESHTASTIC_MCP_ENV_NRF52=rak4631-dap`). Default map: `nrf52→rak4631`, `esp32s3→heltec-v3`. |
132+
| `MESHTASTIC_MCP_SEED` | PSK seed for the session test profile. Defaults to `mcp-<user>-<host>`. |
133+
| `MESHTASTIC_MCP_FLASH_LOG` | File path to tee pio/esptool/nrfutil/picotool output. `run-tests.sh` sets this to `tests/flash.log` so the TUI can stream live flash progress. |
134+
| `MESHTASTIC_MCP_TCP_HOST` | `host` or `host:port` of a `meshtasticd` daemon (e.g. the `native-macos` build). Surfaces it in `list_devices` as `tcp://host:port` so `connect()`-based tools target it transparently. Default port 4403. |
135+
| `MESHTASTIC_UHUBCTL_BIN` | Absolute path to `uhubctl` binary. Default: PATH lookup. |
136+
| `MESHTASTIC_UHUBCTL_LOCATION_<ROLE>` | Pin a role to a specific uhubctl hub location (e.g. `1-1.3`). Wins over VID auto-detection — use when multiple devices share a VID. |
137+
| `MESHTASTIC_UHUBCTL_PORT_<ROLE>` | Pin a role to a specific hub port number. Required alongside `LOCATION_<ROLE>`. |
138+
| `MESHTASTIC_UI_CAMERA_BACKEND` | Camera backend for UI tier + `capture_screen` tool: `opencv` / `ffmpeg` / `null` / `auto` (default). |
139+
| `MESHTASTIC_UI_CAMERA_DEVICE` | Generic camera device (index or path). Used by the UI tier when no per-role var is set. |
140+
| `MESHTASTIC_UI_CAMERA_DEVICE_<ROLE>` | Per-role camera pinning (e.g. `MESHTASTIC_UI_CAMERA_DEVICE_ESP32S3=0` for the OLED-bearing heltec-v3). |
141+
| `MESHTASTIC_UI_OCR_BACKEND` | OCR engine selection: `easyocr` / `pytesseract` / `null` / `auto` (default). |
142+
| `MESHTASTIC_UI_TUI_CAMERA` | Set to `1` to mount the live camera-feed panel in `meshtastic-mcp-test-tui`. |

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
44
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
55

6-
FROM python:3.14-slim-trixie AS builder
6+
FROM debian:trixie AS builder
77
ARG PIO_ENV=native
88
ENV DEBIAN_FRONTEND=noninteractive
99
ENV TZ=Etc/UTC
1010

1111
# Install Dependencies
1212
ENV PIP_ROOT_USER_ACTION=ignore
13+
ENV PIP_BREAK_SYSTEM_PACKAGES=1
1314
RUN apt-get update && apt-get install --no-install-recommends -y \
1415
curl wget g++ zip git ca-certificates pkg-config \
16+
python3-pip python3-grpc-tools \
1517
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
1618
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
1719
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \

alpine.Dockerfile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
55

66
# Ensure the Alpine version is updated in both stages of the container!
7-
FROM python:3.14-alpine3.23 AS builder
7+
FROM alpine:3.23 AS builder
88
ARG PIO_ENV=native
9-
ENV PIP_ROOT_USER_ACTION=ignore
109

10+
# Enable Alpine community repository (for 'py3-grpcio-tools')
11+
RUN echo "https://dl-cdn.alpinelinux.org/alpine/v$(cut -d. -f1,2 /etc/alpine-release)/community" >> /etc/apk/repositories
12+
13+
# Install Dependencies
14+
ENV PIP_ROOT_USER_ACTION=ignore
15+
ENV PIP_BREAK_SYSTEM_PACKAGES=1
1116
RUN apk --no-cache add \
1217
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
18+
py3-pip py3-grpcio-tools \
1319
libgpiod-dev yaml-cpp-dev bluez-dev \
1420
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
1521
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \

mcp-server/README.md

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,73 @@ rather than auto-`sudo`'ing mid-run.
166166

167167
## Environment variables
168168

169-
| Var | Default | Purpose |
170-
| -------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- |
171-
| `MESHTASTIC_FIRMWARE_ROOT` | walks up from cwd for `platformio.ini` | Pin the firmware repo |
172-
| `MESHTASTIC_PIO_BIN` | `~/.platformio/penv/bin/pio``$PATH` `pio``platformio` | Override `pio` location |
173-
| `MESHTASTIC_ESPTOOL_BIN` | `<firmware>/.venv/bin/esptool``$PATH` | Override esptool |
174-
| `MESHTASTIC_NRFUTIL_BIN` | `$PATH` | Override nrfutil |
175-
| `MESHTASTIC_PICOTOOL_BIN` | `$PATH` | Override picotool |
176-
| `MESHTASTIC_MCP_SEED` | `mcp-<user>-<host>` | PSK seed for test-harness session (CI override) |
177-
| `MESHTASTIC_MCP_FLASH_LOG` | `<mcp-server>/tests/flash.log` | Tee target for pio/esptool/nrfutil subprocess output (TUI tails it) |
169+
| Var | Default | Purpose |
170+
| -------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
171+
| `MESHTASTIC_FIRMWARE_ROOT` | walks up from cwd for `platformio.ini` | Pin the firmware repo |
172+
| `MESHTASTIC_PIO_BIN` | `~/.platformio/penv/bin/pio``$PATH` `pio``platformio` | Override `pio` location |
173+
| `MESHTASTIC_ESPTOOL_BIN` | `<firmware>/.venv/bin/esptool``$PATH` | Override esptool |
174+
| `MESHTASTIC_NRFUTIL_BIN` | `$PATH` | Override nrfutil |
175+
| `MESHTASTIC_PICOTOOL_BIN` | `$PATH` | Override picotool |
176+
| `MESHTASTIC_MCP_SEED` | `mcp-<user>-<host>` | PSK seed for test-harness session (CI override) |
177+
| `MESHTASTIC_MCP_FLASH_LOG` | `<mcp-server>/tests/flash.log` | Tee target for pio/esptool/nrfutil subprocess output (TUI tails it) |
178+
| `MESHTASTIC_MCP_TCP_HOST` | unset | `host` or `host:port` of a `meshtasticd` daemon to surface as a TCP device (see "TCP / native-host nodes" below) |
179+
180+
## TCP / native-host nodes
181+
182+
The `native-macos` and `native` PlatformIO envs build a headless `meshtasticd`
183+
binary that runs on the host (Apple Silicon / Intel macOS, or Linux Portduino).
184+
The daemon exposes the meshtastic TCP API on port `4403` rather than a USB
185+
serial endpoint — point the MCP server at it via `MESHTASTIC_MCP_TCP_HOST`:
186+
187+
```bash
188+
# 1. Build + run a daemon on this host (see variants/native/portduino/platformio.ini
189+
# for full Homebrew prereqs and CH341 LoRa-adapter setup).
190+
pio run -e native-macos
191+
~/.meshtasticd/meshtasticd
192+
193+
# 2. Point the MCP server at it.
194+
export MESHTASTIC_MCP_TCP_HOST=localhost # or host:port, default port 4403
195+
```
196+
197+
**First-run gotcha — MAC address.** `meshtasticd` derives its MAC from the
198+
USB adapter's serial-number / product strings. Many cheap CH341 dongles
199+
(MeshStick included — VID 0x1A86 / PID 0x5512) ship with `iSerialNumber=0`
200+
and `iProduct=0`, so the daemon aborts on boot with `*** Blank MAC Address
201+
not allowed!`. Set the MAC explicitly in `config.yaml`:
202+
203+
```yaml
204+
# Under General:
205+
MACAddress: 02:CA:FE:BA:BE:01
206+
```
207+
208+
Use a locally-administered address (first byte's second-LSB set, e.g.
209+
`02:*` / `06:*` / `0A:*` / `0E:*`) to avoid colliding with a real OUI.
210+
211+
There is also a `--hwid AA:BB:CC:DD:EE:FF` CLI flag visible in
212+
`meshtasticd --help`, but it is **currently broken** in
213+
`MAC_from_string()` (`src/platform/portduino/PortduinoGlue.cpp`): the
214+
function strips colons from its parameter but then reads bytes from the
215+
global `portduino_config.mac_address`, so `--hwid` is silently overridden
216+
when `MACAddress:` is also set, and crashes the daemon (uncaught
217+
`std::invalid_argument: stoi: no conversion`) when it isn't. Use the YAML
218+
form until that's fixed upstream.
219+
220+
`list_devices` will surface the daemon as `tcp://localhost:4403` with
221+
`likely_meshtastic=True`, so `device_info`, `list_nodes`, `get_config`,
222+
`set_config`, `set_owner`, `send_text`, `userprefs_*`, and the admin RPCs
223+
auto-select it when no `port` is passed. Pass `port="tcp://other-host:9999"`
224+
explicitly to target a different daemon.
225+
226+
**Tools that don't apply to a TCP/native node** (no USB hardware to operate
227+
on) raise a clear `ConnectionError` rather than failing mysteriously:
228+
`pio_flash`, `erase_and_flash`, `update_flash`, `touch_1200bps`,
229+
`serial_open` (use info/admin tools directly), and the vendor escape hatches
230+
`esptool_*`, `nrfutil_*`, `picotool_*`. `pio_flash` against a `native*` env
231+
similarly raises — there's no upload step; use `build` and run the binary
232+
directly.
233+
234+
The pytest harness in `tests/` still assumes USB-attached devices per role —
235+
TCP-aware fixtures are not part of this surface yet.
178236

179237
## Hardware Test Suite
180238

0 commit comments

Comments
 (0)