Backend server + browser interface for MeshCore mesh radio networks.
This fork is focused on keeping the main project working on Raspberry Pi, including all supported transports:
- Raspberry Pi + LoRa HAT via SPI (no external MeshCore device)
- Raspberry Pi + MeshCore over USB serial
- Raspberry Pi + MeshCore over TCP
- Raspberry Pi + MeshCore over BLE
Connect your radio, and then you can:
- Send and receive DMs and channel messages
- Cache all received packets, decrypting as you gain keys
- Run multiple Python bots that can analyze messages and respond to DMs and channels
- Monitor unlimited contacts and channels (radio limits don't apply -- packets are decrypted server-side)
- Access your radio remotely over your network or VPN
- Search for hashtag room names for channels you don't have keys for yet
- Forward packets to MQTT, LetsMesh, MeshRank, SQS, Apprise, etc.
- Use the more recent 1.14 firmwares which support multibyte pathing
- Visualize the mesh as a map or node set, view repeater stats, and more!
Warning: This app is for trusted environments only. Do not put this on an untrusted network, or open it to the public. You can optionally set MESHCORE_BASIC_AUTH_USERNAME and MESHCORE_BASIC_AUTH_PASSWORD for app-wide HTTP Basic auth, but that is only a coarse gate and must be paired with HTTPS. The bots can execute arbitrary Python code which means anyone who gets access to the app can, too. To completely disable the bot system, start the server with MESHCORE_DISABLE_BOTS=true — this prevents all bot execution and blocks bot configuration changes via the API. If you need stronger access control, consider using a reverse proxy like Nginx, or extending FastAPI; full access control and user management are outside the scope of this app.
This is developed with very heavy agentic assistance -- there is no warranty of fitness for any purpose. It's been lovingly guided by an engineer with a passion for clean code and good tests, but it's still mostly LLM output, so you may find some bugs.
If extending, have your LLM read the three AGENTS.md files: ./AGENTS.md, ./frontend/AGENTS.md, and ./app/AGENTS.md.
- Python 3.10+
- Node.js LTS or current (20, 22, 24, 25) (only needed if you will build the frontend; see below)
- UV package manager:
curl -LsSf https://astral.sh/uv/install.sh | sh - Radio: MeshCore device over USB serial, TCP, or BLE (supported on Raspberry Pi too). On Raspberry Pi you can also drive a supported LoRa HAT directly over SPI (no external MeshCore device; see Running on Raspberry Pi (SPI mode))
- Optional (SQS fanout): To use Amazon SQS as a fanout destination, run
pip install boto3(or install the project with full deps; boto3 is listed inpyproject.tomlbut may be omitted in minimal installs).
To use the browser UI, the frontend must be built into frontend/dist (Quick Start and the systemd service build it). If frontend/dist is already present, Node.js is not required.
Finding your serial port (Linux / Raspberry Pi OS)
ls /dev/ttyUSB* /dev/ttyACM*The device typically appears as /dev/ttyUSB0 or /dev/ttyACM0. If it does not appear, check dmesg | tail -20 after plugging in the device.
Recommended for Raspberry Pi and Linux.
git clone https://github.com/codemonkeybr/meshcore-pi-companion.git
cd meshcore-pi-companion
# Install backend dependencies
uv sync
# Build frontend
cd frontend && npm ci && npm run build && cd ..
# Run server
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000The server auto-detects the serial port. To specify a transport manually:
# Serial (explicit port)
MESHCORE_SERIAL_PORT=/dev/ttyUSB0 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
# TCP (e.g. via wifi-enabled firmware)
MESHCORE_TCP_HOST=192.168.1.100 MESHCORE_TCP_PORT=4000 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
# BLE (address and PIN both required)
MESHCORE_BLE_ADDRESS=AA:BB:CC:DD:EE:FF MESHCORE_BLE_PIN=123456 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000Access at http://localhost:8000
Note: WebGPU cracking requires HTTPS when not on localhost. See the HTTPS section under Additional Setup.
If you're using a MeshCore device directly (not a LoRa HAT), run the native install like in Quick Start, then set exactly one transport:
# USB serial
MESHCORE_SERIAL_PORT=/dev/ttyUSB0 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
# TCP
MESHCORE_TCP_HOST=192.168.1.100 MESHCORE_TCP_PORT=4000 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
# BLE (address and PIN both required)
MESHCORE_BLE_ADDRESS=AA:BB:CC:DD:EE:FF MESHCORE_BLE_PIN=123456 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000Tip: SPI mode is selected when a
config.yaml(ordata/config.yaml) exists. If you want SPI, use the SPI section below; if you want USB/TCP/BLE, ensure no SPI config file is present.
On a Pi with a LoRa HAT (e.g. Waveshare SX1262), RemoteTerm can drive the radio over SPI, or use a USB MeshCore radio.
One-line install (recommended): paste this into your Pi terminal and follow the interactive prompts:
bash <(curl -fsSL https://raw.githubusercontent.com/codemonkeybr/meshcore-pi-companion/main/scripts/get-remoteterm.sh)This downloads the source, fetches the pre-built frontend from GitHub Releases, installs Python dependencies, and sets up a systemd service. You will be asked to choose SPI (LoRa HAT) or USB serial and configure accordingly.
Manual production install (systemd, SPI boot line, USB vs SPI, uninstall):
chmod +x scripts/manage_remoterm.sh
sudo ./scripts/manage_remoterm.shLightweight dev install (venv + .[spi] only; no systemd):
chmod +x scripts/install_remoteterm_pi.sh
./scripts/install_remoteterm_pi.sh
uv run python -m app.setup_cliStart the server (without systemd):
./scripts/run_remoterm.sh --host 0.0.0.0 --port 8000SPI config defaults to data/config.yaml (see python -m app.setup_cli --help). You can copy config.yaml.example instead; see that file for node, radio, and hardware profile options.
For deployment (troubleshooting, service, identity), see docs/PI_DEPLOYMENT.md.
uv sync
uv run uvicorn app.main:app --reload # autodetects serial port
# Or with explicit serial port
MESHCORE_SERIAL_PORT=/dev/ttyUSB0 uv run uvicorn app.main:app --reloadcd frontend
npm ci
npm run dev # Dev server at http://localhost:5173 (proxies API to :8000)
npm run build # Production build to dist/Run both the backend and npm run dev for hot-reloading frontend development.
Please test, lint, format, and quality check your code before PRing or committing. At the least, run a lint + autoformat + pyright check on the backend, and a lint + autoformat on the frontend.
Run everything at once:
./scripts/all_quality.shOr run individual checks
# python
uv run ruff check app/ tests/ --fix # lint + auto-fix
uv run ruff format app/ tests/ # format (always writes)
uv run pyright app/ # type checking
PYTHONPATH=. uv run pytest tests/ -v # backend tests
# frontend
cd frontend
npm run lint:fix # esLint + auto-fix
npm run test:run # run tests
npm run format # prettier (always writes)
npm run build # build the frontend| Variable | Default | Description |
|---|---|---|
MESHCORE_SERIAL_PORT |
(auto-detect) | Serial port path |
MESHCORE_SERIAL_BAUDRATE |
115200 | Serial baud rate |
MESHCORE_TCP_HOST |
TCP host (mutually exclusive with serial/BLE/SPI) | |
MESHCORE_TCP_PORT |
4000 | TCP port |
MESHCORE_BLE_ADDRESS |
BLE device address (mutually exclusive with serial/TCP/SPI) | |
MESHCORE_BLE_PIN |
BLE PIN (required when BLE address is set) | |
MESHCORE_CONFIG_FILE |
data/config.yaml | Path to SPI config file; when this file exists, SPI mode is used (Pi + LoRa HAT). Mutually exclusive with serial/TCP/BLE. |
MESHCORE_LOG_LEVEL |
INFO | DEBUG, INFO, WARNING, ERROR |
MESHCORE_DATABASE_PATH |
data/meshcore.db | SQLite database path |
MESHCORE_DISABLE_BOTS |
false | Disable bot system entirely (blocks execution and config) |
MESHCORE_BASIC_AUTH_USERNAME |
Optional app-wide HTTP Basic auth username; must be set together with MESHCORE_BASIC_AUTH_PASSWORD |
|
MESHCORE_BASIC_AUTH_PASSWORD |
Optional app-wide HTTP Basic auth password; must be set together with MESHCORE_BASIC_AUTH_USERNAME |
Only one transport may be active at a time (serial, TCP, BLE, or SPI). SPI is selected when the config file exists; no env vars are required for SPI beyond an optional MESHCORE_CONFIG_FILE override.
If you enable Basic Auth, protect the app with HTTPS. HTTP Basic credentials are not safe on plain HTTP.
These are intended for diagnosing or working around radios that behave oddly.
| Variable | Default | Description |
|---|---|---|
MESHCORE_ENABLE_MESSAGE_POLL_FALLBACK |
false | Run aggressive 10-second get_msg() fallback polling instead of the default hourly sanity check |
MESHCORE_FORCE_CHANNEL_SLOT_RECONFIGURE |
false | Disable channel-slot reuse and force set_channel(...) before every channel send |
By default the app relies on radio events plus MeshCore auto-fetch for incoming messages, and also runs a low-frequency hourly audit poll. That audit checks both:
- whether messages were left on the radio without reaching the app through event subscription
- whether the app's channel-slot expectations still match the radio's actual channel listing
If the audit finds a mismatch, you'll see an error in the application UI and your logs. If you see that warning, or if messages on the radio never show up in the app, try MESHCORE_ENABLE_MESSAGE_POLL_FALLBACK=true to switch that task into a more aggressive 10-second safety net. If room sends appear to be using the wrong channel slot or another client is changing slots underneath this app, try MESHCORE_FORCE_CHANNEL_SLOT_RECONFIGURE=true to force the radio to validate the channel slot is valid before sending (will delay sending by ~500ms).
HTTPS (Required for WebGPU room-finding outside localhost)
WebGPU requires a secure context. When not on localhost, serve over HTTPS:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --ssl-keyfile=key.pem --ssl-certfile=cert.pemAccept the browser warning, or use mkcert for locally-trusted certs.
Systemd Service (Linux)
Raspberry Pi (recommended): paste this into your Pi terminal:
bash <(curl -fsSL https://raw.githubusercontent.com/codemonkeybr/meshcore-pi-companion/main/scripts/get-remoteterm.sh)Or, if you already have the repo cloned:
chmod +x scripts/manage_remoterm.sh
sudo ./scripts/manage_remoterm.shSee docs/PI_DEPLOYMENT.md. USB serial overrides go in /etc/remoterm/environment (MESHCORE_SERIAL_PORT); the unit file loads them via EnvironmentFile=-/etc/remoterm/environment.
Manual install (any path; assumes /opt/remoteterm):
# Create service user
sudo useradd -r -m -s /bin/false remoteterm
# Install to /opt/remoteterm
sudo mkdir -p /opt/remoteterm
sudo cp -r . /opt/remoteterm/
sudo chown -R remoteterm:remoteterm /opt/remoteterm
# Install dependencies
cd /opt/remoteterm
sudo -u remoteterm uv venv
sudo -u remoteterm uv sync
# Build frontend (required for the backend to serve the web UI; requires Node.js)
cd /opt/remoteterm/frontend
sudo -u remoteterm npm ci
sudo -u remoteterm npm run build
# Install and start service
sudo cp /opt/remoteterm/remoteterm.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now remoteterm
# Check status
sudo systemctl status remoteterm
sudo journalctl -u remoteterm -fEdit /etc/systemd/system/remoteterm.service or /etc/remoterm/environment to set MESHCORE_SERIAL_PORT for USB radios.
Testing
Backend:
PYTHONPATH=. uv run pytest tests/ -vFrontend:
cd frontend
npm run test:runE2E:
Warning: these tests are only guaranteed to run correctly in a narrow subset of environments; they require a busy mesh with messages arriving constantly and an available autodetect-able radio, as well as a contact in the test database (which you can provide in tests/e2e/.tmp/e2e-test.db after an initial run). E2E tests are generally not necessary to run for normal development work.
cd tests/e2e
npx playwright test # headless
npx playwright test --headed # show the browser windowWith the backend running: http://localhost:8000/docs (interactive OpenAPI).
Markdown reference for all exposed endpoints: docs/api/ (health, setup, radio, contacts, channels, messages, packets, read-state, settings, fanout, statistics, WebSocket).
If you're experiencing issues or opening a bug report, please start the backend with debug logging enabled. Debug mode provides a much more detailed breakdown of radio communication, packet processing, and other internal operations, which makes it significantly easier to diagnose problems.
To start the server with debug logging:
MESHCORE_LOG_LEVEL=DEBUG uv run uvicorn app.main:app --host 0.0.0.0 --port 8000Submit a bug: Open an issue and include the relevant debug log output. Use the "Bug report" template for a structured report.
