Chatting via Meshtastic in Emacs

I'd like to show you how to connect a Meshtastic device over USB directly to Emacs using a package I built for myself called meshtastic.el.

How it works

meshtastic.el spawns a Python subprocess that talks to the device over serial. Messages arrive as JSON lines at the Emacs process filter.

flowchart LR
    D["๐Ÿ“ป Meshtastic\ndevice"]:::device
    B["๐Ÿ meshtastic-bridge.py\n(Python subprocess)"]:::bridge
    E["meshtastic.el\n(Emacs)"]:::emacs

    D <-->|"USB serial"| B
    B <-->|"JSON\nstdin / stdout"| E

    classDef device fill:#37474F,stroke:#263238,stroke-width:2px,color:#fff
    classDef bridge fill:#00897B,stroke:#00695C,stroke-width:2px,color:#fff
    classDef emacs fill:#7B1FA2,stroke:#4A148C,stroke-width:2px,color:#fff

They are shown in ERC-style chat buffers.

Installation

The first thing you need to do is install the Meshtastic Python library.

pip install meshtastic

Next, install my package. It's available on MELPA.

M-x package-install RET meshtastic RET

Otherwise you can use use-package and :vc, which are native to Emacs.

(use-package meshtastic
  :vc (:url "https://git.andros.dev/andros/meshtastic.el"
       :rev :newest))

Configuration

Linux

Find the port with:

ls /dev/ttyUSB* /dev/ttyACM*
(setq meshtastic-serial-port "/dev/ttyUSB0")

If you get a permission error, add your user to the dialout group:

sudo usermod -aG dialout $USER

macOS

Ports on macOS use the cu.usbserial-* scheme:

ls /dev/cu.usbserial-*
(setq meshtastic-serial-port "/dev/cu.usbserial-0001")

Windows

(setq meshtastic-serial-port "COM3")
(setq meshtastic-python-executable "python")

The port number appears in Device Manager, under "Ports (COM & LPT)".

Usage

With everything ready, you can run M-x meshtastic. The bridge starts automatically and the welcome screen shows the connection status:

  Meshtastic
  ======================================

  Connection
  Port:      /dev/ttyUSB0
  Status:    Connected
  Node:      Hilltop Relay (!a1b2c3d4)

  Statistics
  Nodes:     12
  Channels:  2

  --------------------------------------

  [c] Channels          [n] Nodes
  [g] Refresh           [q] Quit

  --------------------------------------

While connecting, the status shows Connecting.... Once the connection is established it changes to Connected and the node and channel statistics appear without needing to refresh manually.

Channel list

Press c to see the available channels:

  ID  Name          Role
  0   LongFast      Primary
  1   HikingGroup   Secondary

Press RET on any channel to open its chat, or use the 0-7 keys directly.

Node list

Press n to see all the nodes on the mesh:

  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          -

The list is sorted by hops. The ๐ŸŸข indicator means the node has been heard in the last 15 minutes. The Battery column shows the battery level when the node has sent telemetry; - if there's no data yet.

The list updates automatically when nodes exchange information on the mesh, without needing to press g.

Chat

Press RET on a channel or node to open the chat buffer:

[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
[09:05] <BaseStation K9> Confirmed โœ“
#LongFast> _

Type your message after the prompt and press RET to send. Your own messages show ยท while they wait for confirmation from the bridge and change to โœ“ when the bridge confirms the send.

Navigate the input history with M-p and M-n.

Traceroute

Press t on any node in the list to send a traceroute. A dedicated log buffer opens:

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

Each entry shows the forward route with the SNR values of each hop, and the return route. While waiting for the response, โง– Pending... appears and updates as soon as it arrives.

You can also use M-x meshtastic-traceroute from a DM buffer (it uses the chat's node) or from any other buffer (it asks for the Node ID).

Send position

M-x meshtastic-send-position sends your GPS coordinates to the selected node. It uses calendar-latitude and calendar-longitude if they are configured in Emacs; if not, it falls back to the device's own GPS; and if there's none either, it shows an error.

It works from the node list, from a DM, or from any buffer (in which case it asks for the Node ID).

Limitations

We shouldn't equate a modern messaging app with a LoRa conversation. Its limitations are structural and you have to learn to live with them. Emacs can't get around them.

  • No history on startup: the bridge has no database. Only the messages received since it started are available.
  • Limited bandwidth: LoRa is a long-range but low-speed radio. Messages must be short.
  • One device per instance: each Emacs instance connects to a single serial port.
  • Messages can be lost: nobody guarantees that messages reach the recipient.

But what has no limit is the fun and all the people you can get to meet in your area.

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Will you buy me a coffee?

Comments

There are no comments yet.

Written by Andros Fenollosa

June 5, 2026

4 min of reading

You may also like

Visitors in real time

You are alone: ๐Ÿฑ