Posted on

This is a post about writing code to run on a computer or server that then connects to a meshtastic node.

Overview

meshtastic supports sending data as either a protobuf (protocol buffer, a universal data standard set by google (it's basically just a struct)) or as a raw binary. These packets can be directed to certain endpoints. On connection to the node we have a certain expected packet exchange before we enter normal operations where we send messages and listen to messages.

HTTP Endpoints

endpointpermissionspurpose
fromradioreadthis is the endpoint for data travelling from the node to the client. it is a FIFO queue.
toradiowritethis is the endpoint for data travelling from the client to the node
fromnumread, notify, writethis is the number of the message currently waiting in the fromradio endpoint. note that this number should only increase, unless the node is rebooted.
lognotifythis is where debug messages go

pubsub

The python library uses a message bus to handle all meshtastic events. We then create a callback and subscribe it to the relevant "topic". Each topic is a string with dot notation to indicate levels of specificity, for example: "meshtastic.receive.text" is the topic for only text packets, "meshtastic.receive" is the topic for all packets, "meshtastic" is the topic for all events.

import pubsub
import meshtastic.serial_interface

connection = meshtastic.serial_interface.SerialInterface() 
# we need to establish a connection in order to have the bus receive messages

def callback(packet, interface):
    print(packet)

pub.subscribe(callback, "meshtastic.receive") #callback called on any packet

The packet takes the form of a dictionary, but the fields of that dictionary DON'T APPEAR TO BE DOCUMENTED ANYWHERE since technically anything could be using meshtastic as a transport layer the responsibility to document anything is passed on. (look at the portnums for more details, essentially portnum indicates what protocol the packet is implementing)

example packets

below are two example packet dictionaries so you can see the sort of fields they have. Notice that only the 'decoded' field changes. Also notice that 'portnum' tells you what kind of data it is (Although the actual list of fields for each portnum isn't necessarily listed anywhere). See here for a full list.

TELEMETRY
{
    'from': <id>, 
    'to': <id>, 
    'decoded': {
        'portnum': 'TELEMETRY_APP',
        'payload': b'\r*\x02\x11\x00\x12\x13\x08e\x15L7\xc9@\x1d\xb8\x1ee?%\x08et<(B',
        'bitfield': 1,
        'telemetry': {
            'time': 1114666,
            'deviceMetrics': {
                'batteryLevel': 101,
                'voltage': 6.288,
                'channelUtilization': 0.895,
                'airUtilTx': 0.014916666,
                'uptimeSeconds': 66
            },
            'raw': 
                time: 1114666
                device_metrics {
                    battery_level: 101,
                    voltage: 6.288000106811523,
                    channel_utilization: 0.8949999809265137,
                    air_util_tx: 0.014916665852069855,
                    uptime_seconds: 66
                }
        }
    },
    'id': <id>,
    'hopLimit': 5, 
    'priority': 'BACKGROUND',
    'hopStart': 5,
    'relayNode': 224,
    'raw': 
        from: <id>
        to: <id>
        decoded {
            portnum: TELEMETRY_APP
            payload: "\r*\002\021\000\022\023\010e\025L7\311@\035\270\036e?%\010et<(B"
            bitfield: 1
        }
        id: <id>
        hop_limit: 5
        priority: BACKGROUND
        hop_start: 5
        relay_node: 224
    'fromId': '<id>',
    'toId': '^all'
}

TEXT MESSAGE
{
    'from': <id>, 
    'to': <id>, 
    'decoded': {
        'portnum': 'TEXT_MESSAGE_APP', 
        'payload': b'hi', 
        'bitfield': 0, 
        'text': 'hi'
        }, 
    'id': 944823075, 
    'rxTime': 1758739701, 
    'rxSnr': 6.5, 
    'hopLimit': 7, 
    'wantAck': True, 
    'rxRssi': -51, 
    'hopStart': 7, 
    'publicKey': 'FyvMvBZ1TRRWUNsZkDPX7JTW4ZuEymOMxiTRSrsu9Bg=', 
    'pkiEncrypted': True, 
    'raw': 
        from: <id>
        to: <id>
        decoded {
            portnum: TEXT_MESSAGE_APP
            payload: "hi"
            bitfield: 0
        }
        id: <id>
        rx_time: 1758739701
        rx_snr: 6.5
        hop_limit: 7
        want_ack: true
        rx_rssi: -51
        hop_start: 7
        public_key: "\027+\314\274\026uM\024VP\333\031\2203\327\354\224\326\341\233\204\312c\214\306$\321J\273.\364\030"
        pki_encrypted: true, 
    'fromId': '<id>', 
    'toId': '<id>'
}

links

since I found the documentation pretty frustrating to navigate so here's a list of documentation pages that have been actually useful. https://meshtastic.org/docs/configuration/radio/device/ https://github.com/meshtastic/python/blob/master/TODO.md https://python.meshtastic.org/ https://buf.build/meshtastic/protobufs/docs/main:meshtastic#meshtastic.PortNum