meshtastic.el
Chat client for Meshtastic LoRa mesh networks in Emacs, inspired by ERC and rcirc.
Requires Emacs 28.1 or later and the meshtastic Python package.
Connects directly to a Meshtastic device over USB serial. No server or intermediate proxy required.
How it works
LoRa Radio Python bridge Emacs
+-----------+ +-------------------+ +---------------+
| Meshtastic|<---->| meshtastic-bridge |<---->| meshtastic.el |
| Device | USB | .py (subprocess) | JSON | (this package)|
+-----------+ +-------------------+ +---------------+
Physical Reads/writes serial Chat buffers
device Emits JSON events Send / receive
- A Meshtastic device sends and receives messages over LoRa radio.
- meshtastic-bridge.py connects to the device via serial, forwards received messages as JSON lines to stdout, and accepts JSON commands on stdin.
- meshtastic.el spawns the bridge as a subprocess, parses its output, and provides chat buffers.
Buffers
Welcome screen (M-x meshtastic)
Meshtastic
======================================
Connection
Port: /dev/ttyUSB0
Status: Connected
Node: Hilltop Relay (!a1b2c3d4)
Statistics
Nodes: 12
Channels: 2
--------------------------------------
[c] Channels [n] Nodes
[g] Refresh [q] Quit
--------------------------------------
Channel list (M-x meshtastic-channels)
ID Name Role
0 LongFast Primary
1 HikingGroup Secondary
Node list (M-x meshtastic-nodes)
Hops Name Node ID Last heard Battery
0 🟢 Hilltop Relay !a1b2c3d4 now 85%
1 🟢 Solar Node 7 !d4e5f6a7 12m -
2 🟢 BaseStation K9 !b8c9d0e1 5m 62%
3 ⚫ Mountain Peak !f2a3b4c5 1h -
Traceroute log (M-x meshtastic-traceroute or t in node list)
[08:30:01] → Solar Node 7 (!d4e5f6a7)
✓ → Hilltop Relay → BaseStation K9 (8.0dB) → Solar Node 7
← Solar Node 7 → BaseStation K9 (7.0dB) → Hilltop Relay
[08:31:15] → Mountain Peak (!f2a3b4c5)
⧖ Pending...
Chat buffer (channel or DM)
[08:15] <Hilltop Relay> Good morning mesh!
[08:20] <BaseStation K9> Morning! Signal is great today
[08:21] <Solar Node 7> Copy that, 3 hops from here
[08:45] <Mountain Peak> Anyone near the trailhead?
[09:02] <Hilltop Relay> I can see 12 nodes from up here
[09:05] <BaseStation K9> Confirmed ✓
[09:30] <River Bridge> Just set up a new repeater
#LongFast> _
Features
- Channel list: browse available Meshtastic channels.
- Node list: browse all mesh nodes sorted by hop count, with online indicator and last-heard time.
- Chat buffers: read and send messages with an ERC-like prompt interface.
- Direct messages: open a DM chat with any node by pressing
RETin the node list. - Message buffer: messages received since the bridge started are kept in memory and shown when a chat buffer opens.
- Delivery indicator: sent messages show
·(sent to bridge),✓(bridge confirmed send) or✗(failed). - Desktop notifications: get notified when new messages arrive in background buffers.
- Input history: navigate previous inputs with
M-p/M-n. - Traceroute: send a traceroute to a node and see the hop path and SNR values in a dedicated log buffer. Works from the node list (
t), a DM chat (M-x meshtastic-traceroute), or any buffer (prompts for a node ID). - Send position: send your GPS coordinates to a node via
M-x meshtastic-send-position. Usescalendar-latitude/calendar-longitudeif set, otherwise falls back to the device GPS. Works from the node list, a DM chat, or any buffer. - Telemetry: battery level (and other device metrics) is shown in the Battery column of the node list as percentages arrive from the mesh. Nodes that have not sent telemetry show
-. - Real-time node list: the node list updates automatically when nodes exchange information on the mesh (new nodes appearing, name or hardware changes), without needing to press
g.
Keymap
Channel and node list buffers
| Key | Description |
|---|---|
RET |
Open channel chat or DM with node |
0-7 |
Open channel by number (channels) |
t |
Send traceroute to node (nodes) |
g |
Refresh list from device |
q |
Quit buffer |
Chat buffers
| Key | Description |
|---|---|
RET |
Send message |
M-p |
Previous input from history |
M-n |
Next input from history |
C-c C-l |
Reload buffered messages |
Commands available from any buffer
| Command | Description |
|---|---|
M-x meshtastic-traceroute |
Traceroute to a node (uses DM target if in DM) |
M-x meshtastic-send-position |
Send GPS position (uses DM target if in DM) |
Requirements
- Emacs 28.1+
- Python 3 with the meshtastic package:
pip install meshtastic
- A Meshtastic device connected via USB serial.
Installation
MELPA
M-x package-install RET meshtastic RET
use-package with :vc (Emacs 29+)
(use-package meshtastic
:vc (:url "https://git.andros.dev/andros/meshtastic.el"
:rev :newest)
:config
(setq meshtastic-serial-port "/dev/ttyUSB0"))
use-package with :load-path
(use-package meshtastic
:load-path "/path/to/meshtastic.el"
:config
(setq meshtastic-serial-port "/dev/ttyUSB0"))
Manual
Clone the repository and place the files on your load-path:
git clone https://git.andros.dev/andros/meshtastic.el.git
Add to your init file:
(add-to-list 'load-path "/path/to/meshtastic.el")
(require 'meshtastic)
(setq meshtastic-serial-port "/dev/ttyUSB0")
Usage
- Set
meshtastic-serial-portto match your device (e.g./dev/ttyUSB0on Linux,/dev/cu.usbserial-*on macOS). - Run
M-x meshtasticto open the welcome screen. The bridge starts automatically. - Press
cto browse channels ornto browse nodes. - Press
RETon a channel or node to open a chat buffer. - Type your message and press
RETto send. - Run
M-x meshtastic-disconnectto stop the bridge.
Configuration examples
Linux, pip
The default configuration works out of the box after pip install meshtastic.
Adjust the serial port to match your device:
(use-package meshtastic
:ensure t
:config
(setq meshtastic-serial-port "/dev/ttyUSB0"))
Find your port with ls /dev/ttyUSB* /dev/ttyACM* after plugging in the device.
macOS, pip
macOS serial ports use a cu.usbserial-* naming scheme:
(use-package meshtastic
:ensure t
:config
(setq meshtastic-serial-port "/dev/cu.usbserial-0001"))
Find your port with ls /dev/cu.usbserial-* or ls /dev/cu.SLAB_USBtoUART*.
macOS, uv
If you manage the Python environment with uv,
clone the repository (it includes a pyproject.toml with the meshtastic
dependency) and point the executable at uv run python:
git clone https://git.andros.dev/andros/meshtastic.el.git
cd meshtastic.el
uv sync # creates .venv and installs meshtastic
(use-package meshtastic
:load-path "~/path/to/meshtastic.el"
:config
(setq meshtastic-serial-port "/dev/cu.usbserial-0001")
(setq meshtastic-python-executable "/opt/homebrew/bin/uv run python"))
uv run python resolves the .venv from the package directory automatically;
no manual activation is needed.
Windows
Serial ports on Windows are named COMn:
(use-package meshtastic
:ensure t
:config
(setq meshtastic-serial-port "COM3")
(setq meshtastic-python-executable "python"))
Check Device Manager under "Ports (COM & LPT)" for the correct number.
Customization
Run M-x customize-group RET meshtastic RET to list all options.
Key options:
meshtastic-serial-port(default"/dev/ttyUSB0"): path to the serial device.meshtastic-python-executable(default"python3"): Python binary used to launch the bridge. Supports multi-word values such as"uv run python".meshtastic-bridge-script: path tomeshtastic-bridge.py, auto-resolved from the package directory.meshtastic-notify(defaultt): enable desktop notifications (via D-Bus).meshtastic-timestamp-format(default"%H:%M"): format for message timestamps.meshtastic-message-history(default200): number of messages kept in memory per channel or DM.
Contributing
Bug reports, feature requests and pull requests are welcome. Please read the contribution guidelines before opening an issue or PR:
https://git.andros.dev/andros/contribute
Development
Running the tests
Install dev dependencies and run the test suite with uv:
uv sync --group dev
uv run pytest
Or with pip:
pip install pytest
pytest
Notes
- No message history on startup: unlike server-based clients, the bridge has no database. Only messages received since the bridge started are available.
- LoRa bandwidth: Meshtastic uses LoRa radio, which has very limited bandwidth. Keep messages short.
- One device: each running instance connects to exactly one serial device.
License
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.