Meshtastic
Ruby gem for interfacing with Meshtastic nodes / network.
Setting Expectations
This gem was created to support alt-comm capabilities w/in a security research framework known as PWN. Contributors of this effort cannot guarantee full functionality or support for all Meshtastic features.
Objectives
- Consume the latest Meshtastic Protobof Specs and auto-generate Ruby protobuf modules for Meshtastic using the
grpc_tools_ruby_protoccommand:Complete - Integrate auto-generated Ruby protobuf modules into a working Ruby gem:
Complete - Scale out Meshtastic Ruby Modules for their respective protobufs within the meshtastic gem (e.g. Meshtastic::MQTTPB is auto-generated based on latest Meshtastic protobuf specs and extended via Meshtastic::MQTT for more MQTT interaction as desired):
Ongoing Effort
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add meshtastic
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install meshtastic
Usage
The primary interaction modules today are Meshtastic::MQTT (broker) and Meshtastic::SerialInterface (USB/UART). Examples for each follow.
MQTT
To view MQTT messages, and include only messages containing _APP and LongFast strings, use the following code:
require 'meshtastic'
Meshtastic::MQTT.help
mqtt_obj = Meshtastic::MQTT.connect
puts mqtt_obj.inspect
Meshtastic::MQTT.subscribe(
mqtt_obj: mqtt_obj,
include: '_APP, LongFast'
)
This code will dump the contents of every message:
require 'meshtastic'
mqtt_obj = Meshtastic::MQTT.connect
Meshtastic::MQTT.subscribe(
mqtt_obj: mqtt_obj,
root_topic: 'msh',
region: 'US',
topic: '2/e/LongFast/#',
psks: { LongFast: 'AQ==' }
) do ||
puts .inspect
end
Sending a message over MQTT:
require 'meshtastic'
mqtt_obj = Meshtastic::MQTT.connect
client_id = "!#{mqtt_obj.client_id}"
Meshtastic::MQTT.send_text(
mqtt_obj: mqtt_obj,
from: client_id,
to: '!ffffffff',
root_topic: 'msh',
region: 'US',
topic: '2/e/LongFast/#',
channel: 93,
text: 'Hello, World!',
psks: { LongFast: 'AQ==' }
)
One of the "gotchas" when sending messages is ensuring you're sending over the proper integer for the channel parameter. The best way to determine the proper channel value is by sending a test message from within the meshtastic app and then viewing the MQTT message similar to the following:
require 'meshtastic'
mqtt_obj = Meshtastic::MQTT.connect
Meshtastic::MQTT.subscribe(
mqtt_obj: mqtt_obj,
root_topic: 'msh',
region: 'US',
topic: '2/e/LongFast/#',
psks: { LongFast: 'AQ==' },
include: '!YOUR_CLIENT_ID'
) do ||
puts .inspect
end
You should see something like this:
{packet: {from: 4080917205, to: 4294967295, channel: 93, id: 1198634591, rx_time: 1738614021, rx_snr: 0.0, hop_limit: 3, want_ack: false, priority: :HIGH, rx_rssi: 0, delayed: :NO_DELAY, via_mqtt: false, hop_start: 3, public_key: "", pki_encrypted: false, next_hop: 0, relay_node: 0, tx_after: 0, decoded: {portnum: :TEXT_MESSAGE_APP, payload: "WHAT IS MY channel VALUE?", want_response: false, dest: 0, source: 0, request_id: 0, reply_id: 0, emoji: 0, bitfield: 0}, encrypted: :decrypted, topic: "msh/US/2/e/LongFast/!f33ddad5", node_id_from: "!f33ddad5", node_id_to: "!ffffffff", rx_time_utc: "2025-01-01 07:00:00 UTC"}, channel_id: "LongFast", gateway_id: "!f33ddad5"}
Note where is says channel: 93. This is the channel value required to send messages in this particular example.
Serial Interface
Talk directly to a Meshtastic node over USB/UART (/dev/ttyUSB*, /dev/ttyACM*). Unlike MQTT, the radio owns channel crypto for serial: payloads are sent decoded and the device encrypts with its configured channel key.
To inspect available methods and open a serial session:
require 'meshtastic'
Meshtastic::SerialInterface.help
serial_obj = Meshtastic::SerialInterface.connect(
block_dev: '/dev/ttyUSB0', # or /dev/ttyACM0
baud: 115_200
)
puts serial_obj.inspect
This code will dump every FromRadio packet (blocks until CTRL+C):
require 'meshtastic'
serial_obj = Meshtastic::SerialInterface.connect(
block_dev: '/dev/ttyUSB0',
baud: 115_200
)
Meshtastic::SerialInterface.subscribe(
serial_obj: serial_obj
) do ||
puts .inspect
end
Filter with include / exclude (comma-delimited substrings), same idea as MQTT:
require 'meshtastic'
serial_obj = Meshtastic::SerialInterface.connect(block_dev: '/dev/ttyUSB0')
Meshtastic::SerialInterface.subscribe(
serial_obj: serial_obj,
include: 'TEXT_MESSAGE_APP',
exclude: 'TELEMETRY_APP'
) do ||
puts .inspect
end
Sending a message over serial:
require 'meshtastic'
serial_obj = Meshtastic::SerialInterface.connect(
block_dev: '/dev/ttyUSB0',
baud: 115_200
)
Meshtastic::SerialInterface.send_text(
serial_obj: serial_obj,
to: '!ffffffff', # broadcast; or a node id like '!f33ddad5'
channel: 0, # primary channel index on the radio
text: 'Hello over serial!'
)
Meshtastic::SerialInterface.disconnect(serial_obj: serial_obj)
A typical end-to-end send + receive session looks like this (connect once, send, then subscribe):
require 'meshtastic'
serial_obj = Meshtastic::SerialInterface.connect(
block_dev: '/dev/ttyUSB0',
baud: 115_200
)
# Optional: give the device a moment to finish want_config / my_info
sleep 2
puts "local node: !#{serial_obj[:my_node_num].to_s(16)}" if serial_obj[:my_node_num]
Meshtastic::SerialInterface.send_text(
serial_obj: serial_obj,
to: '!ffffffff',
channel: 0,
text: 'Hello over serial!'
)
# Blocks; CTRL+C disconnects cleanly
Meshtastic::SerialInterface.subscribe(serial_obj: serial_obj) do ||
puts .inspect
end
Non-blocking receive helpers (useful when you drive the loop yourself):
# Pop one framed FromRadio (or nil on timeout)
fr = Meshtastic::SerialInterface.recv_from_radio(timeout: 2)
puts fr&.to_h
# Drain whatever is already queued
Meshtastic::SerialInterface.drain_from_radio.each { |fr| puts fr.to_h }
# Debug console / proto dumps collected by the RX thread
puts Meshtastic::SerialInterface.dump_stdout_data(type: :console)
puts Meshtastic::SerialInterface.dump_stdout_data(type: :proto).size
Note: Serial path leaves mesh packets decoded and lets the radio encrypt with its configured channel key. The MQTT path still pre-encrypts with the PSK you supply via
psks:. Channel index (channel:) on serial is the index configured on the device, not the MQTT channel hash.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/0dayinc/meshtastic. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Code of Conduct
Everyone interacting in the Meshtastic project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.