Skip to main content

restmesh

PyPI restmesh version Downloads Buy me a coffee M-Powered FastAPI

A stateless thread-safe REST API for Meshtastic.

Video

YouTube video
Click on the image to play the video on YouTube

Description and features

Send messages on Meshtastic using a standard REST API:

  • thread safe via asyncio
  • safe because a FIFO queue avoid overwhelming the mesh
  • very simple to integrate in other projects: Apprise is already available
  • aims to have 100% unit test coverage
  • doesn't use a database, it merely acts as a gateway
  • no MQTT, WiFI, bluetooth: just plug in the radio via USB, set it as CLIENT_MUTE and enjoy

Examples

Home Assistant Web hook

This will trigger a simple message every 5 minutes to display the total number of sensors configured in Home Assistant. The trigger event is time based, but you can configure to send messages from other triggers instead:

  1. create a new REST command in the ./configuration.yaml file:

    rest_command:
      message_meshtastic:
        url: 'http://127.0.0.1:8000/api/v1/channels/1/messages'
        content_type: 'application/json'
        method: post
        payload: |
          {
            "text": "Home Assistant test: {{ now().isoformat() }}; sensors: {{ states.sensor | length }}"
          }
    
  2. create the automation trigger in the ./automations.yaml file:

    - alias: "Message Meshtastic every 5 minutes"
      description: "Triggers a REST command every 5 minutes"
      trigger:
        - platform: time_pattern
          minutes: "/5"
     action:
       - action: rest_command.message_meshtastic
     mode: single
    
  3. restart Home Assistant

  4. check the /config/automation/dashboard page in the web UI

Error reporting: is Internet down?

A typical use case for restmesh is for system error reporting, for example when local Internet is down. You could set up a script to interface with restmesh like how I described in Automatic alerts and news on Meshtastic - part 1

RSS/Atom feeds to mesh: weather warnings

You can also resyndicate RSS/Atom feeds to Meshtastic using a third party program called feed2exec. You can add as many feeds as you like and schedule the feed fetching via some cron. See the Automatic alerts and news on Meshtastic - part 2 post.

Quickstart

One minute setup

  1. install pipx

  2. install restmesh

    pipx install restmesh
    
  3. your user must have access to the modem. For example on Debian you have to add your user to the dialout group

    sudo usermod -aG dialout ${USER}
    
  4. run restmesh

    restmesh
    
  5. connect to the /docs page using a browser

  6. create a Systemd service

    [Unit]
    Requires=network-online.target
    After=network-online.target
    
    [Service]
    User=meshtastic
    Group=meshtastic
    Type=simple
    ExecStart=/bin/restmesh
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    

Safe install

  1. import my GPG public key:

    curl https://blog.franco.net.eu.org/pubkeys/pgp_pubkey_since_2019.txt | gpg --import
    
  2. check its fingerprint and compare it with the one stored on DNS:

    gpg --list-keys --fingerprint
    dig TXT franco.net.eu.org +short | grep 'public-key-git-sig-fingerprint'
    

    If in doubt, contact me privately to arrange a key exchange.

  3. run the safe install script with Bash:

    ./safe_install.sh
    
  4. follow the post-installation instructions from the one minute setup

CLI help

usage: restmesh [-h] [--version] [--host HOST] [--port PORT] [--radio-serial-path RADIO_SERIAL_PATH]

restmesh: stateless thread-safe REST API for Meshtastic

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit
  --host HOST           server host listening address (default: 127.0.0.1)
  --port PORT           server listening port (default: 8000)
  --radio-serial-path RADIO_SERIAL_PATH
                        path of the USB serial device radio (default: /dev/ttyUSB0)

Running

Defaults

restmesh --host 127.0.0.1 --port 8000 --radio-serial-path /dev/ttyUSB0

Connect to http://127.0.0.1:8000/docs for the Swagger page to test the endpoints, or use Apprise directly.

Globally

restmesh --host 0.0.0.0 --port 8000 --radio-serial-path /dev/ttyUSB0

Use the Core API

Send to channel 0 (the primary channel):

curl -X 'POST' \
'http://127.0.0.1:8000/api/v1/channels/0/messages' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "text": "This is a message for the mesh on channel 0!",
  "want_ack": true,
  "port_num": 1
}'

Send to node !0a1b2c3d:

curl -X 'POST' \
  'http://127.0.0.1:8000/api/v1/nodes/%210a1b2c3d/messages' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "text": "This is a message for node !0a1b2c3d",
  "want_ack": true,
  "want_response": false,
  "port_num": 1
}'

BBS-Style commands

Some very basic BBS commands are implemented. You have to send direct messages to the node connected to restmesh. Broadcast messages are ignored. All commands must start with ! or / and are argument-less.

Name Command Description
show help any string that is not a command prints the help
API !api or /api shows API name, version and credits
help !help or /help prints the help
MOTD !motd or /motd shows the set Message Of The Day. This will be settable via the API in future restmesh releases
ping !ping or /ping node replies with pong

Integrations

Apprise

restmesh accepts Apprise via the JSON schema. To be able to use it you need to install it first. See also the Repology page to see the available packages for Apprise.

Channel

Simple example using channel 0:

apprise -b 'My message here' "json://localhost:8000/api/v1/integrations/apprise/channels/0/messages"

With parameters:

apprise -b 'Hello world!' "json://localhost:8000/api/v1/integrations/apprise/channels/0/messages?:want_ack=false&:port_num=1"

Node

Send a message to the node with hex id !0a1b2c3d. Alternatively you can use the decimal integer representation of the node id, without prepending the ! character:

apprise -b 'My message here' "json://localhost:8000/api/v1/integrations/apprise/nodes/!0a1b2c3d/messages"

apprise -b 'My message here' "json://localhost:8000/api/v1/integrations/apprise/nodes/169552957/messages"

With parameters:

apprise -b 'Hello world!' "json://localhost:8000/api/v1/integrations/apprise/nodes/!0a1b2c3d/messages?:want_response=false&want_ack=true&:port_num=1"

apprise -b 'Hello world!' "json://localhost:8000/api/v1/integrations/apprise/nodes/169552957/messages?:want_response=false&want_ack=true&:port_num=1"

REST API

See the API file.

Contributing

See Contributing.

Responsible usage policy

Meshtastic

In some places, such as Europe, the non-ham LoRa band has limited air time use. Please don't use restmesh for mass spamming, and never broadcast automated messages on public channels such as MediumFast or LongFast. Setup private channels instead.

restmesh has a basic message FIFO queue also to mitigate the air time problem.

FAQ

Why did you create restmesh?

I wanted a very simple zero-effort way to push existing notifications on Meshtastic as well. None of the solution satisfied me.

How do you write restmesh?

restmesh is the official name of the package, so all lower case. Restmesh or RestMesh are both wrong.

Why the name restmesh?

restmesh = REST [API] + Mesh[tastic]

Is the objective of restmesh to replicate the official Meshtastic Python CLI 1:1?

No. The objective of restmesh is to simplify automations involving Meshtastic: some behaviors might be replicated while others will be new and automation oriented (e.g: substitution via regex).

Does restmesh have a web UI besides Swagger's one?

Not yet, but a very simple one for debug purposes will be implemented and served as http://127.0.0.1:8000/

Does restmesh manage incoming messages?

Not yet, but something will be implemented.

Does restmesh support multiple radios at the same time?

No, and there is no plan to do this at the moment: I want to keep things simple.

Is there a retry strategy algorithm?

Yes. Meshtastic already has a built-in implementation for a Reliable Zero Hop Messaging algorithm. When setting the WantAck option for broadcast messages, this applies:

If a transmitting node does not receive an ACK (or NAK) packet after a certain expiration time, it will use Layer 1 to attempt a re-transmission of the sent packet. A reliable packet (at this 'zero hop' level) will be resent a maximum of three times.

Also:

If no ACK or NAK has been received by then the local node will internally generate a NAK (either for local consumption or use by higher layers of the protocol).

The onResponse callback used in restmesh could implement a simple extra retry strategy independent from Meshtastic's firmware, and at the moment only a brief informational logging is printed on the server side when WantAck is true and a NAK is returned, or when WantResponse is true

In all API endpoints, by default, WantAck is true and WantResponse is false.

Does restmesh support sending other types of data?

According to the official Meshtastic Python API, you can send binary, position, text alerts, etc... At the moment restmesh is specific for simple texts but in the future it might support other types of data. Sending and receiving binary data may open up interesting possibilities.

Consulting and custom integrations

If you need help or custom endpoints and integrations, I'm available for contract-based freelance consulting and custom Python development:

License

Copyright (C) 2026 Franco Masotti

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

restmesh 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 restmesh. If not, see http://www.gnu.org/licenses/.

Git forge mirrors

URL Type Notes
https://github.com/frnmst/restmesh RW Official home
https://codeberg.org/frnmst/restmesh RW Mirror
https://framagit.org/frnmst/restmesh RW Mirror
https://repos.franco.net.eu.org/frnmst/restmesh RW Mirror

Support this project

Metadata

Release files for restmesh 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for restmesh 0.6.0
File Size Uploaded Provenance Actions
restmesh-0.6.0.tar.gz 37.1 kB Without provenance Details for restmesh-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for restmesh 0.6.0
File Interpreter ABI Platform Actions
restmesh-0.6.0-py3-none-any.whl Python 3 none any Details for restmesh-0.6.0-py3-none-any.whl

Total release size: 67.1 kB

Release files / restmesh-0.6.0.tar.gz

Download URL restmesh-0.6.0.tar.gz
Size 37.1 kB
Tags Source
SHA-256 checksum
How to use checksums
e1f334a09080a4b684e93435e697c81ef8b8f4e1474cccb2eccb23ed7e1c0b3f
BLAKE2b-256 checksum
How to use checksums
d470736d4344c22732bfc4308b4e5c67509192b44d0aef7016b7b0c072fa61e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / restmesh-0.6.0-py3-none-any.whl

Download URL restmesh-0.6.0-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7daa970392429180e70f4be71ad5513404307a0724c2b0cd6a4e2ae6e1fb36aa
BLAKE2b-256 checksum
How to use checksums
4de1209ecab957fa40d6593b22688fa5f4133e61b89cf3daef8c1aacf28d7f5a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for restmesh 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page