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
  1. A Meshtastic device sends and receives messages over LoRa radio.
  2. meshtastic-bridge.py connects to the device via serial, forwards received messages as JSON lines to stdout, and accepts JSON commands on stdin.
  3. 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
  0     🟢 Hilltop Relay               !a1b2c3d4       now
  1     🟢 Solar Node 7                !d4e5f6a7       12m
  2     🟢 BaseStation K9              !b8c9d0e1       5m
  3     ⚫ Mountain Peak               !f2a3b4c5       1h

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 RET in 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: press t on a node to send a traceroute via the CLI.

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

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

  1. Set meshtastic-serial-port to match your device (e.g. /dev/ttyUSB0 on Linux, /dev/cu.usbserial-* on macOS).
  2. Run M-x meshtastic to open the welcome screen. The bridge starts automatically.
  3. Press c to browse channels or n to browse nodes.
  4. Press RET on a channel or node to open a chat buffer.
  5. Type your message and press RET to send.
  6. Run M-x meshtastic-disconnect to 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 to meshtastic-bridge.py, auto-resolved from the package directory.
  • meshtastic-notify (default t): enable desktop notifications (via D-Bus).
  • meshtastic-timestamp-format (default "%H:%M"): format for message timestamps.
  • meshtastic-message-history (default 200): number of messages kept in memory per channel or DM.

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/.

S
Description
Chat client for Meshtastic LoRa mesh networks in Emacs
Readme GPL-3.0 161 KiB
Languages
Emacs Lisp 59%
Python 41%