meshtastic 0.0.169 → 0.0.171

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8f7b8b0a9e3ef3d3830a06af9e4115b2a86e82ab0051a355601631622893cf5
4
- data.tar.gz: c25b3a7ca577b8ab7477b15b2f16bc9ed502578741be845580cb773db4fa50aa
3
+ metadata.gz: 9c5cf87c010662bcc98b9738bc6e1a4692bc67ba03e9f9c92b57b31b8b9211c2
4
+ data.tar.gz: f35c8128b63a7152fbfc31aeeb1814a2b66637ab9d5c049d045f6eaea46456ac
5
5
  SHA512:
6
- metadata.gz: 5339975316659ee89aca979cea79b9d79e61ead161bfdbd7b6a2c0bdbe9eb136469bedbf4e3c2a5c50d1bdb58bc0d4f309c3019bbc9ace9cfe5d811b6af9c5e3
7
- data.tar.gz: acb50a768aee9d2a3f4a7c3e868d8b509a4ea5c447356dffd25e8f9a3c283a5211af412ab94a523c71e62e555c5b6752b3e0f4728dbc21da62111b71a67607ad
6
+ metadata.gz: bc536784d826a5be06cc4c2385e0573d0b8f67a5ddb4ce2edb2c6a90c1cfca366ff68fe03436360adda292247575a02fdef2a29b99b2e4f6c272a101b77f2183
7
+ data.tar.gz: 10c01895bf08c5a026bff5b465e0ab57d47d2a470bfeca990485eb61a6b66e1aaaccae66b329b21336523902413391ee0927c471f8b137eaeb96d406ecf32646
data/README.md CHANGED
@@ -24,12 +24,16 @@ If bundler is not being used to manage dependencies, install the gem by executin
24
24
 
25
25
  ## Usage
26
26
 
27
- At the moment the only module available is `Meshtastic::MQTT`. To view MQTT messages, and include only messages containing `_APP` _and_ `LongFast` strings, use the following code:
27
+ The primary interaction modules today are `Meshtastic::MQTT` (broker) and `Meshtastic::SerialInterface` (USB/UART). Examples for each follow.
28
+
29
+ ### MQTT
30
+
31
+ To view MQTT messages, and include only messages containing `_APP` _and_ `LongFast` strings, use the following code:
28
32
 
29
33
  ```ruby
30
34
  require 'meshtastic'
31
35
  Meshtastic::MQTT.help
32
- mqtt_obj = Meshastic::MQTT.connect
36
+ mqtt_obj = Meshtastic::MQTT.connect
33
37
  puts mqtt_obj.inspect
34
38
  Meshtastic::MQTT.subscribe(
35
39
  mqtt_obj: mqtt_obj,
@@ -41,7 +45,7 @@ This code will dump the contents of every message:
41
45
 
42
46
  ```ruby
43
47
  require 'meshtastic'
44
- mqtt_obj = Meshastic::MQTT.connect
48
+ mqtt_obj = Meshtastic::MQTT.connect
45
49
  Meshtastic::MQTT.subscribe(
46
50
  mqtt_obj: mqtt_obj,
47
51
  root_topic: 'msh',
@@ -57,7 +61,7 @@ Sending a message over MQTT:
57
61
 
58
62
  ```ruby
59
63
  require 'meshtastic'
60
- mqtt_obj = Meshastic::MQTT.connect
64
+ mqtt_obj = Meshtastic::MQTT.connect
61
65
  client_id = "!#{mqtt_obj.client_id}"
62
66
  Meshtastic::MQTT.send_text(
63
67
  mqtt_obj: mqtt_obj,
@@ -76,7 +80,7 @@ One of the "gotchas" when sending messages is ensuring you're sending over the p
76
80
 
77
81
  ```ruby
78
82
  require 'meshtastic'
79
- mqtt_obj = Meshastic::MQTT.connect
83
+ mqtt_obj = Meshtastic::MQTT.connect
80
84
  Meshtastic::MQTT.subscribe(
81
85
  mqtt_obj: mqtt_obj,
82
86
  root_topic: 'msh',
@@ -97,6 +101,112 @@ You should see something like this:
97
101
 
98
102
  Note where is says `channel: 93`. This is the `channel` value required to send messages in this particular example.
99
103
 
104
+ ### Serial Interface
105
+
106
+ 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.
107
+
108
+ To inspect available methods and open a serial session:
109
+
110
+ ```ruby
111
+ require 'meshtastic'
112
+ Meshtastic::SerialInterface.help
113
+ serial_obj = Meshtastic::SerialInterface.connect(
114
+ block_dev: '/dev/ttyUSB0', # or /dev/ttyACM0
115
+ baud: 115_200
116
+ )
117
+ puts serial_obj.inspect
118
+ ```
119
+
120
+ This code will dump every FromRadio packet (blocks until CTRL+C):
121
+
122
+ ```ruby
123
+ require 'meshtastic'
124
+ serial_obj = Meshtastic::SerialInterface.connect(
125
+ block_dev: '/dev/ttyUSB0',
126
+ baud: 115_200
127
+ )
128
+ Meshtastic::SerialInterface.subscribe(
129
+ serial_obj: serial_obj
130
+ ) do |message|
131
+ puts message.inspect
132
+ end
133
+ ```
134
+
135
+ Filter with `include` / `exclude` (comma-delimited substrings), same idea as MQTT:
136
+
137
+ ```ruby
138
+ require 'meshtastic'
139
+ serial_obj = Meshtastic::SerialInterface.connect(block_dev: '/dev/ttyUSB0')
140
+ Meshtastic::SerialInterface.subscribe(
141
+ serial_obj: serial_obj,
142
+ include: 'TEXT_MESSAGE_APP',
143
+ exclude: 'TELEMETRY_APP'
144
+ ) do |message|
145
+ puts message.inspect
146
+ end
147
+ ```
148
+
149
+ Sending a message over serial:
150
+
151
+ ```ruby
152
+ require 'meshtastic'
153
+ serial_obj = Meshtastic::SerialInterface.connect(
154
+ block_dev: '/dev/ttyUSB0',
155
+ baud: 115_200
156
+ )
157
+ Meshtastic::SerialInterface.send_text(
158
+ serial_obj: serial_obj,
159
+ to: '!ffffffff', # broadcast; or a node id like '!f33ddad5'
160
+ channel: 0, # primary channel index on the radio
161
+ text: 'Hello over serial!'
162
+ )
163
+ Meshtastic::SerialInterface.disconnect(serial_obj: serial_obj)
164
+ ```
165
+
166
+ A typical end-to-end send + receive session looks like this (connect once, send, then subscribe):
167
+
168
+ ```ruby
169
+ require 'meshtastic'
170
+
171
+ serial_obj = Meshtastic::SerialInterface.connect(
172
+ block_dev: '/dev/ttyUSB0',
173
+ baud: 115_200
174
+ )
175
+
176
+ # Optional: give the device a moment to finish want_config / my_info
177
+ sleep 2
178
+ puts "local node: !#{serial_obj[:my_node_num].to_s(16)}" if serial_obj[:my_node_num]
179
+
180
+ Meshtastic::SerialInterface.send_text(
181
+ serial_obj: serial_obj,
182
+ to: '!ffffffff',
183
+ channel: 0,
184
+ text: 'Hello over serial!'
185
+ )
186
+
187
+ # Blocks; CTRL+C disconnects cleanly
188
+ Meshtastic::SerialInterface.subscribe(serial_obj: serial_obj) do |message|
189
+ puts message.inspect
190
+ end
191
+ ```
192
+
193
+ Non-blocking receive helpers (useful when you drive the loop yourself):
194
+
195
+ ```ruby
196
+ # Pop one framed FromRadio (or nil on timeout)
197
+ fr = Meshtastic::SerialInterface.recv_from_radio(timeout: 2)
198
+ puts fr&.to_h
199
+
200
+ # Drain whatever is already queued
201
+ Meshtastic::SerialInterface.drain_from_radio.each { |fr| puts fr.to_h }
202
+
203
+ # Debug console / proto dumps collected by the RX thread
204
+ puts Meshtastic::SerialInterface.dump_stdout_data(type: :console)
205
+ puts Meshtastic::SerialInterface.dump_stdout_data(type: :proto).size
206
+ ```
207
+
208
+ > **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.
209
+
100
210
  ## Contributing
101
211
 
102
212
  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](https://github.com/0dayinc/meshtastic/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,152 @@
1
+ # Meshtastic
2
+
3
+ Ruby gem for interfacing with Meshtastic nodes / network.
4
+
5
+ # Setting Expectations
6
+
7
+ This gem was created to support alt-comm capabilities w/in a security research framework known as [PWN](https://github.com/0dayInc/pwn). Contributors of this effort cannot guarantee full functionality or support for all Meshtastic features.
8
+
9
+ # Objectives
10
+
11
+ - Consume the latest [Meshtastic Protobof Specs](https://github.com/meshtastic/protobufs) and [auto-generate Ruby protobuf modules for Meshtastic](https://github.com/0dayInc/meshtastic/blob/master/AUTOGEN_meshtastic_protobufs.sh) using the `grpc_tools_ruby_protoc` command: `Complete`
12
+ - Integrate auto-generated Ruby protobuf modules into a working Ruby gem: `Complete`
13
+ - 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`
14
+
15
+ ## Installation
16
+
17
+ Install the gem and add to the application's Gemfile by executing:
18
+
19
+ $ bundle add meshtastic
20
+
21
+ If bundler is not being used to manage dependencies, install the gem by executing:
22
+
23
+ $ gem install meshtastic
24
+
25
+ ## Usage
26
+
27
+ At the moment the only module available is `Meshtastic::MQTT`. To view MQTT messages, and include only messages containing `_APP` _and_ `LongFast` strings, use the following code:
28
+
29
+ ```ruby
30
+ require 'meshtastic'
31
+ Meshtastic::MQTT.help
32
+ mqtt_obj = Meshastic::MQTT.connect
33
+ puts mqtt_obj.inspect
34
+ Meshtastic::MQTT.subscribe(
35
+ mqtt_obj: mqtt_obj,
36
+ include: '_APP, LongFast'
37
+ )
38
+ ```
39
+
40
+ This code will dump the contents of every message:
41
+
42
+ ```ruby
43
+ require 'meshtastic'
44
+ mqtt_obj = Meshastic::MQTT.connect
45
+ Meshtastic::MQTT.subscribe(
46
+ mqtt_obj: mqtt_obj,
47
+ root_topic: 'msh',
48
+ region: 'US',
49
+ topic: '2/e/LongFast/#',
50
+ psks: { LongFast: 'AQ==' }
51
+ ) do |message|
52
+ puts message.inspect
53
+ end
54
+ ```
55
+
56
+ Sending a message over MQTT:
57
+
58
+ ```ruby
59
+ require 'meshtastic'
60
+ mqtt_obj = Meshastic::MQTT.connect
61
+ client_id = "!#{mqtt_obj.client_id}"
62
+ Meshtastic::MQTT.send_text(
63
+ mqtt_obj: mqtt_obj,
64
+ from: client_id,
65
+ to: '!ffffffff',
66
+ root_topic: 'msh',
67
+ region: 'US',
68
+ topic: '2/e/LongFast/#',
69
+ channel: 93,
70
+ text: 'Hello, World!',
71
+ psks: { LongFast: 'AQ==' }
72
+ )
73
+ ```
74
+
75
+ 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:
76
+
77
+ ```ruby
78
+ require 'meshtastic'
79
+ mqtt_obj = Meshastic::MQTT.connect
80
+ Meshtastic::MQTT.subscribe(
81
+ mqtt_obj: mqtt_obj,
82
+ root_topic: 'msh',
83
+ region: 'US',
84
+ topic: '2/e/LongFast/#',
85
+ psks: { LongFast: 'AQ==' },
86
+ include: '!YOUR_CLIENT_ID'
87
+ ) do |message|
88
+ puts message.inspect
89
+ end
90
+ ```
91
+
92
+ You should see something like this:
93
+
94
+ ```
95
+ {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"}
96
+ ```
97
+
98
+ Note where is says `channel: 93`. This is the `channel` value required to send messages in this particular example.
99
+
100
+
101
+ ## Serial Interface
102
+
103
+ Talk directly to a Meshtastic node over USB/UART (`/dev/ttyUSB*`, `/dev/ttyACM*`).
104
+
105
+ ```ruby
106
+ require 'meshtastic'
107
+
108
+ serial_obj = Meshtastic::SerialInterface.connect(
109
+ block_dev: '/dev/ttyUSB0', # or /dev/ttyACM0
110
+ baud: 115_200
111
+ )
112
+
113
+ # Send a text message (device applies channel PSK)
114
+ Meshtastic::SerialInterface.send_text(
115
+ serial_obj: serial_obj,
116
+ to: '!ffffffff',
117
+ channel: 0,
118
+ text: 'Hello over serial!'
119
+ )
120
+
121
+ # Subscribe to FromRadio packets (blocks; CTRL+C to stop)
122
+ Meshtastic::SerialInterface.subscribe(serial_obj: serial_obj) do |msg|
123
+ puts msg.inspect
124
+ end
125
+
126
+ Meshtastic::SerialInterface.disconnect(serial_obj: serial_obj)
127
+ ```
128
+
129
+ Non-blocking helpers:
130
+
131
+ ```ruby
132
+ # Pop one framed FromRadio (or nil on timeout)
133
+ fr = Meshtastic::SerialInterface.recv_from_radio(timeout: 2)
134
+ puts fr&.to_h
135
+
136
+ # Drain whatever is already queued
137
+ Meshtastic::SerialInterface.drain_from_radio.each { |fr| puts fr.to_h }
138
+
139
+ # Debug console / proto dumps collected by the RX thread
140
+ puts Meshtastic::SerialInterface.dump_stdout_data(type: :console)
141
+ puts Meshtastic::SerialInterface.dump_stdout_data(type: :proto).size
142
+ ```
143
+
144
+ > **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.
145
+
146
+ ## Contributing
147
+
148
+ 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](https://github.com/0dayinc/meshtastic/blob/master/CODE_OF_CONDUCT.md).
149
+
150
+ ## Code of Conduct
151
+
152
+ Everyone interacting in the Meshtastic project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/0dayinc/meshtastic/blob/master/CODE_OF_CONDUCT.md).
@@ -213,10 +213,16 @@ module Meshtastic
213
213
  hop_limit = opts[:hop_limit] ||= 3
214
214
 
215
215
  public_psk = '1PG7OiApB1nwvP+rz05pAQ=='
216
- psks = opts[:psks] ||= { LongFast: public_psk }
217
- raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
216
+ # Explicit nil means "do not encrypt" (serial/radio path — device owns PSK).
217
+ if opts.key?(:psks) && opts[:psks].nil?
218
+ psks = nil
219
+ else
220
+ psks = opts[:psks] || { LongFast: public_psk }
221
+ raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
218
222
 
219
- psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
223
+ psks = psks.dup
224
+ psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
225
+ end
220
226
 
221
227
  # my_info = Meshtastic::FromRadio.my_info
222
228
  # wait_connected if to != my_info.my_node_num && my_info.is_a(Meshtastic::Deviceonly::MyInfo)
@@ -228,7 +234,10 @@ module Meshtastic
228
234
  mesh_packet.hop_limit = hop_limit
229
235
  mesh_packet.id = generate_packet_id(last_packet_id: last_packet_id)
230
236
 
231
- if psks
237
+ # When psks is nil/empty, leave the packet decoded so the radio (serial/TCP)
238
+ # device can apply the channel PSK itself. MQTT callers must pass psks so the
239
+ # ServiceEnvelope payload is pre-encrypted for the mesh.
240
+ if psks && !psks.empty?
232
241
  nonce_packet_id = [mesh_packet.id].pack('V').ljust(8, "\x00")
233
242
  nonce_from_node = [from].pack('V').ljust(8, "\x00")
234
243
  nonce = "#{nonce_packet_id}#{nonce_from_node}"
@@ -305,10 +314,15 @@ module Meshtastic
305
314
  raise "ERROR: Invalid port_num" unless port_num.positive? && port_num < max_port_num
306
315
 
307
316
  public_psk = '1PG7OiApB1nwvP+rz05pAQ=='
308
- psks = opts[:psks] ||= { LongFast: public_psk }
309
- raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
317
+ if opts.key?(:psks) && opts[:psks].nil?
318
+ psks = nil
319
+ else
320
+ psks = opts[:psks] || { LongFast: public_psk }
321
+ raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
310
322
 
311
- psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
323
+ psks = psks.dup
324
+ psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
325
+ end
312
326
 
313
327
  data_len = data.payload.length
314
328
  max_len = Meshtastic::Constants::DATA_PAYLOAD_LEN
@@ -369,10 +383,15 @@ module Meshtastic
369
383
  on_response = opts[:on_response]
370
384
 
371
385
  public_psk = '1PG7OiApB1nwvP+rz05pAQ=='
372
- psks = opts[:psks] ||= { LongFast: public_psk }
373
- raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
386
+ if opts.key?(:psks) && opts[:psks].nil?
387
+ psks = nil
388
+ else
389
+ psks = opts[:psks] || { LongFast: public_psk }
390
+ raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
374
391
 
375
- psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
392
+ psks = psks.dup
393
+ psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
394
+ end
376
395
 
377
396
  # TODO: verify text length validity
378
397
  max_txt_len = Meshtastic::Constants::DATA_PAYLOAD_LEN
@@ -381,7 +400,7 @@ module Meshtastic
381
400
  port_num = Meshtastic::PortNum::TEXT_MESSAGE_APP
382
401
 
383
402
  data = Meshtastic::Data.new
384
- data.payload = text.force_encoding('ASCII-8BIT')
403
+ data.payload = text.to_s.dup.force_encoding('ASCII-8BIT')
385
404
  data.portnum = port_num
386
405
  data.want_response = want_response
387
406
  # puts data.to_h