meshtastic 0.0.182 → 0.0.183

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.
@@ -27,9 +27,68 @@ end
27
27
  describe Meshtastic::Admin do
28
28
  include AdminSpecHelpers
29
29
 
30
+ it 'classifies actual connected transport shapes with TCP taking precedence over serial framing' do
31
+ serial = fake_serial_obj
32
+ expect(described_class.transport_type(transport_obj: serial)).to eq(:serial)
33
+ expect(described_class.transport_type(transport_obj: serial.merge(tcp_socket: serial[:serial_conn]))).to eq(:tcp)
34
+ expect(described_class.transport_type(transport_obj: { bluetooth_conn: Object.new })).to eq(:bluetooth)
35
+ expect(described_class.transport_type(transport_obj: MQTTClient.new)).to eq(:mqtt)
36
+ end
37
+
38
+ it 'rejects missing, unknown and ambiguous transport handles' do
39
+ [nil, Object.new, {}, { serial_conn: nil }, { tcp_socket: Object.new }, { tcp_socket: nil, serial_conn: Object.new },
40
+ { bluetooth_conn: Object.new, serial_conn: Object.new },
41
+ { bluetooth_conn: Object.new, tcp_socket: Object.new, serial_conn: Object.new }].each do |handle|
42
+ expect { described_class.transport_type(transport_obj: handle) }.to raise_error(ArgumentError, /transport_obj/)
43
+ end
44
+ end
45
+
46
+ it 'rejects legacy transport options with migration guidance even alongside transport_obj' do
47
+ %i[serial_obj bluetooth_obj tcp_obj mqtt_obj].each do |key|
48
+ %i[transport_type encode decode response send request reboot].each do |method|
49
+ [nil, fake_serial_obj].each do |handle|
50
+ options = { key => nil, transport_obj: handle }
51
+ options[:get_owner_request] = true unless %i[transport_type reboot].include?(method)
52
+ expect { described_class.public_send(method, options) }.to raise_error(ArgumentError, /#{key}.*use transport_obj:/)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ it 'maps only the inferred transport key at the delivery boundary without changing caller options' do
59
+ serial = fake_serial_obj
60
+ handles = { serial: serial, tcp: serial.merge(tcp_socket: serial[:serial_conn]),
61
+ bluetooth: { bluetooth_conn: Object.new, my_node_num: 0xb0b }, mqtt: MQTTClient.new }
62
+ handles.each do |type, handle|
63
+ options = { transport_obj: handle, to: '!aabbccdd', get_owner_request: true }
64
+ expect(Meshtastic).to receive(:deliver_data) do |delivery|
65
+ expect(delivery.keys & %i[serial_obj bluetooth_obj tcp_obj mqtt_obj]).to eq([:"#{type}_obj"])
66
+ expect(delivery[:"#{type}_obj"]).to equal(handle)
67
+ expect(delivery).not_to have_key(:transport_obj)
68
+ end
69
+ described_class.send(options)
70
+ expect(options).to eq(transport_obj: handle, to: '!aabbccdd', get_owner_request: true)
71
+ end
72
+ end
73
+
74
+ it 'publishes MQTT Admin protobufs with an explicit passkey but rejects synchronous readback and PKI' do
75
+ client = MQTTClient.new(client_id: 'aabbccdd')
76
+ published = []
77
+ client.define_singleton_method(:publish) { |topic, bytes| published << [topic, bytes] }
78
+ options = { transport_obj: client, to: '!aabbccee', session_passkey: '12345678', psks: { LongFast: 'AQ==' } }
79
+ described_class.reboot(options)
80
+ expect(published.length).to eq(1)
81
+ envelope = Meshtastic::ServiceEnvelope.decode(published.first.last)
82
+ expect(envelope.packet.to).to eq(0xaabbccee)
83
+ expect(envelope.packet.encrypted).not_to be_empty
84
+ expect { described_class.request(options.merge(get_owner_request: true)) }.to raise_error(ArgumentError, /receive queue/)
85
+ expect { described_class.reboot(options.merge(pki_encrypted: true)) }.to raise_error(ArgumentError, /PKI|pki|public.key/)
86
+ expect(published.length).to eq(1)
87
+ end
88
+
30
89
  it 'sends a reboot AdminMessage on ADMIN_APP' do
31
90
  serial_obj = fake_serial_obj
32
- described_class.reboot(serial_obj: serial_obj, seconds: 7)
91
+ described_class.reboot(transport_obj: serial_obj, seconds: 7)
33
92
  packet, admin = decode_admin(serial_obj)
34
93
  expect(packet.decoded.portnum).to eq(:ADMIN_APP)
35
94
  expect(admin.reboot_seconds).to eq(7)
@@ -37,7 +96,7 @@ describe Meshtastic::Admin do
37
96
 
38
97
  it 'sends a set_owner AdminMessage' do
39
98
  serial_obj = fake_serial_obj
40
- described_class.set_owner(serial_obj: serial_obj, long_name: 'Test Node', short_name: 'TN')
99
+ described_class.set_owner(transport_obj: serial_obj, long_name: 'Test Node', short_name: 'TN')
41
100
  _packet, admin = decode_admin(serial_obj)
42
101
  expect(admin.set_owner.long_name).to eq('Test Node')
43
102
  expect(admin.set_owner.short_name).to eq('TN')
@@ -62,7 +121,7 @@ describe Meshtastic::Admin do
62
121
  }
63
122
  examples.each do |meth, (field, expected)|
64
123
  serial_obj[:written].clear
65
- described_class.public_send(meth, serial_obj: serial_obj)
124
+ described_class.public_send(meth, transport_obj: serial_obj)
66
125
  _packet, admin = decode_admin(serial_obj)
67
126
  expect(admin.public_send(field)).to eq(expected), meth.to_s
68
127
  end
@@ -70,16 +129,16 @@ describe Meshtastic::Admin do
70
129
 
71
130
  it 'sends factory resets, file delete, favorites, and edit transactions' do
72
131
  serial_obj = fake_serial_obj
73
- described_class.factory_reset_config(serial_obj: serial_obj)
132
+ described_class.factory_reset_config(transport_obj: serial_obj)
74
133
  expect(decode_admin(serial_obj).last.factory_reset_config).to eq(1)
75
134
  serial_obj[:written].clear
76
- described_class.factory_reset_device(serial_obj: serial_obj)
135
+ described_class.factory_reset_device(transport_obj: serial_obj)
77
136
  expect(decode_admin(serial_obj).last.factory_reset_device).to eq(1)
78
137
  serial_obj[:written].clear
79
- described_class.delete_file(serial_obj: serial_obj, path: '/prefs.json')
138
+ described_class.delete_file(transport_obj: serial_obj, path: '/prefs.json')
80
139
  expect(decode_admin(serial_obj).last.delete_file_request).to eq('/prefs.json')
81
140
  serial_obj[:written].clear
82
- described_class.set_favorite_node(serial_obj: serial_obj, node_num: 0xb0b)
141
+ described_class.set_favorite_node(transport_obj: serial_obj, node_num: 0xb0b)
83
142
  expect(decode_admin(serial_obj).last.set_favorite_node).to eq(0xb0b)
84
143
  end
85
144
  end
@@ -117,11 +176,11 @@ describe Meshtastic::Admin, 'automatic sessions' do
117
176
  reply = Meshtastic::AdminMessage.new(get_config_response: Meshtastic::Config.new, session_passkey: '12345678')
118
177
  connection[:from_radio_queue] << admin_reply(packet, message: reply)
119
178
  end
120
- 2.times { described_class.set_owner(serial_obj: handle, to: '!aabbccdd', long_name: 'Remote', timeout: 0.2) }
179
+ 2.times { described_class.set_owner(transport_obj: handle, to: '!aabbccdd', long_name: 'Remote', timeout: 0.2) }
121
180
  messages = handle[:sent].map { |packet| Meshtastic::AdminMessage.decode(packet.decoded.payload) }
122
181
  expect(messages.map(&:payload_variant)).to eq(%i[get_config_request set_owner set_owner])
123
182
  expect(messages.drop(1).map(&:session_passkey)).to eq(%w[12345678 12345678])
124
- described_class.set_owner(serial_obj: handle, to: '!aabbccee', long_name: 'Other', timeout: 0.2)
183
+ described_class.set_owner(transport_obj: handle, to: '!aabbccee', long_name: 'Other', timeout: 0.2)
125
184
  expect(handle[:sent].length).to eq(5)
126
185
  end
127
186
 
@@ -137,7 +196,7 @@ describe Meshtastic::Admin, 'automatic sessions' do
137
196
  decoded: Meshtastic::Data.new(portnum: :ROUTING_APP, request_id: packet.id, payload: Meshtastic::Routing.new(error_reason: reason).to_proto)))
138
197
  end
139
198
  end
140
- options = { serial_obj: handle, to: '!aabbccdd', reboot_seconds: 5, timeout: 0.2 }
199
+ options = { transport_obj: handle, to: '!aabbccdd', reboot_seconds: 5, timeout: 0.2 }
141
200
  expect(described_class.request(options)).to include(variant: :routing, value: :NONE)
142
201
  reason = :ADMIN_BAD_SESSION_KEY
143
202
  expect { described_class.request(options) }.to raise_error(Meshtastic::Admin::RoutingError)
@@ -156,7 +215,7 @@ describe Meshtastic::Admin, 'automatic sessions' do
156
215
  reply = Meshtastic::AdminMessage.new(get_config_response: Meshtastic::Config.new, session_passkey: key)
157
216
  connection[:from_radio_queue] << admin_reply(packet, message: reply)
158
217
  end
159
- options = { serial_obj: handle, to: '!aabbccdd', timeout: 0.5 }
218
+ options = { transport_obj: handle, to: '!aabbccdd', timeout: 0.5 }
160
219
  described_class.request(options.merge(get_config_request: :SESSIONKEY_CONFIG))
161
220
  described_class.reboot(options)
162
221
  expect(handle[:sent].length).to eq(2)
@@ -174,7 +233,7 @@ describe Meshtastic::Admin, 'automatic sessions' do
174
233
  connection[:from_radio_queue] << admin_reply(packet, message: Meshtastic::AdminMessage.new(get_config_response: Meshtastic::Config.new))
175
234
  end
176
235
  handle[:admin_sessions] = { 0xaabbccdd => { key: 'old-key!', expires_at: Process.clock_gettime(Process::CLOCK_MONOTONIC) + 100 } }
177
- options = { serial_obj: handle, to: '!aabbccdd', timeout: 0.2 }
236
+ options = { transport_obj: handle, to: '!aabbccdd', timeout: 0.2 }
178
237
  expect { described_class.reboot(options.merge(refresh_session: true)) }.to raise_error(ArgumentError, /eight-byte/)
179
238
  expect { described_class.reboot(options) }.to raise_error(ArgumentError, /eight-byte/)
180
239
  expect(handle[:sent].map { |packet| Meshtastic::AdminMessage.decode(packet.decoded.payload).payload_variant }).to eq(%i[get_config_request get_config_request])
@@ -184,7 +243,7 @@ describe Meshtastic::Admin, 'automatic sessions' do
184
243
  handle = responding_serial { |_connection, _packet| nil }
185
244
  expect do
186
245
  Timeout.timeout(0.5, RuntimeError, 'outer deadline exceeded') do
187
- described_class.request(serial_obj: handle, to: '!aabbccdd', reboot_seconds: 5, wait: false, timeout: 0.15)
246
+ described_class.request(transport_obj: handle, to: '!aabbccdd', reboot_seconds: 5, wait: false, timeout: 0.15)
188
247
  end
189
248
  end.to raise_error(Timeout::Error, /Admin response/)
190
249
  expect(handle[:sent].length).to eq(1)
@@ -198,11 +257,11 @@ describe Meshtastic::Admin, 'automatic sessions' do
198
257
  release.pop
199
258
  connection[:from_radio_queue] << admin_reply(packet)
200
259
  end
201
- worker = Thread.new { described_class.request(serial_obj: handle, get_device_metadata_request: true, timeout: 1) }
260
+ worker = Thread.new { described_class.request(transport_obj: handle, get_device_metadata_request: true, timeout: 1) }
202
261
  entered.pop
203
262
  begin
204
263
  expect do
205
- Timeout.timeout(0.3) { described_class.request(serial_obj: handle, get_device_metadata_request: true, timeout: 0.1) }
264
+ Timeout.timeout(0.3) { described_class.request(transport_obj: handle, get_device_metadata_request: true, timeout: 0.1) }
206
265
  end.to raise_error(IOError, /already active/)
207
266
  ensure
208
267
  release << true
@@ -224,14 +283,16 @@ describe Meshtastic::Admin, 'synchronous requests' do
224
283
  connection[:from_radio_queue] << unrelated
225
284
  connection[:from_radio_queue] << admin_reply(packet)
226
285
  end
227
- expect(described_class.request(serial_obj: handle, get_device_metadata_request: true, timeout: 0.2)[:value].firmware_version).to eq('test-version')
286
+ expect(described_class.request(transport_obj: handle, get_device_metadata_request: true, timeout: 0.2)[:value].firmware_version).to eq('test-version')
228
287
  expect(handle[:from_radio_queue].pop(true)).to eq(unrelated)
229
288
  end
230
289
 
231
290
  %i[tcp_obj bluetooth_obj].each do |transport|
232
291
  it "reads correlated replies through the real #{transport} send path" do
233
292
  handle = if transport == :tcp_obj
234
- responding_serial { |connection, packet| connection[:from_radio_queue] << admin_reply(packet) }
293
+ responding_serial { |connection, packet| connection[:from_radio_queue] << admin_reply(packet) }.tap do |tcp|
294
+ tcp[:tcp_socket] = tcp[:serial_conn]
295
+ end
235
296
  else
236
297
  { my_node_num: 0xb0b, from_radio_queue: Queue.new, tx_mutex: Mutex.new, bluetooth_conn: Object.new }
237
298
  end
@@ -243,7 +304,7 @@ describe Meshtastic::Admin, 'synchronous requests' do
243
304
  bytes.bytesize
244
305
  end
245
306
  end
246
- result = described_class.request(transport => handle, get_device_metadata_request: true, timeout: 0.5)
307
+ result = described_class.request(transport_obj: handle, get_device_metadata_request: true, timeout: 0.5)
247
308
  expect(result[:value].firmware_version).to eq('test-version')
248
309
  end
249
310
  end
@@ -257,7 +318,7 @@ describe Meshtastic::Admin, 'synchronous requests' do
257
318
  preserved.each { |message| connection[:from_radio_queue] << message }
258
319
  end
259
320
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
260
- expect { described_class.request(serial_obj: handle, get_device_metadata_request: true, timeout: 0.15) }.to raise_error(Timeout::Error)
321
+ expect { described_class.request(transport_obj: handle, get_device_metadata_request: true, timeout: 0.15) }.to raise_error(Timeout::Error)
261
322
  expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).to be < 0.6
262
323
  expect(preserved.map { handle[:from_radio_queue].pop(true) }).to eq(preserved)
263
324
  end
@@ -267,18 +328,18 @@ describe Meshtastic::Admin, 'synchronous requests' do
267
328
  connection[:from_radio_queue] << admin_reply(packet, message: Meshtastic::AdminMessage.new(get_config_response: Meshtastic::Config.new))
268
329
  end
269
330
  expect do
270
- described_class.reboot(serial_obj: handle, to: '!aabbccdd', timeout: 0.2)
331
+ described_class.reboot(transport_obj: handle, to: '!aabbccdd', timeout: 0.2)
271
332
  end.to raise_error(ArgumentError, /eight-byte passkey/)
272
333
  expect(handle[:sent].map { |packet| Meshtastic::AdminMessage.decode(packet.decoded.payload).payload_variant }).to eq([:get_config_request])
273
334
  expect do
274
- described_class.reboot(mqtt_obj: Object.new, to: '!aabbccdd')
335
+ described_class.reboot(transport_obj: MQTTClient.new, to: '!aabbccdd')
275
336
  end.to raise_error(ArgumentError, /supply session_passkey/)
276
337
  end
277
338
 
278
339
  it 'rejects invalid timeouts before submitting any data' do
279
340
  handle = responding_serial { |_connection, _packet| raise 'must not send' }
280
341
  [0, -1, nil, Float::INFINITY, Float::NAN, '1'].each do |timeout|
281
- expect { described_class.request(serial_obj: handle, get_owner_request: true, timeout: timeout) }.to raise_error(ArgumentError, /timeout/)
342
+ expect { described_class.request(transport_obj: handle, get_owner_request: true, timeout: timeout) }.to raise_error(ArgumentError, /timeout/)
282
343
  end
283
344
  expect(handle[:sent]).to be_empty
284
345
  end
@@ -290,7 +351,7 @@ describe Meshtastic::Admin, 'synchronous requests' do
290
351
  connection[:from_radio_queue].close
291
352
  end
292
353
  expect do
293
- described_class.request(serial_obj: handle, get_owner_request: true, timeout: 0.2)
354
+ described_class.request(transport_obj: handle, get_owner_request: true, timeout: 0.2)
294
355
  end.to raise_error(Timeout::Error)
295
356
  expect(handle[:from_radio_queue].pop(true)).to eq(unrelated)
296
357
  expect(handle[:from_radio_queue]).to be_closed
@@ -306,7 +367,7 @@ describe Meshtastic::Admin, 'synchronous requests' do
306
367
  end
307
368
  end
308
369
  expect do
309
- described_class.request(serial_obj: handle, to: '!aabbccdd', get_device_metadata_request: true, timeout: 0.1)
370
+ described_class.request(transport_obj: handle, to: '!aabbccdd', get_device_metadata_request: true, timeout: 0.1)
310
371
  end.to raise_error(Meshtastic::Admin::RoutingError, /NOT_AUTHORIZED/)
311
372
  expect(handle[:from_radio_queue].size).to eq(1)
312
373
  end
@@ -320,7 +381,7 @@ describe Meshtastic::Admin, 'synchronous requests' do
320
381
  unrelated.each { |message| connection[:from_radio_queue] << message }
321
382
  connection[:from_radio_queue] << admin_reply(packet)
322
383
  end
323
- result = described_class.request(serial_obj: handle, message: Meshtastic::AdminMessage.new(get_device_metadata_request: true), timeout: 0.2)
384
+ result = described_class.request(transport_obj: handle, message: Meshtastic::AdminMessage.new(get_device_metadata_request: true), timeout: 0.2)
324
385
  expect(result[:value].firmware_version).to eq('test-version')
325
386
  expect(result[:request_id]).to eq(handle[:sent].last.id)
326
387
  expect(unrelated.map { handle[:from_radio_queue].pop(true) }).to eq(unrelated)
@@ -352,11 +413,11 @@ describe Meshtastic::Admin, 'validation and response handling' do
352
413
  serial_obj = fake_serial_obj
353
414
  [0, 7].each do |index|
354
415
  serial_obj[:written].clear
355
- described_class.get_channel(serial_obj: serial_obj, index: index)
416
+ described_class.get_channel(transport_obj: serial_obj, index: index)
356
417
  expect(decode_admin(serial_obj).last.get_channel_request).to eq(index + 1)
357
418
  end
358
419
  [-1, 8, '1', 1.5].each do |index|
359
- expect { described_class.get_channel(serial_obj: serial_obj, index: index) }.to raise_error(ArgumentError)
420
+ expect { described_class.get_channel(transport_obj: serial_obj, index: index) }.to raise_error(ArgumentError)
360
421
  end
361
422
  end
362
423
 
@@ -376,20 +437,20 @@ describe Meshtastic::Admin, 'validation and response handling' do
376
437
 
377
438
  it 'targets the connected node locally and requests replies only for getters by default' do
378
439
  serial_obj = fake_serial_obj
379
- described_class.reboot(serial_obj: serial_obj)
440
+ described_class.reboot(transport_obj: serial_obj)
380
441
  packet, = decode_admin(serial_obj)
381
442
  expect(packet.to).to eq(0xb0b)
382
443
  expect(packet.from).to eq(0)
383
444
  expect(packet.decoded.want_response).to be(false)
384
445
  serial_obj[:written].clear
385
- described_class.get_owner(serial_obj: serial_obj)
446
+ described_class.get_owner(transport_obj: serial_obj)
386
447
  expect(decode_admin(serial_obj).first.decoded.want_response).to be(true)
387
448
  serial_obj[:written].clear
388
- described_class.reboot(serial_obj: serial_obj, to: '!aabbccdd', want_response: true, auto_session: false)
449
+ described_class.reboot(transport_obj: serial_obj, to: '!aabbccdd', want_response: true, auto_session: false)
389
450
  expect(decode_admin(serial_obj).first.to).to eq(0xaabbccdd)
390
451
  expect(decode_admin(serial_obj).first.decoded.want_response).to be(true)
391
- expect { described_class.get_owner(mqtt_obj: Object.new) }.to raise_error(ArgumentError, /destination/)
392
- expect { described_class.get_owner(serial_obj: serial_obj, to: '!ffffffff') }.to raise_error(ArgumentError, /destination/)
452
+ expect { described_class.get_owner(transport_obj: MQTTClient.new) }.to raise_error(ArgumentError, /destination/)
453
+ expect { described_class.get_owner(transport_obj: serial_obj, to: '!ffffffff') }.to raise_error(ArgumentError, /destination/)
393
454
  end
394
455
 
395
456
  it 'decodes admin responses and correlates without consuming transport queues' do
@@ -410,13 +471,13 @@ describe Meshtastic::Admin, 'validation and response handling' do
410
471
  it 'supports OTA wire operations and resetting all nodes explicitly' do
411
472
  serial_obj = fake_serial_obj
412
473
  event = Meshtastic::AdminMessage::OTAEvent.new(reboot_ota_mode: :OTA_BLE, ota_hash: 'x' * 32)
413
- described_class.ota_request(serial_obj: serial_obj, event: event)
474
+ described_class.ota_request(transport_obj: serial_obj, event: event)
414
475
  expect(decode_admin(serial_obj).last.ota_request).to eq(event)
415
476
  serial_obj[:written].clear
416
- described_class.reboot_ota(serial_obj: serial_obj, seconds: -1)
477
+ described_class.reboot_ota(transport_obj: serial_obj, seconds: -1)
417
478
  expect(decode_admin(serial_obj).last.reboot_ota_seconds).to eq(-1)
418
479
  serial_obj[:written].clear
419
- described_class.nodedb_reset(serial_obj: serial_obj, preserve_favorites: false)
480
+ described_class.nodedb_reset(transport_obj: serial_obj, preserve_favorites: false)
420
481
  expect(decode_admin(serial_obj).last.payload_variant).to eq(:nodedb_reset)
421
482
  expect(decode_admin(serial_obj).last.nodedb_reset).to be(false)
422
483
  end
@@ -424,21 +485,21 @@ describe Meshtastic::Admin, 'validation and response handling' do
424
485
  it 'does not turn omitted required values into zero or empty destructive commands' do
425
486
  serial_obj = fake_serial_obj
426
487
  %i[delete_file set_scale set_time set_canned_messages set_ringtone remove_by_nodenum set_favorite_node remove_favorite_node set_ignored_node remove_ignored_node toggle_muted_node].each do |method|
427
- expect { described_class.public_send(method, serial_obj: serial_obj) }.to raise_error(ArgumentError), method.to_s
488
+ expect { described_class.public_send(method, transport_obj: serial_obj) }.to raise_error(ArgumentError), method.to_s
428
489
  end
429
490
  expect(serial_obj[:written]).to be_empty
430
- described_class.set_canned_messages(serial_obj: serial_obj, messages: '')
491
+ described_class.set_canned_messages(transport_obj: serial_obj, messages: '')
431
492
  expect(decode_admin(serial_obj).last.payload_variant).to eq(:set_canned_message_module_messages)
432
493
  end
433
494
 
434
495
  it 'returns the actual transmitted request ID for later response correlation' do
435
496
  serial_obj = fake_serial_obj
436
- result = described_class.request(serial_obj: serial_obj, get_owner_request: true, request_id: 1234, wait: false)
497
+ result = described_class.request(transport_obj: serial_obj, get_owner_request: true, request_id: 1234, wait: false)
437
498
  expect(result[:request_id]).to eq(1234)
438
499
  expect(decode_admin(serial_obj).first.id).to eq(1234)
439
- expect { described_class.request(serial_obj: serial_obj, get_owner_request: true, request_id: 0) }.to raise_error(ArgumentError)
500
+ expect { described_class.request(transport_obj: serial_obj, get_owner_request: true, request_id: 0) }.to raise_error(ArgumentError)
440
501
  serial_obj[:written].clear
441
- result = described_class.request(serial_obj: serial_obj, get_config_request: :SESSIONKEY_CONFIG, wait: false)
502
+ result = described_class.request(transport_obj: serial_obj, get_config_request: :SESSIONKEY_CONFIG, wait: false)
442
503
  expect(result[:request_id]).to eq(decode_admin(serial_obj).first.id)
443
504
  expect(result[:request_id]).to be_positive
444
505
  end
@@ -450,6 +511,11 @@ describe Meshtastic::Admin, 'validation and response handling' do
450
511
  expect(described_class.encode(get_owner_request: true, session_passkey: "\x00" * 8).session_passkey.bytesize).to eq(8)
451
512
  end
452
513
 
514
+ it 'documents the public classifier and the sole Admin transport option' do
515
+ expect { described_class.help }.to output(/Meshtastic::Admin.transport_type\(.*transport_obj:.*:serial.*:bluetooth.*:tcp.*:mqtt/m).to_stdout
516
+ expect { described_class.help }.not_to output(/(?:serial_obj|bluetooth_obj|tcp_obj|mqtt_obj):/).to_stdout
517
+ end
518
+
453
519
  it 'prints usage without raising' do
454
520
  expect { described_class.help }.to output(/USAGE/).to_stdout
455
521
  end
@@ -3,6 +3,53 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Meshtastic::ModuleConfig do
6
+ %i[serial_obj bluetooth_obj tcp_obj mqtt_obj transport_obj].each do |key|
7
+ it "translates #{key} to the Admin connection option without mutating options" do
8
+ connection = { marker: Object.new }
9
+ options = { key => connection, to: '!aabbccdd' }.freeze
10
+ expect(Meshtastic::Admin).to receive(:send).with({ transport_obj: connection, to: '!aabbccdd', get_module_config_request: :MQTT_CONFIG }).and_return(:submitted)
11
+ expect(described_class.get(options)).to eq(:submitted)
12
+ end
13
+
14
+ it "translates #{key} for setters" do
15
+ connection = { marker: Object.new }
16
+ value = Meshtastic::ModuleConfig.new
17
+ expect(Meshtastic::Admin).to receive(:send).with({ transport_obj: connection, module_config: value, set_module_config: value }).and_return(:submitted)
18
+ expect(described_class.set({ key => connection, module_config: value }.freeze)).to eq(:submitted)
19
+ end
20
+ end
21
+
22
+ %i[get set].each do |operation|
23
+ %i[transport_obj serial_obj bluetooth_obj tcp_obj mqtt_obj].combination(2) do |first, second|
24
+ it "rejects ambiguous #{first}/#{second} connections for #{operation}" do
25
+ connection = fake_serial_obj
26
+ expect(Meshtastic::Admin).not_to receive(:send)
27
+ expect { described_class.public_send(operation, { first => connection, second => connection, module_config: Meshtastic::ModuleConfig.new }) }
28
+ .to raise_error(ArgumentError, /connection|transport/i)
29
+ expect(connection[:written]).to be_empty
30
+ end
31
+ end
32
+ end
33
+
34
+ it 'documents the canonical connection option in help' do
35
+ expect { described_class.help }.to output(/transport_obj: connection/).to_stdout
36
+ end
37
+
38
+ %i[serial_obj transport_obj].each do |key|
39
+ %i[get set].each do |operation|
40
+ it "encodes a real Admin packet for #{operation} with #{key}" do
41
+ connection = fake_serial_obj
42
+ options = { key => connection, module_config: Meshtastic::ModuleConfig.new }
43
+ options[:bluetooth_obj] = nil
44
+ described_class.public_send(operation, options.freeze)
45
+ frame = connection[:written]
46
+ packet = Meshtastic::ToRadio.decode(frame.byteslice(4, (frame.getbyte(2) << 8) + frame.getbyte(3))).packet
47
+ admin = Meshtastic::AdminMessage.decode(packet.decoded.payload)
48
+ expect(admin.payload_variant).to eq(operation == :get ? :get_module_config_request : :set_module_config)
49
+ end
50
+ end
51
+ end
52
+
6
53
  def fake_serial_obj
7
54
  written = +''.b
8
55
  serial_conn = Object.new
@@ -3,6 +3,53 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Meshtastic::RTTTL do
6
+ %i[serial_obj bluetooth_obj tcp_obj mqtt_obj transport_obj].each do |key|
7
+ it "translates #{key} to the Admin connection option without mutating options" do
8
+ connection = { marker: Object.new }
9
+ options = { key => connection, to: '!aabbccdd' }.freeze
10
+ expect(Meshtastic::Admin).to receive(:send).with({ transport_obj: connection, to: '!aabbccdd', get_ringtone_request: true }).and_return(:submitted)
11
+ expect(described_class.get(options)).to eq(:submitted)
12
+ end
13
+
14
+ it "translates #{key} for setters" do
15
+ connection = { marker: Object.new }
16
+ value = 'beep:d=4,o=5,b=120:16c6'
17
+ expect(Meshtastic::Admin).to receive(:send).with({ transport_obj: connection, ringtone: value, set_ringtone_message: value }).and_return(:submitted)
18
+ expect(described_class.set({ key => connection, ringtone: value }.freeze)).to eq(:submitted)
19
+ end
20
+ end
21
+
22
+ %i[get set].each do |operation|
23
+ %i[transport_obj serial_obj bluetooth_obj tcp_obj mqtt_obj].combination(2) do |first, second|
24
+ it "rejects ambiguous #{first}/#{second} connections for #{operation}" do
25
+ connection = fake_serial_obj
26
+ expect(Meshtastic::Admin).not_to receive(:send)
27
+ expect { described_class.public_send(operation, { first => connection, second => connection, ringtone: 'beep' }) }
28
+ .to raise_error(ArgumentError, /connection|transport/i)
29
+ expect(connection[:written]).to be_empty
30
+ end
31
+ end
32
+ end
33
+
34
+ it 'documents the canonical connection option in help' do
35
+ expect { described_class.help }.to output(/transport_obj: connection/).to_stdout
36
+ end
37
+
38
+ %i[serial_obj transport_obj].each do |key|
39
+ %i[get set].each do |operation|
40
+ it "encodes a real Admin packet for #{operation} with #{key}" do
41
+ connection = fake_serial_obj
42
+ options = { key => connection, ringtone: 'beep' }
43
+ options[:bluetooth_obj] = nil
44
+ described_class.public_send(operation, options.freeze)
45
+ frame = connection[:written]
46
+ packet = Meshtastic::ToRadio.decode(frame.byteslice(4, (frame.getbyte(2) << 8) + frame.getbyte(3))).packet
47
+ admin = Meshtastic::AdminMessage.decode(packet.decoded.payload)
48
+ expect(admin.payload_variant).to eq(operation == :get ? :get_ringtone_request : :set_ringtone_message)
49
+ end
50
+ end
51
+ end
52
+
6
53
  def fake_serial_obj
7
54
  written = +''.b
8
55
  serial_conn = Object.new
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meshtastic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.182
4
+ version: 0.0.183
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -287,8 +287,10 @@ files:
287
287
  - documentation/README.md
288
288
  - documentation/admin-channel.md
289
289
  - documentation/admin-config.md
290
+ - documentation/admin-firmware-hex.md
290
291
  - documentation/admin-firmware-nordic.md
291
292
  - documentation/admin-firmware-serial.md
293
+ - documentation/admin-firmware-uf2.md
292
294
  - documentation/admin-firmware.md
293
295
  - documentation/admin.md
294
296
  - documentation/apponly.md
@@ -327,8 +329,10 @@ files:
327
329
  - lib/meshtastic/admin/config.rb
328
330
  - lib/meshtastic/admin/firmware.rb
329
331
  - lib/meshtastic/admin/firmware/ble.rb
332
+ - lib/meshtastic/admin/firmware/hex.rb
330
333
  - lib/meshtastic/admin/firmware/nordic_dfu.rb
331
334
  - lib/meshtastic/admin/firmware/serial_bootloader.rb
335
+ - lib/meshtastic/admin/firmware/uf2.rb
332
336
  - lib/meshtastic/admin_pb.rb
333
337
  - lib/meshtastic/apponly.rb
334
338
  - lib/meshtastic/apponly_pb.rb
@@ -393,8 +397,10 @@ files:
393
397
  - spec/lib/meshtastic/admin/channel_spec.rb
394
398
  - spec/lib/meshtastic/admin/config_spec.rb
395
399
  - spec/lib/meshtastic/admin/firmware/ble_spec.rb
400
+ - spec/lib/meshtastic/admin/firmware/hex_spec.rb
396
401
  - spec/lib/meshtastic/admin/firmware/nordic_dfu_spec.rb
397
402
  - spec/lib/meshtastic/admin/firmware/serial_bootloader_spec.rb
403
+ - spec/lib/meshtastic/admin/firmware/uf2_spec.rb
398
404
  - spec/lib/meshtastic/admin/firmware_spec.rb
399
405
  - spec/lib/meshtastic/admin_pb_spec.rb
400
406
  - spec/lib/meshtastic/admin_spec.rb