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.
- checksums.yaml +4 -4
- data/documentation/README.md +4 -0
- data/documentation/admin-channel.md +6 -6
- data/documentation/admin-config.md +5 -5
- data/documentation/admin-firmware-hex.md +57 -0
- data/documentation/admin-firmware-serial.md +1 -1
- data/documentation/admin-firmware-uf2.md +94 -0
- data/documentation/admin-firmware.md +52 -10
- data/documentation/admin.md +8 -2
- data/documentation/module-config.md +8 -4
- data/documentation/rtttl.md +8 -4
- data/lib/meshtastic/admin/channel.rb +6 -3
- data/lib/meshtastic/admin/config.rb +14 -14
- data/lib/meshtastic/admin/firmware/hex.rb +209 -0
- data/lib/meshtastic/admin/firmware/uf2.rb +166 -0
- data/lib/meshtastic/admin/firmware.rb +55 -13
- data/lib/meshtastic/admin.rb +58 -23
- data/lib/meshtastic/module_config.rb +22 -4
- data/lib/meshtastic/rtttl.rb +22 -4
- data/lib/meshtastic/version.rb +1 -1
- data/spec/lib/meshtastic/admin/channel_spec.rb +23 -9
- data/spec/lib/meshtastic/admin/config_spec.rb +28 -12
- data/spec/lib/meshtastic/admin/firmware/hex_spec.rb +160 -0
- data/spec/lib/meshtastic/admin/firmware/uf2_spec.rb +234 -0
- data/spec/lib/meshtastic/admin/firmware_spec.rb +106 -3
- data/spec/lib/meshtastic/admin_spec.rb +106 -40
- data/spec/lib/meshtastic/module_config_spec.rb +47 -0
- data/spec/lib/meshtastic/rtttl_spec.rb +47 -0
- metadata +7 -1
data/lib/meshtastic/admin.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'timeout'
|
|
4
4
|
require 'monitor'
|
|
5
5
|
require 'meshtastic/admin_pb'
|
|
6
|
+
require 'meshtastic/mqtt'
|
|
6
7
|
|
|
7
8
|
module Meshtastic
|
|
8
9
|
module Admin
|
|
@@ -18,7 +19,7 @@ module Meshtastic
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
SKIP = %i[
|
|
21
|
-
message
|
|
22
|
+
message transport_obj to from channel want_ack hop_limit
|
|
22
23
|
want_response port_num data via psks seconds owner long_name short_name index config_type
|
|
23
24
|
module_config_type path node_num lat lon altitude time channel_settings channel_pb
|
|
24
25
|
config module_config messages ringtone scale location event event_code kb_char touch_x
|
|
@@ -27,7 +28,26 @@ module Meshtastic
|
|
|
27
28
|
].freeze
|
|
28
29
|
PAYLOAD_FIELDS = Meshtastic::AdminMessage.descriptor.lookup_oneof('payload_variant').map { |field| field.name.to_sym }.freeze
|
|
29
30
|
|
|
31
|
+
# Classify a handle returned by a transport's connect method.
|
|
32
|
+
public_class_method def self.transport_type(opts = {})
|
|
33
|
+
reject_legacy_transport_options(opts)
|
|
34
|
+
transport = opts[:transport_obj]
|
|
35
|
+
return :mqtt if transport.is_a?(MQTTClient)
|
|
36
|
+
raise ArgumentError, 'transport_obj has ambiguous transport handle keys' if transport.is_a?(Hash) && transport.key?(:bluetooth_conn) && (transport.key?(:serial_conn) || transport.key?(:tcp_socket))
|
|
37
|
+
|
|
38
|
+
if transport.is_a?(Hash) && transport.key?(:tcp_socket)
|
|
39
|
+
raise ArgumentError, 'transport_obj TCP handle requires tcp_socket and serial_conn' unless transport[:tcp_socket] && transport[:serial_conn]
|
|
40
|
+
|
|
41
|
+
return :tcp
|
|
42
|
+
end
|
|
43
|
+
return :bluetooth if transport.is_a?(Hash) && transport[:bluetooth_conn]
|
|
44
|
+
return :serial if transport.is_a?(Hash) && transport[:serial_conn]
|
|
45
|
+
|
|
46
|
+
raise ArgumentError, 'transport_obj must be a connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
47
|
+
end
|
|
48
|
+
|
|
30
49
|
public_class_method def self.encode(opts = {})
|
|
50
|
+
reject_legacy_transport_options(opts)
|
|
31
51
|
original = opts[:message] || Meshtastic::AdminMessage.new
|
|
32
52
|
raise ArgumentError, 'message must be an AdminMessage' unless original.is_a?(Meshtastic::AdminMessage)
|
|
33
53
|
|
|
@@ -54,8 +74,9 @@ module Meshtastic
|
|
|
54
74
|
end
|
|
55
75
|
|
|
56
76
|
public_class_method def self.send(opts = {})
|
|
77
|
+
type = transport_type(opts)
|
|
57
78
|
message = encode(opts)
|
|
58
|
-
connection = opts[:
|
|
79
|
+
connection = opts[:transport_obj] unless type == :mqtt
|
|
59
80
|
destination = opts[:to] || connection&.dig(:my_node_num)
|
|
60
81
|
destination = destination.delete_prefix('!').to_i(16) if destination.is_a?(String) && destination.match?(/\A![0-9a-fA-F]{8}\z/)
|
|
61
82
|
raise ArgumentError, 'an explicit unicast destination or connected my_node_num is required' unless destination.is_a?(Integer) && destination.between?(1, 0xfffffffe)
|
|
@@ -70,19 +91,21 @@ module Meshtastic
|
|
|
70
91
|
payload: message.to_proto,
|
|
71
92
|
want_response: want_response
|
|
72
93
|
)
|
|
73
|
-
delivery = opts.merge(data: data, port_num: Meshtastic::PortNum::ADMIN_APP, to: destination)
|
|
94
|
+
delivery = opts.except(:transport_obj).merge(data: data, port_num: Meshtastic::PortNum::ADMIN_APP, to: destination)
|
|
95
|
+
delivery[:"#{type}_obj"] = opts[:transport_obj]
|
|
74
96
|
delivery[:from] = 0 if connection && !opts.key?(:from)
|
|
75
97
|
Meshtastic.deliver_data(delivery)
|
|
76
98
|
end
|
|
77
99
|
|
|
78
100
|
public_class_method def self.request(opts = {})
|
|
101
|
+
type = transport_type(opts)
|
|
79
102
|
request_id = opts.fetch(:request_id) { Random.rand(2..0xffffffff) }
|
|
80
103
|
raise ArgumentError, 'request_id must be an Integer from 2 through 0xffffffff' unless request_id.is_a?(Integer) && request_id.between?(2, 0xffffffff)
|
|
81
104
|
|
|
82
105
|
options = opts.except(:request_id, :wait)
|
|
83
106
|
return { request_id: request_id, result: send(options.merge(last_packet_id: request_id - 1)) } unless opts.fetch(:wait, true)
|
|
84
107
|
|
|
85
|
-
connection = options[:
|
|
108
|
+
connection = options[:transport_obj] unless type == :mqtt
|
|
86
109
|
queue = connection && connection[:from_radio_queue]
|
|
87
110
|
raise ArgumentError, 'synchronous Admin requires a connected radio receive queue' unless queue
|
|
88
111
|
|
|
@@ -114,6 +137,11 @@ module Meshtastic
|
|
|
114
137
|
lock.exit if acquired
|
|
115
138
|
end
|
|
116
139
|
|
|
140
|
+
private_class_method def self.reject_legacy_transport_options(opts = {})
|
|
141
|
+
legacy = opts.keys & %i[serial_obj bluetooth_obj tcp_obj mqtt_obj]
|
|
142
|
+
raise ArgumentError, "Admin no longer accepts #{legacy.join(', ')}; use transport_obj: with the connected handle" unless legacy.empty?
|
|
143
|
+
end
|
|
144
|
+
|
|
117
145
|
private_class_method def self.acquire_session(opts = {})
|
|
118
146
|
connection = opts[:connection]
|
|
119
147
|
raise ArgumentError, 'automatic Admin sessions require a radio receive queue; supply session_passkey for MQTT' unless connection && connection[:from_radio_queue]
|
|
@@ -174,6 +202,7 @@ module Meshtastic
|
|
|
174
202
|
end
|
|
175
203
|
|
|
176
204
|
public_class_method def self.decode(opts = {})
|
|
205
|
+
reject_legacy_transport_options(opts)
|
|
177
206
|
packet = opts[:packet]
|
|
178
207
|
packet = packet.packet if packet.is_a?(Meshtastic::FromRadio)
|
|
179
208
|
data = packet.is_a?(Meshtastic::MeshPacket) ? packet.decoded : packet
|
|
@@ -186,6 +215,7 @@ module Meshtastic
|
|
|
186
215
|
end
|
|
187
216
|
|
|
188
217
|
public_class_method def self.response(opts = {})
|
|
218
|
+
reject_legacy_transport_options(opts)
|
|
189
219
|
packet = opts[:packet]
|
|
190
220
|
packet = packet.packet if packet.is_a?(Meshtastic::FromRadio)
|
|
191
221
|
return nil unless packet.is_a?(Meshtastic::MeshPacket) && packet.decoded&.portnum == :ADMIN_APP
|
|
@@ -419,6 +449,14 @@ module Meshtastic
|
|
|
419
449
|
|
|
420
450
|
public_class_method def self.help
|
|
421
451
|
puts "USAGE:
|
|
452
|
+
# Classify the actual connected transport handle.
|
|
453
|
+
#{self}.transport_type(
|
|
454
|
+
transport_obj: 'required - handle from Serial.connect, Bluetooth.connect, TCP.connect or MQTT.connect'
|
|
455
|
+
)
|
|
456
|
+
# Returns :serial, :bluetooth, :tcp or :mqtt; TCP includes serial framing keys.
|
|
457
|
+
# Nil, unknown and ambiguous handles are rejected. Old transport option keys are rejected.
|
|
458
|
+
# All send wrappers below require transport_obj and accept the shared send options.
|
|
459
|
+
|
|
422
460
|
# Encode exactly one AdminMessage payload without modifying the caller.
|
|
423
461
|
#{self}.encode(
|
|
424
462
|
message: 'optional - existing AdminMessage to copy instead of a new one',
|
|
@@ -427,10 +465,7 @@ module Meshtastic
|
|
|
427
465
|
|
|
428
466
|
# Send an AdminMessage on ADMIN_APP over a connected transport.
|
|
429
467
|
#{self}.send(
|
|
430
|
-
|
|
431
|
-
bluetooth_obj: 'optional - BLE handle from Meshtastic::Bluetooth.connect',
|
|
432
|
-
tcp_obj: 'optional - TCP handle from Meshtastic::TCP.connect',
|
|
433
|
-
mqtt_obj: 'optional - MQTT client from Meshtastic::MQTT.connect',
|
|
468
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client',
|
|
434
469
|
to: 'optional - unicast node integer or !eighthex; defaults to connected my_node_num; required for MQTT',
|
|
435
470
|
from: 'optional - sender number; radio default zero denotes the local PhoneAPI client',
|
|
436
471
|
channel: 'optional - routing channel index (default zero)',
|
|
@@ -478,13 +513,13 @@ module Meshtastic
|
|
|
478
513
|
|
|
479
514
|
# Reboot the node after a delay.
|
|
480
515
|
#{self}.reboot(
|
|
481
|
-
|
|
516
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client',
|
|
482
517
|
seconds: 'optional - delay before reboot in seconds (default: 5)'
|
|
483
518
|
)
|
|
484
519
|
|
|
485
520
|
# Shut down the node after a delay.
|
|
486
521
|
#{self}.shutdown(
|
|
487
|
-
|
|
522
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client',
|
|
488
523
|
seconds: 'optional - delay before shutdown in seconds (default: 5)'
|
|
489
524
|
)
|
|
490
525
|
|
|
@@ -497,7 +532,7 @@ module Meshtastic
|
|
|
497
532
|
|
|
498
533
|
# Request the node owner User protobuf.
|
|
499
534
|
#{self}.get_owner(
|
|
500
|
-
|
|
535
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
501
536
|
)
|
|
502
537
|
|
|
503
538
|
# Write a Channel protobuf to the node.
|
|
@@ -533,7 +568,7 @@ module Meshtastic
|
|
|
533
568
|
|
|
534
569
|
# Request canned-message module strings.
|
|
535
570
|
#{self}.get_canned_messages(
|
|
536
|
-
|
|
571
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
537
572
|
)
|
|
538
573
|
|
|
539
574
|
# Set canned-message module strings.
|
|
@@ -543,12 +578,12 @@ module Meshtastic
|
|
|
543
578
|
|
|
544
579
|
# Request DeviceMetadata (firmware version and hardware).
|
|
545
580
|
#{self}.get_device_metadata(
|
|
546
|
-
|
|
581
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
547
582
|
)
|
|
548
583
|
|
|
549
584
|
# Request the RTTTL ringtone string.
|
|
550
585
|
#{self}.get_ringtone(
|
|
551
|
-
|
|
586
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
552
587
|
)
|
|
553
588
|
|
|
554
589
|
# Set the RTTTL ringtone string.
|
|
@@ -558,17 +593,17 @@ module Meshtastic
|
|
|
558
593
|
|
|
559
594
|
# Request DeviceConnectionStatus.
|
|
560
595
|
#{self}.get_device_connection_status(
|
|
561
|
-
|
|
596
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
562
597
|
)
|
|
563
598
|
|
|
564
599
|
# Request remote-hardware pin definitions.
|
|
565
600
|
#{self}.get_node_remote_hardware_pins(
|
|
566
|
-
|
|
601
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
567
602
|
)
|
|
568
603
|
|
|
569
604
|
# Ask the node to enter DFU / UF2 mode.
|
|
570
605
|
#{self}.enter_dfu(
|
|
571
|
-
|
|
606
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
572
607
|
)
|
|
573
608
|
|
|
574
609
|
# Delete a file on the node filesystem.
|
|
@@ -640,7 +675,7 @@ module Meshtastic
|
|
|
640
675
|
|
|
641
676
|
# Clear the fixed GPS position on the node.
|
|
642
677
|
#{self}.remove_fixed_position(
|
|
643
|
-
|
|
678
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
644
679
|
)
|
|
645
680
|
|
|
646
681
|
# Set node wall-clock time.
|
|
@@ -650,7 +685,7 @@ module Meshtastic
|
|
|
650
685
|
|
|
651
686
|
# Request DeviceUIConfig.
|
|
652
687
|
#{self}.get_ui_config(
|
|
653
|
-
|
|
688
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
654
689
|
)
|
|
655
690
|
|
|
656
691
|
# Store DeviceUIConfig on the node.
|
|
@@ -675,12 +710,12 @@ module Meshtastic
|
|
|
675
710
|
|
|
676
711
|
# Open a settings edit transaction.
|
|
677
712
|
#{self}.begin_edit(
|
|
678
|
-
|
|
713
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
679
714
|
)
|
|
680
715
|
|
|
681
716
|
# Commit a settings edit transaction.
|
|
682
717
|
#{self}.commit_edit(
|
|
683
|
-
|
|
718
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
684
719
|
)
|
|
685
720
|
|
|
686
721
|
# Add a SharedContact to the node.
|
|
@@ -705,13 +740,13 @@ module Meshtastic
|
|
|
705
740
|
|
|
706
741
|
# Clear the node database.
|
|
707
742
|
#{self}.nodedb_reset(
|
|
708
|
-
|
|
743
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client',
|
|
709
744
|
preserve_favorites: 'optional - retain favorite nodes (default true); some firmware roles always preserve favorites'
|
|
710
745
|
)
|
|
711
746
|
|
|
712
747
|
# Exit the firmware simulator if running.
|
|
713
748
|
#{self}.exit_simulator(
|
|
714
|
-
|
|
749
|
+
transport_obj: 'required - actual connected Serial, Bluetooth, TCP handle or MQTT client'
|
|
715
750
|
)
|
|
716
751
|
|
|
717
752
|
# Write a SensorConfig protobuf.
|
|
@@ -5,11 +5,25 @@ require 'meshtastic/module_config_pb'
|
|
|
5
5
|
module Meshtastic
|
|
6
6
|
class ModuleConfig
|
|
7
7
|
public_class_method def self.get(opts = {})
|
|
8
|
-
Admin.send(opts.merge(get_module_config_request: opts.fetch(:module_config_type, :MQTT_CONFIG)))
|
|
8
|
+
Admin.send(admin_options(opts).merge(get_module_config_request: opts.fetch(:module_config_type, :MQTT_CONFIG)))
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
public_class_method def self.set(opts = {})
|
|
12
|
-
Admin.send(opts.merge(set_module_config: opts.fetch(:module_config)))
|
|
12
|
+
Admin.send(admin_options(opts).merge(set_module_config: opts.fetch(:module_config)))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Keep the external transport aliases out of the Admin API.
|
|
16
|
+
private_class_method def self.admin_options(opts = {})
|
|
17
|
+
options = opts.merge({})
|
|
18
|
+
keys = %i[transport_obj serial_obj bluetooth_obj tcp_obj mqtt_obj]
|
|
19
|
+
selected = keys.reject { |name| options[name].nil? }
|
|
20
|
+
raise ArgumentError, 'provide exactly one transport connection' if selected.length > 1
|
|
21
|
+
|
|
22
|
+
key = selected.first
|
|
23
|
+
connection = options[key] if key
|
|
24
|
+
keys.each { |name| options.delete(name) }
|
|
25
|
+
options[:transport_obj] = connection if key
|
|
26
|
+
options
|
|
13
27
|
end
|
|
14
28
|
|
|
15
29
|
public_class_method def self.authors
|
|
@@ -19,10 +33,14 @@ module Meshtastic
|
|
|
19
33
|
public_class_method def self.help
|
|
20
34
|
puts " USAGE:
|
|
21
35
|
# Run the get class method for this module.
|
|
22
|
-
#{self}.get
|
|
36
|
+
#{self}.get(transport_obj: connection, module_config_type: :MQTT_CONFIG)
|
|
23
37
|
|
|
24
38
|
# Run the set class method for this module.
|
|
25
|
-
#{self}.set
|
|
39
|
+
#{self}.set(transport_obj: connection, module_config: module_config)
|
|
40
|
+
|
|
41
|
+
# Legacy serial_obj:, bluetooth_obj:, tcp_obj:, mqtt_obj: remain accepted.
|
|
42
|
+
# Supply one non-nil connection only; mixed connection options are rejected.
|
|
43
|
+
# Remote setters inherit Admin automatic sessions, not persistence readback.
|
|
26
44
|
|
|
27
45
|
# Run the authors class method for this module.
|
|
28
46
|
#{self}.authors
|
data/lib/meshtastic/rtttl.rb
CHANGED
|
@@ -11,11 +11,25 @@ module Meshtastic
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
public_class_method def self.set(opts = {})
|
|
14
|
-
Admin.send(opts.merge(set_ringtone_message: opts.fetch(:ringtone)))
|
|
14
|
+
Admin.send(admin_options(opts).merge(set_ringtone_message: opts.fetch(:ringtone)))
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
public_class_method def self.get(opts = {})
|
|
18
|
-
Admin.send(opts.merge(get_ringtone_request: true))
|
|
18
|
+
Admin.send(admin_options(opts).merge(get_ringtone_request: true))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Keep the external transport aliases out of the Admin API.
|
|
22
|
+
private_class_method def self.admin_options(opts = {})
|
|
23
|
+
options = opts.merge({})
|
|
24
|
+
keys = %i[transport_obj serial_obj bluetooth_obj tcp_obj mqtt_obj]
|
|
25
|
+
selected = keys.reject { |name| options[name].nil? }
|
|
26
|
+
raise ArgumentError, 'provide exactly one transport connection' if selected.length > 1
|
|
27
|
+
|
|
28
|
+
key = selected.first
|
|
29
|
+
connection = options[key] if key
|
|
30
|
+
keys.each { |name| options.delete(name) }
|
|
31
|
+
options[:transport_obj] = connection if key
|
|
32
|
+
options
|
|
19
33
|
end
|
|
20
34
|
|
|
21
35
|
public_class_method def self.authors
|
|
@@ -28,10 +42,14 @@ module Meshtastic
|
|
|
28
42
|
#{self}.encode
|
|
29
43
|
|
|
30
44
|
# Run the set class method for this module.
|
|
31
|
-
#{self}.set
|
|
45
|
+
#{self}.set(transport_obj: connection, ringtone: ringtone)
|
|
32
46
|
|
|
33
47
|
# Run the get class method for this module.
|
|
34
|
-
#{self}.get
|
|
48
|
+
#{self}.get(transport_obj: connection)
|
|
49
|
+
|
|
50
|
+
# Legacy serial_obj:, bluetooth_obj:, tcp_obj:, mqtt_obj: remain accepted.
|
|
51
|
+
# Supply one non-nil connection only; mixed connection options are rejected.
|
|
52
|
+
# Remote setters inherit Admin automatic sessions, not persistence readback.
|
|
35
53
|
|
|
36
54
|
# Run the authors class method for this module.
|
|
37
55
|
#{self}.authors
|
data/lib/meshtastic/version.rb
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
+
RSpec.describe Meshtastic::Admin::Channel do
|
|
6
|
+
it 'documents transport_obj rather than legacy connection keywords' do
|
|
7
|
+
expect { described_class.help }.to output(/transport_obj:/).to_stdout
|
|
8
|
+
expect { described_class.help }.not_to output(/serial_obj:|bluetooth_obj:|tcp_obj:|mqtt_obj:/).to_stdout
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
shared_context 'channel serial framing' do
|
|
6
13
|
def fake_serial_obj
|
|
7
14
|
written = +''.b
|
|
@@ -26,16 +33,23 @@ end
|
|
|
26
33
|
describe Meshtastic::Admin::Channel do
|
|
27
34
|
include_context 'channel serial framing'
|
|
28
35
|
|
|
36
|
+
it 'rejects legacy connection keys before filtering or building channel options' do
|
|
37
|
+
%i[serial_obj bluetooth_obj tcp_obj mqtt_obj].each do |key|
|
|
38
|
+
expect { described_class.set(transport_obj: fake_serial_obj, key => nil, index: -1) }
|
|
39
|
+
.to raise_error(ArgumentError, /#{key}.*transport_obj/)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
29
43
|
it 'requests a channel by index via Admin' do
|
|
30
44
|
serial_obj = fake_serial_obj
|
|
31
|
-
described_class.get(
|
|
45
|
+
described_class.get(transport_obj: serial_obj, index: 1)
|
|
32
46
|
expect(decode_admin(serial_obj).get_channel_request).to eq(2)
|
|
33
47
|
end
|
|
34
48
|
|
|
35
49
|
[0, 7].each do |index|
|
|
36
50
|
it "requests zero-based slot #{index} with its one-based wire index" do
|
|
37
51
|
serial_obj = fake_serial_obj
|
|
38
|
-
described_class.get(
|
|
52
|
+
described_class.get(transport_obj: serial_obj, index: index)
|
|
39
53
|
expect(decode_admin(serial_obj).get_channel_request).to eq(index + 1)
|
|
40
54
|
end
|
|
41
55
|
end
|
|
@@ -43,7 +57,7 @@ describe Meshtastic::Admin::Channel do
|
|
|
43
57
|
it 'sets a Channel protobuf including settings and role' do
|
|
44
58
|
serial_obj = fake_serial_obj
|
|
45
59
|
settings = described_class.build_settings(name: 'LongFast', uplink_enabled: true)
|
|
46
|
-
described_class.set(
|
|
60
|
+
described_class.set(transport_obj: serial_obj, index: 0, role: :PRIMARY, settings: settings)
|
|
47
61
|
channel = decode_admin(serial_obj).set_channel
|
|
48
62
|
expect(channel.index).to eq(0)
|
|
49
63
|
expect(channel.role).to eq(:PRIMARY)
|
|
@@ -124,7 +138,7 @@ describe Meshtastic::Admin::Channel, 'channel URL support' do
|
|
|
124
138
|
it 'rejects invalid settings lengths and slot indexes before any write' do
|
|
125
139
|
[-1, 8, 0.5, 'junk'].each do |index|
|
|
126
140
|
serial_obj = fake_serial_obj
|
|
127
|
-
expect { described_class.set(
|
|
141
|
+
expect { described_class.set(transport_obj: serial_obj, index: index, role: :PRIMARY) }.to raise_error(ArgumentError)
|
|
128
142
|
expect(serial_obj[:written]).to be_empty
|
|
129
143
|
end
|
|
130
144
|
expect { described_class.build_settings(psk: 'bad') }.to raise_error(ArgumentError)
|
|
@@ -137,7 +151,7 @@ describe Meshtastic::Admin::Channel, 'channel URL support' do
|
|
|
137
151
|
it 'validates and overlays supplied channel/settings before transmission' do
|
|
138
152
|
serial_obj = fake_serial_obj
|
|
139
153
|
original = Meshtastic::Channel.new(index: 2, role: :SECONDARY, settings: { name: 'before', uplink_enabled: true })
|
|
140
|
-
described_class.set(
|
|
154
|
+
described_class.set(transport_obj: serial_obj, channel: original, name: 'after', uplink_enabled: false)
|
|
141
155
|
written = decode_admin(serial_obj).set_channel
|
|
142
156
|
expect(written.index).to eq(2)
|
|
143
157
|
expect(written.settings.name).to eq('after')
|
|
@@ -145,7 +159,7 @@ describe Meshtastic::Admin::Channel, 'channel URL support' do
|
|
|
145
159
|
expect(original.settings.name).to eq('before')
|
|
146
160
|
expect(described_class.build(settings: { name: 'hash' }).settings.name).to eq('hash')
|
|
147
161
|
invalid = Meshtastic::Channel.new(index: 8)
|
|
148
|
-
expect { described_class.set(
|
|
162
|
+
expect { described_class.set(transport_obj: fake_serial_obj, channel: invalid) }.to raise_error(ArgumentError)
|
|
149
163
|
expect { described_class.build(settings: { psk: 'bad' }) }.to raise_error(ArgumentError)
|
|
150
164
|
end
|
|
151
165
|
|
|
@@ -153,7 +167,7 @@ describe Meshtastic::Admin::Channel, 'channel URL support' do
|
|
|
153
167
|
serial_obj = fake_serial_obj
|
|
154
168
|
channel_set = Meshtastic::ChannelSet.new(settings: [{ name: 'main' }, { name: 'other' }], lora_config: { region: :US })
|
|
155
169
|
url = "https://meshtastic.org/e/##{Base64.urlsafe_encode64(channel_set.to_proto, padding: false)}"
|
|
156
|
-
results = described_class.apply_url(
|
|
170
|
+
results = described_class.apply_url(transport_obj: serial_obj, url: url, session_passkey: 'test-key')
|
|
157
171
|
frames = serial_obj[:written].dup
|
|
158
172
|
messages = []
|
|
159
173
|
until frames.empty?
|
|
@@ -176,11 +190,11 @@ describe Meshtastic::Admin::Channel, 'channel URL support' do
|
|
|
176
190
|
serial_obj = fake_serial_obj
|
|
177
191
|
invalid = Meshtastic::ChannelSet.new(settings: [{ name: 'good' }, { psk: 'bad' }])
|
|
178
192
|
url = "https://meshtastic.org/e/##{Base64.urlsafe_encode64(invalid.to_proto, padding: false)}"
|
|
179
|
-
expect { described_class.apply_url(
|
|
193
|
+
expect { described_class.apply_url(transport_obj: serial_obj, url: url) }.to raise_error(ArgumentError)
|
|
180
194
|
expect(serial_obj[:written]).to be_empty
|
|
181
195
|
valid = Meshtastic::ChannelSet.new(settings: [{ name: 'test' }])
|
|
182
196
|
url = "https://meshtastic.org/e/?add=true##{Base64.urlsafe_encode64(valid.to_proto, padding: false)}"
|
|
183
|
-
expect { described_class.apply_url(
|
|
197
|
+
expect { described_class.apply_url(transport_obj: serial_obj, url: url) }.to raise_error(ArgumentError, /add-only/)
|
|
184
198
|
expect(serial_obj[:written]).to be_empty
|
|
185
199
|
end
|
|
186
200
|
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
+
RSpec.describe Meshtastic::Admin::Config do
|
|
6
|
+
it 'documents transport_obj rather than legacy connection keywords' do
|
|
7
|
+
expect { described_class.help }.to output(/transport_obj:/).to_stdout
|
|
8
|
+
expect { described_class.help }.not_to output(/serial_obj:|bluetooth_obj:|tcp_obj:|mqtt_obj:/).to_stdout
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
describe Meshtastic::Admin::Config do
|
|
6
13
|
def fake_serial_obj
|
|
7
14
|
written = +''.b
|
|
@@ -22,9 +29,18 @@ describe Meshtastic::Admin::Config do
|
|
|
22
29
|
Meshtastic::AdminMessage.decode(Meshtastic::ToRadio.decode(body).packet.decoded.payload)
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
it 'rejects legacy connection keys rather than silently filtering them' do
|
|
33
|
+
%i[serial_obj bluetooth_obj tcp_obj mqtt_obj].each do |key|
|
|
34
|
+
handle = fake_serial_obj
|
|
35
|
+
expect { described_class.set_device_ui(transport_obj: handle, key => nil, device_ui: {}) }
|
|
36
|
+
.to raise_error(ArgumentError, /#{key}/)
|
|
37
|
+
expect(handle[:written]).to be_empty
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
25
41
|
it 'requests LoRa config via Admin' do
|
|
26
42
|
serial_obj = fake_serial_obj
|
|
27
|
-
described_class.get(
|
|
43
|
+
described_class.get(transport_obj: serial_obj, config_type: :LORA_CONFIG)
|
|
28
44
|
expect(decode_admin(serial_obj).get_config_request).to eq(:LORA_CONFIG)
|
|
29
45
|
end
|
|
30
46
|
|
|
@@ -32,13 +48,13 @@ describe Meshtastic::Admin::Config do
|
|
|
32
48
|
serial_obj = fake_serial_obj
|
|
33
49
|
config = Meshtastic::Config.new
|
|
34
50
|
config.device = Meshtastic::Config::DeviceConfig.new(role: :CLIENT)
|
|
35
|
-
described_class.set(
|
|
51
|
+
described_class.set(transport_obj: serial_obj, config: config)
|
|
36
52
|
expect(decode_admin(serial_obj).set_config.device.role).to eq(:CLIENT)
|
|
37
53
|
end
|
|
38
54
|
|
|
39
55
|
it 'requests each ConfigType via named getters' do
|
|
40
56
|
serial_obj = fake_serial_obj
|
|
41
|
-
described_class.get_lora(
|
|
57
|
+
described_class.get_lora(transport_obj: serial_obj)
|
|
42
58
|
expect(decode_admin(serial_obj).get_config_request).to eq(:LORA_CONFIG)
|
|
43
59
|
end
|
|
44
60
|
|
|
@@ -48,7 +64,7 @@ describe Meshtastic::Admin::Config do
|
|
|
48
64
|
it "writes the #{field.name} section through real serial framing" do
|
|
49
65
|
serial_obj = fake_serial_obj
|
|
50
66
|
section = field.subtype.msgclass.new
|
|
51
|
-
described_class.public_send("set_#{field.name}",
|
|
67
|
+
described_class.public_send("set_#{field.name}", transport_obj: serial_obj, field.name.to_sym => section)
|
|
52
68
|
config = decode_admin(serial_obj).set_config
|
|
53
69
|
expect(config.payload_variant).to eq(field.name.to_sym)
|
|
54
70
|
expect(config[field.name]).to eq(section)
|
|
@@ -57,7 +73,7 @@ describe Meshtastic::Admin::Config do
|
|
|
57
73
|
|
|
58
74
|
it 'rejects an empty config before writing bytes' do
|
|
59
75
|
serial_obj = fake_serial_obj
|
|
60
|
-
expect { described_class.set(
|
|
76
|
+
expect { described_class.set(transport_obj: serial_obj, config: Meshtastic::Config.new) }.to raise_error(ArgumentError)
|
|
61
77
|
expect(serial_obj[:written]).to be_empty
|
|
62
78
|
end
|
|
63
79
|
|
|
@@ -66,31 +82,31 @@ describe Meshtastic::Admin::Config do
|
|
|
66
82
|
|
|
67
83
|
it "accepts a field hash for #{field.name}" do
|
|
68
84
|
serial_obj = fake_serial_obj
|
|
69
|
-
described_class.public_send("set_#{field.name}",
|
|
85
|
+
described_class.public_send("set_#{field.name}", transport_obj: serial_obj, field.name.to_sym => {})
|
|
70
86
|
expect(decode_admin(serial_obj).set_config.payload_variant).to eq(field.name.to_sym)
|
|
71
87
|
end
|
|
72
88
|
|
|
73
89
|
it "rejects missing #{field.name} before writing bytes" do
|
|
74
90
|
serial_obj = fake_serial_obj
|
|
75
|
-
expect { described_class.public_send("set_#{field.name}",
|
|
91
|
+
expect { described_class.public_send("set_#{field.name}", transport_obj: serial_obj) }.to(raise_error { |error| expect([ArgumentError, KeyError]).to include(error.class) })
|
|
76
92
|
expect(serial_obj[:written]).to be_empty
|
|
77
93
|
end
|
|
78
94
|
end
|
|
79
95
|
|
|
80
96
|
it 'uses dedicated device UI requests and stores instead of firmware no-op Config fields' do
|
|
81
97
|
serial_obj = fake_serial_obj
|
|
82
|
-
described_class.get_device_ui(
|
|
98
|
+
described_class.get_device_ui(transport_obj: serial_obj)
|
|
83
99
|
expect(decode_admin(serial_obj).payload_variant).to eq(:get_ui_config_request)
|
|
84
100
|
serial_obj = fake_serial_obj
|
|
85
|
-
described_class.set_device_ui(
|
|
101
|
+
described_class.set_device_ui(transport_obj: serial_obj, device_ui: {})
|
|
86
102
|
expect(decode_admin(serial_obj).payload_variant).to eq(:store_ui_config)
|
|
87
103
|
end
|
|
88
104
|
|
|
89
105
|
it 'rejects the read-only session-key placeholder without transmitting' do
|
|
90
106
|
serial_obj = fake_serial_obj
|
|
91
107
|
config = Meshtastic::Config.new(sessionkey: {})
|
|
92
|
-
expect { described_class.set(
|
|
93
|
-
expect { described_class.set_sessionkey(
|
|
108
|
+
expect { described_class.set(transport_obj: serial_obj, config: config) }.to raise_error(ArgumentError, /request-only/)
|
|
109
|
+
expect { described_class.set_sessionkey(transport_obj: serial_obj, sessionkey: {}) }.to raise_error(ArgumentError, /request-only/)
|
|
94
110
|
expect(serial_obj[:written]).to be_empty
|
|
95
111
|
end
|
|
96
112
|
|
|
@@ -101,7 +117,7 @@ describe Meshtastic::Admin::Config do
|
|
|
101
117
|
}.each do |section, config_type|
|
|
102
118
|
it "requests #{section} using its protocol ConfigType" do
|
|
103
119
|
serial_obj = fake_serial_obj
|
|
104
|
-
described_class.public_send("get_#{section}",
|
|
120
|
+
described_class.public_send("get_#{section}", transport_obj: serial_obj)
|
|
105
121
|
message = decode_admin(serial_obj)
|
|
106
122
|
expect(message.payload_variant).to eq(:get_config_request)
|
|
107
123
|
expect(message.get_config_request).to eq(config_type)
|