RTFM-EV

RTFM-EV

RTFM-EV is a web interface and FastAPI backend for MeshCore companion radios. A small server connects to one radio over Serial (USB), TCP or BLE, and you use it from any browser on your network: messaging, contact and repeater management, a live map, packet analysis and integrations such as MQTT and Home Assistant.

RTFM-EV is a fork of RemoteTerm for MeshCore by Jack Kingsman. Upstream development is on hold; this fork (Elektr0Vodka/RTFM-EV) is the active repository. It keeps everything RemoteTerm does as a live terminal for a radio and treats the server database as the system of record, so a radio that stays connected builds a long-lived local history of the mesh that you can browse and analyze. On top of that the fork adds, among other things:

See CHANGELOG-DMC-EV.md for everything the fork has changed.

Screenshot of the application's web interface

[!WARNING] Trusted networks only. There are no user accounts: anyone who can reach the page can use the radio. Do not expose the app to an untrusted network or the public internet. See Security.

[!NOTE] The app manages your radio. Once a radio is connected, its contacts and channels are imported into the app, and the app decides which contacts stay loaded on the radio. This makes it a poor fit if you swap radios in and out and want each radio to keep its own state. Only battery, noise floor and airtime history is kept per radio; contacts, channels, packets and messages are shared by every radio that feeds the install.

Features

Per-screen detail is in the in-app User Guide (sidebar Tools > User Guide, or #manual in the app). Its English source is frontend/src/content/manual/en.md.

Messaging

Contacts and nodes

Map

Tools and diagnostics

Radio management

Integrations and automation

Data and backup

Interface

Requirements

Finding your serial port ```bash ####### # Linux ####### ls /dev/ttyUSB* /dev/ttyACM* ####### # macOS ####### ls /dev/cu.usbserial-* /dev/cu.usbmodem* ########### # Windows ########### # In PowerShell: Get-CimInstance Win32_SerialPort | Select-Object DeviceID, Caption ###### # WSL2 ###### # Run this in an elevated PowerShell (not WSL) window winget install usbipd # restart console # then find device ID usbipd list # make device shareable usbipd bind --busid 3-8 # (or whatever the right ID is) # attach device to WSL (run this each time you plug in the device) usbipd attach --wsl --busid 3-8 # device will appear in WSL as /dev/ttyUSB0 or /dev/ttyACM0 ```

Quick start

Running from source is recommended over Docker: intermittent serial communication issues have been seen in containers on *nix systems.

From source

git clone https://github.com/Elektr0Vodka/RTFM-EV.git
cd RTFM-EV

uv sync
cd frontend && npm install && npm run build && cd ..

uv run uvicorn app.main:app --host 0.0.0.0 --port 8000

Open http://localhost:8000. The API docs are at http://localhost:8000/docs.

Pick the radio with environment variables (only one transport at a time):

# Serial (explicit port; leave unset to auto-detect)
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=5000 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000

# BLE
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 8000

On Windows (PowerShell), set environment variables as a separate statement:

$env:MESHCORE_SERIAL_PORT="COM8" # or your COM port
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000

[!WARNING] Windows + MQTT: Python’s default Windows event loop (ProactorEventLoop) does not work with the MQTT libraries. If you configure any MQTT integration, add --loop none to the uvicorn command:

uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --loop none

Without it the app starts, but MQTT connections fail and the UI shows a toast with this guidance.

On Linux you can install the app as a systemd service (named remoteterm) that starts on boot and restarts on failure:

bash scripts/setup/install_service.sh

See README_ADVANCED.md for details.

scripts/setup/fetch_prebuilt_frontend.py downloads a prebuilt frontend into frontend/prebuilt from the fork’s latest GitHub release, so you can skip the Node build. It only works once the fork publishes a release that carries a prebuilt frontend; until then, build the frontend as shown above.

Upgrading a source checkout

# If you run it as a systemd service, stop it first:
sudo systemctl stop remoteterm

cd RTFM-EV
git pull
uv sync
cd frontend && npm install && npm run build && cd ..

# Restart the service (or re-run uvicorn manually):
sudo systemctl start remoteterm

[!IMPORTANT] git pull alone is not enough. The browser loads the compiled frontend from frontend/dist, which is gitignored and only regenerated by npm run build, and uv sync picks up backend dependency changes.

Docker

[!WARNING] Docker has had intermittent issues with serial event subscriptions. The source install above is more reliable.

The published image ghcr.io/elektr0vodka/rtfm-ev:latest is rebuilt on every push to main. Create a local docker-compose.yml (gitignored, so pulls do not overwrite it) in one of two ways:

# Copy the example and edit the device mapping and environment by hand
cp docker-compose.example.yml docker-compose.yml

# Or generate one interactively
bash scripts/setup/install_docker.sh

The interactive generator enables a self-signed (snakeoil) TLS certificate by default, so the app is served over HTTPS; decline if you want plain HTTP or terminate TLS elsewhere. It can collect BLE settings, but BLE from Docker still needs manual compose changes (Bluetooth passthrough, possibly privileged mode or host networking). For BLE, the source install is simpler.

Then start it:

sudo docker compose up # add -d to run in the background once it works

The database lives in ./data/ (bind-mounted), the same place the source install uses.

# Update to the latest image
sudo docker compose pull
sudo docker compose up -d

# Stop
sudo docker compose down

Notes:

Configuration

Settings are environment variables with the MESHCORE_ prefix. Only one transport (serial, TCP or BLE) may be set; if more than one is set, the server refuses to start. With none set, the server auto-detects a serial radio.

Variable Default Description
MESHCORE_SERIAL_PORT (auto-detect) Serial port path
MESHCORE_SERIAL_BAUDRATE 115200 Serial baud rate
MESHCORE_TCP_HOST   TCP host
MESHCORE_TCP_PORT 5000 TCP port
MESHCORE_BLE_ADDRESS   BLE device address
MESHCORE_BLE_PIN   BLE PIN (required when MESHCORE_BLE_ADDRESS is set)
MESHCORE_LOG_LEVEL INFO DEBUG, INFO, WARNING or ERROR
MESHCORE_DATABASE_PATH data/meshcore.db SQLite database path
MESHCORE_BASIC_AUTH_USERNAME   Optional app-wide HTTP Basic auth username; set together with the password
MESHCORE_BASIC_AUTH_PASSWORD   Optional app-wide HTTP Basic auth password; set together with the username
MESHCORE_VAPID_SUBJECT mailto:noreply@meshcore.local Web Push VAPID sub claim (mailto: or https:). Apple rejects the default .local domain, so set a real address for iOS/Safari push
MESHCORE_HOST_REPEATER_ENABLED false Server switch needed to arm the host repeater for live forwarding. Shadow mode does not need it
MESHCORE_UPDATE_CHECK_ENABLED true Check GitHub for a newer fork build and show an in-app indicator; set false to disable the outbound request

Remediation and advanced variables (MESHCORE_ENABLE_MESSAGE_POLL_FALLBACK, MESHCORE_FORCE_CHANNEL_SLOT_RECONFIGURE, MESHCORE_LOAD_WITH_AUTOEVICT, MESHCORE_ENABLE_LOCAL_PRIVATE_KEY_EXPORT and others) are described in README_ADVANCED.md. Most other settings live in the app under Settings.

Security

Firmware and node support

meshcomod (DMC-EV) firmware

meshcomod (DMC / DMC-EV) is a multi-transport companion firmware for Heltec and Seeed LoRa devices. When the connected radio runs meshcomod, a Meshcomod (DMC-EV) panel appears under Settings > Radio:

Detection is automatic from the radio’s device info; each control disables itself when the firmware build does not report support, and the panel is hidden on other firmware. Stock companion firmware that reports the gps custom var gets a standalone GPS toggle instead.

OpenHop nodes

OpenHop repeaters and room servers (a Python MeshCore daemon) work in two ways:

Host repeater

RTFM-EV can judge every packet its radio receives the way a repeater would: MeshCore forwarding rules, flood.max, region and loop settings, the DMC packet filter, DMC duty-cycle region gating, OpenHop-style policy rules (modelled on the jhuebert/MeshCore repeater filter) and an advert limiter. Configure it under Settings > Host repeater.

Documentation

Contributing

Read CONTRIBUTING.md for local development, tests, linting and E2E notes. If you extend the app with an LLM, have it read the three AGENTS.md files: AGENTS.md, app/AGENTS.md and frontend/AGENTS.md.

This project is developed with heavy agentic assistance, guided by an engineer who cares about clean code and good tests. There is no warranty of fitness for any purpose, and you may find bugs.

License and credits