Ruby gem for interfacing with Meshtastic nodes / network.
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.
- 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
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
The primary interaction modules today are Meshtastic::MQTT (broker) and Meshtastic::SerialInterface (USB/UART). Examples for each follow.
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 |message|
puts message.inspect
endSending 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 |message|
puts message.inspect
endYou 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.
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.inspectThis 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 |message|
puts message.inspect
endFilter 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 |message|
puts message.inspect
endSending 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 |message|
puts message.inspect
endNon-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).sizeNote: 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.
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.
Everyone interacting in the Meshtastic project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.