module Meshtastic::SerialInterface
def self.authors
def self.authors rt@0dayinc.com>
def self.connect(opts = {})
def self.connect(opts = {}) block_dev] ||= '/dev/ttyUSB0' ck device: #{block_dev}" unless File.exist?(block_dev) ||= 115_200 data_bits] ||= 8 stop_bits] ||= 1 ity] ||= :none o_sym ity: #{opts[:parity]}" if parity.nil? s}#{parity}#{stop_bits}" .open( _conn] = serial_conn e_thread] = init_stdout_thread( ial_conn, thread] = init_stdout_thread( ial_conn, ial_obj: serial_obj) MeshInterface.new => e obj: serial_obj) unless serial_obj.nil?
def self.disconnect(opts = {})
serial_obj: 'required - serial_obj returned from #connect method'
serial_obj = Meshtastic.disconnect(
Supported Method Parameters::
def self.disconnect(opts = {}) :serial_obj] serial_obj[:console_thread] erial_obj[:proto_thread] rial_obj[:serial_conn] terminate rminate se => e
def self.dump_stdout_data(opts = {})
def self.dump_stdout_data(opts = {}) roto console] lid type: #{type}. Supported types are :proto or :console" unless valid_types.include?(type) { |proto_hash| yield proto_hash } if type == :proto in.split("\n").each{ |line| yield line.force_encoding('UTF-8') } if type == :console roto_data if type == :proto onsole_data.join if type == :console => e
def self.flush_data(opts = {})
def self.flush_data(opts = {}) roto console] lid type: #{type}. Supported types are :proto or :console" unless valid_types.include?(type) r if type == :console if type == :proto => e
def self.help
def self.help elf}.connect( l - mqtt host (default: mqtt.meshtastic.org)', l - mqtt port (defaults: 1883)', - use TLS (default: false)', ional - mqtt username (default: meshdev)', ional - (default: large4cats)', tional - client ID (default: random 4-byte hex string)', ptional - keep alive interval (default: 15)', optional - acknowledgement timeout (default: 30)' device( equired - serial_obj returned from #connect method' equired serial_obj returned from #connect method', ired - array of bytes OR string to write to serial device (e.g. [0x00, 0x41, 0x90, 0x00] OR \"\\x00\\x41\\c90\\x00\\r\\n\"' self}.dump_stdout_data( d - :proto or :console' ta( l - :console or :proto (default: nil)' stdout( d - :proto or :console', onal - refresh interval (default: 3)', onal - comma-delimited string(s) to include in message (default: nil)', onal - comma-delimited string(s) to exclude in message (default: nil)', e( equired - serial_obj object returned from #connect method', ptional - root topic (default: msh)', nal - region e.g. 'US/VA', etc (default: US)', 'optional - channel ID path e.g. '2/stat/#' (default: '2/e/LongFast/#')', l - hash of :channel_id => psk key value pairs (default: { LongFast: 'AQ==' })', - quality of service (default: 0)', l - JSON output (default: false)', onal - comma-delimited string(s) to exclude in message (default: nil)', onal - comma-delimited string(s) to include on in message (default: nil)', 'optional - include GPS metadata in output (default: false)' t( equired - serial_obj returned from #connect method', d - From ID (String or Integer) (Default: \"!00000b0b\")', - Destination ID (Default: \"!ffffffff\")', al - topic to publish to (default: 'msh/US/2/e/LongFast/1')', onal - channel (Default: 6)', l - Text Message (Default: SYN)', ional - Want Acknowledgement (Default: false)', 'optional - Want Response (Default: false)', tional - Hop Limit (Default: 3)', optional - Callback on Response', l - hash of :channel => psk key value pairs (default: { LongFast: 'AQ==' })' elf}.disconnect( equired - serial_obj object returned from #connect method'
def self.init_stdout_thread(opts = {})
def self.init_stdout_thread(opts = {}) :serial_conn] oto console] id type: #{type}. Supported types are :proto or :console" unless valid_types.include?(type) obj console_thread d_timeout = -1 tastic::FromRadio.new t_readable s into @console_data, adable bytes if need-be from_radio.to_h if type == :proto < serial_conn.readchar.force_encoding('UTF-8') if type == :console => e
def self.monitor_stdout(opts = {})
def self.monitor_stdout(opts = {}) :serial_obj] roto console] lid type: #{type}. Supported types are :proto or :console" unless valid_types.include?(type) fresh] ||= 3 clude] clude] clude.to_s.split(',').map(&:strip) clude.to_s.split(',').map(&:strip) (type: type) do |data| exclude_arr.none? { |exclude| data.include?(exclude) } && ( include_arr.empty? || include_arr.all? { |include| data.include?(include) } ) isp e: type) ected. Breaking out of console mode..." obj: serial_obj) unless serial_obj.nil? => e obj: serial_obj) unless serial_obj.nil?
def self.request(opts = {})
def self.request(opts = {}) :serial_obj] al_obj[:serial_conn] yload] if payload.instance_of?(Array) .chars if payload.instance_of?(String) lid payload type: #{payload.class}" if byte_arr.nil? byte| (byte) => e obj: serial_obj) unless serial_obj.nil?
def self.send_text(opts = {})
psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: "AQ==" })'
on_response: 'optional - Callback on Response',
hop_limit: 'optional - Hop Limit (Default: 3)',
want_response: 'optional - Want Response (Default: false)',
want_ack: 'optional - Want Acknowledgement (Default: false)',
text: 'optional - Text Message (Default: SYN)',
channel: 'optional - channel (Default: 6)',
topic: 'optional - topic to publish to (Default: "msh/US/2/e/LongFast/1")',
to: 'optional - Destination ID (Default: "!ffffffff")',
from: 'required - From ID (String or Integer) (Default: "!00000b0b")',
serial_obj: 'required - serial_obj returned from #connect method',
Meshtastic::SerialInterface.send_text(
Supported Method Parameters::
def self.send_text(opts = {}) s[:serial_obj] pic] ||= 'msh/US/2/e/LongFast/#' o chunked message to deal with large messages MeshInterface.new ) ivalent of publish sh(topic, protobuf_text) => e nnect(serial_obj: serial_obj) unless serial_obj.nil?
def self.subscribe(opts = {})
def self.subscribe(opts = {}) :serial_obj] :root_topic] ||= 'msh' ion] ||= 'US' ts[:channel_topic] ||= '2/e/LongFast/#' ray of PSKs and attempt each until decrypted OiApB1nwvP+rz05pAQ==' ||= { LongFast: public_psk } parameter must be a hash of :channel_id => psk key value pairs' unless psks.is_a?(Hash) public_psk if psks[:LongFast] == 'AQ==' MeshInterface.new her_keys(psks: psks) |= 0 ||= false clude] clude] s[:gps_metadata] ||= false [:include_raw] ||= false xplorer for topic discovery ot_topic}/#{region}/#{channel_topic}" ot_topic}/#{region}" if region == '#' to: #{full_topic}" be(full_topic, qos) ception: No Ping Response received for 23 seconds (MQTT::ProtocolException) ude.to_s.split(',').map(&:strip) ude.to_s.split(',').map(&:strip) ket do |packet_bytes| ket_bytes.to_s if include_raw et_bytes.topic ||= '' cket_bytes.payload ||= '' d_hash = {} = '' oad_hash = JSON.parse(raw_payload, symbolize_names: true) yload = Meshtastic::ToRadio.decode(raw_payload) oad = Meshtastic::FromRadio.decode(raw_payload) oad_hash = decoded_payload.to_h coded_payload_hash[:packet].is_a?(Hash) ded_payload_hash[:packet] if decoded_payload_hash.keys.include?(:packet) ] = raw_topic id_from] = "!#{message[:from].to_i.to_s(16)}" id_to] = "!#{message[:to].to_i.to_s(16)}" s.include?(:rx_time) = message[:rx_time] nt.is_a?(Integer) c = Time.at(rx_time_int).utc.to_s x_time_utc] = rx_time_utc s.include?(:public_key) ey = message[:public_key] lic_key] = Base64.strict_encode64(raw_public_key) _message is not nil, then decrypt prior to decoding. age = message[:encrypted] essage.to_s.length.positive? && pic] [:pki_encrypted] Display Decrypted PKI Message ey = message[:public_key] ic_key = Base64.strict_decode64(public_key) message[:id] = message[:from] _id = [packet_id].pack('V').ljust(8, "\x00") ode = [packet_from].pack('V').ljust(8, "\x00") once_packet_id}#{nonce_from_node}" LongFast] el = message[:topic].split('/')[-2].to_sym arget_channel] if psks.keys.include?(target_channel) se64.strict_decode64(psk) nSSL::Cipher.new('AES-128-CTR') nSSL::Cipher.new('AES-256-CTR') if dec_psk.length == 32 pt dec_psk nonce cipher.update(encrypted_message) + cipher.final oded] = Meshtastic::Data.decode(decrypted).to_h rypted] = :decrypted coded] Meshtastic::Data.decode(message[:decoded][:payload]).to_h ssage[:decoded][:payload] essage[:decoded][:portnum] stic::MeshInterface.new oded][:payload] = mui.decode_payload( ayload, msg_type, ta: gps_metadata acket] = raw_packet if include_raw d_hash[:packet] = message iven? out] = 'pretty' ge = JSON.pretty_generate(decoded_payload_hash) :CompatibilityError, rotobuf::ParseError, eratorError, rror => e (Encoding::CompatibilityError) rypted] = e.message if e.message.include?('key must be') rypted] = 'unable to decrypt - psk?' if e.message.include?('occurred during parsing') oad_hash[:packet] = message _given? ING: #{e.inspect} - MSG IS >>>" acktrace tdout] = 'inspect' sage = decoded_payload_hash.inspect [message[:id].to_s] if include_arr.empty? a?(Hash) = message.values.join(' ') if exclude_arr.none? { |exclude| flat_message.include?(exclude) } && ( include_arr.first == message[:id] || include_arr.all? { |include| flat_message.include?(include) } ) iven? coded_payload_hash " * 80 G:' out_message * 80 \n\n" ' ected. Exiting..." nnect(serial_obj: serial_obj) unless serial_obj.nil? => e nnect(serial_obj: serial_obj) unless serial_obj.nil?
def self.wake_up_device(opts = {})
serial_obj: 'required - serial_obj returned from #connect method'
wake_up_device(
Supported Method Parameters::
def self.wake_up_device(opts = {}) :serial_obj] [START2].pack('C') * 32 : serial_obj, payload: start2_byte_arr) => e obj: serial_obj) unless serial_obj.nil?