meshtastic 0.0.180 → 0.0.181

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.
@@ -1,34 +1,205 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timeout'
4
+ require 'monitor'
3
5
  require 'meshtastic/admin_pb'
4
6
 
5
7
  module Meshtastic
6
8
  module Admin
9
+ class RoutingError < StandardError
10
+ attr_reader :reason, :request_id, :from
11
+
12
+ def initialize(reason, request_id, from)
13
+ @reason = reason
14
+ @request_id = request_id
15
+ @from = from
16
+ super("Admin routing failure: #{reason} (request #{request_id})")
17
+ end
18
+ end
19
+
7
20
  SKIP = %i[
8
21
  message serial_obj bluetooth_obj tcp_obj mqtt_obj to from channel want_ack hop_limit
9
22
  want_response port_num data via psks seconds owner long_name short_name index config_type
10
- module_config_type path node_num lat lon altitude time
23
+ module_config_type path node_num lat lon altitude time channel_settings channel_pb
24
+ config module_config messages ringtone scale location event event_code kb_char touch_x
25
+ touch_y ham call_sign tx_power frequency position ui_config contact value preserve_favorites
26
+ last_packet_id pki_encrypted public_key topic qos retained timeout auto_session refresh_session
11
27
  ].freeze
28
+ PAYLOAD_FIELDS = Meshtastic::AdminMessage.descriptor.lookup_oneof('payload_variant').map { |field| field.name.to_sym }.freeze
12
29
 
13
30
  public_class_method def self.encode(opts = {})
14
- message = opts[:message] || Meshtastic::AdminMessage.new
15
- opts.each do |key, value|
16
- next if SKIP.include?(key)
17
- next unless message.respond_to?("#{key}=")
31
+ original = opts[:message] || Meshtastic::AdminMessage.new
32
+ raise ArgumentError, 'message must be an AdminMessage' unless original.is_a?(Meshtastic::AdminMessage)
33
+
34
+ fields = opts.keys & PAYLOAD_FIELDS
35
+ variants = (fields + [original.payload_variant]).compact.uniq
36
+ raise ArgumentError, 'exactly one payload is required' unless variants.length == 1
37
+
38
+ unknown = opts.keys - SKIP - PAYLOAD_FIELDS - [:session_passkey]
39
+ raise ArgumentError, "unknown admin options: #{unknown.join(', ')}" unless unknown.empty?
18
40
 
19
- message.public_send("#{key}=", value)
41
+ message = Meshtastic::AdminMessage.decode(original.to_proto)
42
+ fields.each do |key|
43
+ raise ArgumentError, "#{key} cannot be nil" if opts[key].nil?
44
+
45
+ message.public_send("#{key}=", opts[key])
46
+ end
47
+ if opts.key?(:session_passkey)
48
+ passkey = opts[:session_passkey]
49
+ raise ArgumentError, 'session_passkey must contain exactly eight bytes' unless passkey.is_a?(String) && passkey.bytesize == 8
50
+
51
+ message.session_passkey = passkey
20
52
  end
21
53
  message
22
54
  end
23
55
 
24
56
  public_class_method def self.send(opts = {})
25
57
  message = encode(opts)
58
+ connection = opts[:serial_obj] || opts[:bluetooth_obj] || opts[:tcp_obj]
59
+ destination = opts[:to] || connection&.dig(:my_node_num)
60
+ destination = destination.delete_prefix('!').to_i(16) if destination.is_a?(String) && destination.match?(/\A![0-9a-fA-F]{8}\z/)
61
+ raise ArgumentError, 'an explicit unicast destination or connected my_node_num is required' unless destination.is_a?(Integer) && destination.between?(1, 0xfffffffe)
62
+
63
+ if opts.fetch(:auto_session, true) && message.session_passkey.empty? && destination != connection&.dig(:my_node_num) &&
64
+ !message.payload_variant.to_s.match?(/\Aget_.*_(request|response)\z/)
65
+ message.session_passkey = acquire_session(opts.merge(connection: connection, target: destination))
66
+ end
67
+ want_response = opts.fetch(:want_response) { message.payload_variant.to_s.match?(/\Aget_.*_request\z/) }
26
68
  data = Meshtastic::Data.new(
27
69
  portnum: :ADMIN_APP,
28
70
  payload: message.to_proto,
29
- want_response: opts[:want_response] != false
71
+ want_response: want_response
30
72
  )
31
- Meshtastic.deliver_data(opts.merge(data: data, port_num: Meshtastic::PortNum::ADMIN_APP))
73
+ delivery = opts.merge(data: data, port_num: Meshtastic::PortNum::ADMIN_APP, to: destination)
74
+ delivery[:from] = 0 if connection && !opts.key?(:from)
75
+ Meshtastic.deliver_data(delivery)
76
+ end
77
+
78
+ public_class_method def self.request(opts = {})
79
+ request_id = opts.fetch(:request_id) { Random.rand(2..0xffffffff) }
80
+ raise ArgumentError, 'request_id must be an Integer from 2 through 0xffffffff' unless request_id.is_a?(Integer) && request_id.between?(2, 0xffffffff)
81
+
82
+ options = opts.except(:request_id, :wait)
83
+ return { request_id: request_id, result: send(options.merge(last_packet_id: request_id - 1)) } unless opts.fetch(:wait, true)
84
+
85
+ connection = options[:serial_obj] || options[:bluetooth_obj] || options[:tcp_obj]
86
+ queue = connection && connection[:from_radio_queue]
87
+ raise ArgumentError, 'synchronous Admin requires a connected radio receive queue' unless queue
88
+
89
+ timeout = opts.fetch(:timeout, 10)
90
+ raise ArgumentError, 'timeout must be a positive finite number' unless timeout.is_a?(Numeric) && timeout.positive? && timeout.finite?
91
+
92
+ lock = connection[:admin_request_lock] ||= Monitor.new
93
+ acquired = lock.try_enter
94
+ raise IOError, 'a synchronous Admin request is already active on this handle' unless acquired
95
+
96
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
97
+ message = encode(options)
98
+ target = options.fetch(:to, connection[:my_node_num])
99
+ target = target.delete_prefix('!').to_i(16) if target.is_a?(String) && target.match?(/\A![0-9a-fA-F]{8}\z/)
100
+ result = send(options.merge(last_packet_id: request_id - 1, want_response: true, want_ack: true, timeout: timeout))
101
+ variant = message.payload_variant.to_s
102
+ expected = variant.match?(/\Aget_.*_request\z/) ? variant.sub(/_request\z/, '_response').to_sym : :routing
103
+ reply = await_response(connection: connection, queue: queue, deadline: deadline, request_id: request_id, target: target,
104
+ variant: expected)
105
+ if reply[:session_passkey]&.bytesize == 8
106
+ sessions = connection[:admin_sessions] ||= {}
107
+ sessions[target] = { key: reply[:session_passkey], expires_at: deadline - timeout + 150 }
108
+ end
109
+ reply.merge(result: result)
110
+ rescue RoutingError => e
111
+ connection[:admin_sessions]&.delete(target) if connection && e.reason == :ADMIN_BAD_SESSION_KEY
112
+ raise
113
+ ensure
114
+ lock.exit if acquired
115
+ end
116
+
117
+ private_class_method def self.acquire_session(opts = {})
118
+ connection = opts[:connection]
119
+ raise ArgumentError, 'automatic Admin sessions require a radio receive queue; supply session_passkey for MQTT' unless connection && connection[:from_radio_queue]
120
+
121
+ sessions = connection[:admin_sessions] ||= {}
122
+ cached = sessions[opts[:target]]
123
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
124
+ return cached[:key] if cached && now < cached[:expires_at] && !opts[:refresh_session]
125
+
126
+ sessions.delete(opts[:target])
127
+ options = opts.except(*(PAYLOAD_FIELDS + %i[message connection target session_passkey refresh_session last_packet_id]))
128
+ reply = request(options.merge(get_config_request: :SESSIONKEY_CONFIG, auto_session: false))
129
+ key = reply[:session_passkey]
130
+ raise ArgumentError, 'Admin session response did not contain an eight-byte passkey' unless key.bytesize == 8
131
+
132
+ sessions[opts[:target]] = { key: key, expires_at: now + 150 }
133
+ key
134
+ end
135
+
136
+ private_class_method def self.await_response(opts = {})
137
+ queue = opts[:queue]
138
+ deferred = []
139
+ loop do
140
+ remaining = opts[:deadline] - Process.clock_gettime(Process::CLOCK_MONOTONIC)
141
+ raise Timeout::Error, 'Admin response timed out' unless remaining.positive?
142
+
143
+ incoming = queue.pop(timeout: remaining)
144
+ raise Timeout::Error, 'Admin response timed out or transport closed' unless incoming
145
+
146
+ packet = incoming.is_a?(Meshtastic::FromRadio) ? incoming.packet : incoming
147
+ if packet.is_a?(Meshtastic::MeshPacket) && packet.decoded&.portnum == :ROUTING_APP && packet.decoded.request_id == opts[:request_id] &&
148
+ [opts[:target], opts[:connection][:my_node_num]].include?(packet.from)
149
+ reason = Meshtastic::Routing.decode(packet.decoded.payload).error_reason
150
+ raise RoutingError.new(reason, opts[:request_id], packet.from) if reason != :NONE && [opts[:target], opts[:connection][:my_node_num]].include?(packet.from)
151
+ return { variant: :routing, value: reason, request_id: opts[:request_id], from: packet.from } if reason == :NONE && packet.from == opts[:target] && opts[:variant] == :routing
152
+ end
153
+ reply = response(packet: incoming, request_id: opts[:request_id], from: opts[:target])
154
+ return reply if reply && reply[:variant] == opts[:variant]
155
+
156
+ deferred << incoming
157
+ end
158
+ ensure
159
+ begin
160
+ until deferred.nil? || deferred.empty?
161
+ queue << deferred.first
162
+ deferred.shift
163
+ end
164
+ rescue ClosedQueueError
165
+ replacement = Queue.new
166
+ deferred.each { |incoming| replacement << incoming }
167
+ while (incoming = queue.pop)
168
+ replacement << incoming
169
+ end
170
+ replacement.close
171
+ opts[:connection][:from_radio_queue] = replacement
172
+ deferred.clear
173
+ end
174
+ end
175
+
176
+ public_class_method def self.decode(opts = {})
177
+ packet = opts[:packet]
178
+ packet = packet.packet if packet.is_a?(Meshtastic::FromRadio)
179
+ data = packet.is_a?(Meshtastic::MeshPacket) ? packet.decoded : packet
180
+ raise ArgumentError, 'packet must contain decoded ADMIN_APP data' if opts.key?(:packet) && (!data.is_a?(Meshtastic::Data) || data.portnum != :ADMIN_APP)
181
+
182
+ payload = data ? data.payload : opts[:payload]
183
+ raise ArgumentError, 'payload bytes or a decoded packet are required' unless payload.is_a?(String)
184
+
185
+ Meshtastic::AdminMessage.decode(payload)
186
+ end
187
+
188
+ public_class_method def self.response(opts = {})
189
+ packet = opts[:packet]
190
+ packet = packet.packet if packet.is_a?(Meshtastic::FromRadio)
191
+ return nil unless packet.is_a?(Meshtastic::MeshPacket) && packet.decoded&.portnum == :ADMIN_APP
192
+ return nil if opts.key?(:request_id) && packet.decoded.request_id != opts[:request_id]
193
+ return nil if opts.key?(:from) && packet.from != opts[:from]
194
+
195
+ message = decode(packet: packet)
196
+ variant = message.payload_variant
197
+ return nil unless variant.to_s.end_with?('_response')
198
+
199
+ {
200
+ message: message, variant: variant, value: message.public_send(variant),
201
+ session_passkey: message.session_passkey, request_id: packet.decoded.request_id, from: packet.from
202
+ }
32
203
  end
33
204
 
34
205
  public_class_method def self.reboot(opts = {})
@@ -39,6 +210,14 @@ module Meshtastic
39
210
  send(opts.merge(shutdown_seconds: opts[:seconds] || 5))
40
211
  end
41
212
 
213
+ public_class_method def self.reboot_ota(opts = {})
214
+ send(opts.merge(reboot_ota_seconds: opts[:seconds] || 5))
215
+ end
216
+
217
+ public_class_method def self.ota_request(opts = {})
218
+ send(opts.merge(ota_request: opts[:event]))
219
+ end
220
+
42
221
  public_class_method def self.set_owner(opts = {})
43
222
  user = opts[:owner] || Meshtastic::User.new(long_name: opts[:long_name], short_name: opts[:short_name])
44
223
  send(opts.merge(set_owner: user))
@@ -55,7 +234,9 @@ module Meshtastic
55
234
  public_class_method def self.get_channel(opts = {})
56
235
  index = opts[:index]
57
236
  index = 0 if index.nil?
58
- send(opts.merge(get_channel_request: index))
237
+ raise ArgumentError, 'index must be an Integer from 0 through 7' unless index.is_a?(Integer) && index.between?(0, 7)
238
+
239
+ send(opts.merge(get_channel_request: index + 1))
59
240
  end
60
241
 
61
242
  public_class_method def self.get_config(opts = {})
@@ -79,7 +260,7 @@ module Meshtastic
79
260
  end
80
261
 
81
262
  public_class_method def self.set_canned_messages(opts = {})
82
- send(opts.merge(set_canned_message_module_messages: opts[:messages].to_s))
263
+ send(opts.merge(set_canned_message_module_messages: opts[:messages]))
83
264
  end
84
265
 
85
266
  public_class_method def self.get_device_metadata(opts = {})
@@ -91,7 +272,7 @@ module Meshtastic
91
272
  end
92
273
 
93
274
  public_class_method def self.set_ringtone(opts = {})
94
- send(opts.merge(set_ringtone_message: opts[:ringtone].to_s))
275
+ send(opts.merge(set_ringtone_message: opts[:ringtone]))
95
276
  end
96
277
 
97
278
  public_class_method def self.get_device_connection_status(opts = {})
@@ -107,11 +288,11 @@ module Meshtastic
107
288
  end
108
289
 
109
290
  public_class_method def self.delete_file(opts = {})
110
- send(opts.merge(delete_file_request: opts[:path].to_s))
291
+ send(opts.merge(delete_file_request: opts[:path]))
111
292
  end
112
293
 
113
294
  public_class_method def self.set_scale(opts = {})
114
- send(opts.merge(set_scale: opts[:scale].to_i))
295
+ send(opts.merge(set_scale: opts[:scale]))
115
296
  end
116
297
 
117
298
  public_class_method def self.backup_preferences(opts = {})
@@ -148,15 +329,15 @@ module Meshtastic
148
329
  end
149
330
 
150
331
  public_class_method def self.remove_by_nodenum(opts = {})
151
- send(opts.merge(remove_by_nodenum: opts[:node_num].to_i))
332
+ send(opts.merge(remove_by_nodenum: opts[:node_num]))
152
333
  end
153
334
 
154
335
  public_class_method def self.set_favorite_node(opts = {})
155
- send(opts.merge(set_favorite_node: opts[:node_num].to_i))
336
+ send(opts.merge(set_favorite_node: opts[:node_num]))
156
337
  end
157
338
 
158
339
  public_class_method def self.remove_favorite_node(opts = {})
159
- send(opts.merge(remove_favorite_node: opts[:node_num].to_i))
340
+ send(opts.merge(remove_favorite_node: opts[:node_num]))
160
341
  end
161
342
 
162
343
  public_class_method def self.set_fixed_position(opts = {})
@@ -169,7 +350,7 @@ module Meshtastic
169
350
  end
170
351
 
171
352
  public_class_method def self.set_time(opts = {})
172
- send(opts.merge(set_time_only: opts[:time].to_i))
353
+ send(opts.merge(set_time_only: opts[:time]))
173
354
  end
174
355
 
175
356
  public_class_method def self.get_ui_config(opts = {})
@@ -181,15 +362,15 @@ module Meshtastic
181
362
  end
182
363
 
183
364
  public_class_method def self.set_ignored_node(opts = {})
184
- send(opts.merge(set_ignored_node: opts[:node_num].to_i))
365
+ send(opts.merge(set_ignored_node: opts[:node_num]))
185
366
  end
186
367
 
187
368
  public_class_method def self.remove_ignored_node(opts = {})
188
- send(opts.merge(remove_ignored_node: opts[:node_num].to_i))
369
+ send(opts.merge(remove_ignored_node: opts[:node_num]))
189
370
  end
190
371
 
191
372
  public_class_method def self.toggle_muted_node(opts = {})
192
- send(opts.merge(toggle_muted_node: opts[:node_num].to_i))
373
+ send(opts.merge(toggle_muted_node: opts[:node_num]))
193
374
  end
194
375
 
195
376
  public_class_method def self.begin_edit(opts = {})
@@ -217,7 +398,7 @@ module Meshtastic
217
398
  end
218
399
 
219
400
  public_class_method def self.nodedb_reset(opts = {})
220
- send(opts.merge(nodedb_reset: true))
401
+ send(opts.merge(nodedb_reset: opts.fetch(:preserve_favorites, true)))
221
402
  end
222
403
 
223
404
  public_class_method def self.exit_simulator(opts = {})
@@ -238,9 +419,9 @@ module Meshtastic
238
419
 
239
420
  public_class_method def self.help
240
421
  puts "USAGE:
241
- # Encode an AdminMessage from matching opts keys.
422
+ # Encode exactly one AdminMessage payload without modifying the caller.
242
423
  #{self}.encode(
243
- message: 'optional - existing AdminMessage to fill instead of a new one',
424
+ message: 'optional - existing AdminMessage to copy instead of a new one',
244
425
  session_passkey: 'optional - bytes from a prior get_*_response to authorize sets'
245
426
  )
246
427
 
@@ -250,7 +431,49 @@ module Meshtastic
250
431
  bluetooth_obj: 'optional - BLE handle from Meshtastic::Bluetooth.connect',
251
432
  tcp_obj: 'optional - TCP handle from Meshtastic::TCP.connect',
252
433
  mqtt_obj: 'optional - MQTT client from Meshtastic::MQTT.connect',
253
- want_response: 'optional - request an admin reply (default: true)'
434
+ to: 'optional - unicast node integer or !eighthex; defaults to connected my_node_num; required for MQTT',
435
+ from: 'optional - sender number; radio default zero denotes the local PhoneAPI client',
436
+ channel: 'optional - routing channel index (default zero)',
437
+ want_ack: 'optional - request routing acknowledgment, distinct from an admin reply',
438
+ hop_limit: 'optional - maximum radio hop count',
439
+ psks: 'optional - MQTT channel key map; not public-key admin authorization',
440
+ pki_encrypted: 'optional - enable device-owned PKI on serial BLE or TCP; unsupported over MQTT',
441
+ public_key: 'optional - recipient public-key bytes for radio PKI',
442
+ last_packet_id: 'optional - previous packet number used by transport packet generation',
443
+ want_response: 'optional - defaults true for get requests and false for state changes',
444
+ auto_session: 'optional - acquire a session for remote writes unless a passkey is supplied (default true)',
445
+ refresh_session: 'optional - discard cached session for this automatic acquisition (default false)',
446
+ timeout: 'optional - positive finite receive deadline in seconds for session acquisition (default ten)'
447
+ )
448
+
449
+ # Send admin data and synchronously await correlated readback.
450
+ #{self}.request(
451
+ request_id: 'optional - packet ID from 2 through 0xffffffff; generated when omitted; accepts send options',
452
+ wait: 'optional - false returns only request_id and submission result; true waits for reply (default true)',
453
+ timeout: 'optional - positive finite total response budget in seconds including session acquisition (default ten)'
454
+ )
455
+
456
+ # Decode protobuf bytes or an incoming admin packet.
457
+ #{self}.decode(
458
+ payload: 'optional - serialized AdminMessage bytes when packet is omitted',
459
+ packet: 'optional - FromRadio MeshPacket or Data containing decoded ADMIN_APP bytes'
460
+ )
461
+
462
+ # Extract a matching response and its session passkey.
463
+ #{self}.response(
464
+ packet: 'required - incoming FromRadio or MeshPacket protobuf',
465
+ request_id: 'optional - require this Data.request_id to match the outgoing packet ID',
466
+ from: 'optional - require this numeric responding node; mismatches return nil'
467
+ )
468
+
469
+ # Request legacy ESP32 OTA reboot; deprecated firmware operation.
470
+ #{self}.reboot_ota(
471
+ seconds: 'optional - legacy reboot delay, negative cancels (default five); use ota_request on current firmware'
472
+ )
473
+
474
+ # Send the current OTA loader request protobuf.
475
+ #{self}.ota_request(
476
+ event: 'required - AdminMessage::OTAEvent containing mode and firmware hash; see Admin::Firmware'
254
477
  )
255
478
 
256
479
  # Reboot the node after a delay.
@@ -285,7 +508,7 @@ module Meshtastic
285
508
 
286
509
  # Request a channel by index.
287
510
  #{self}.get_channel(
288
- index: 'optional - channel index to request (default: 0)'
511
+ index: 'optional - zero-based channel index 0 through 7; wire conversion happens here (default zero)'
289
512
  )
290
513
 
291
514
  # Request a radio Config section.
@@ -315,7 +538,7 @@ module Meshtastic
315
538
 
316
539
  # Set canned-message module strings.
317
540
  #{self}.set_canned_messages(
318
- messages: 'required - newline-separated canned message text'
541
+ messages: 'required - pipe-separated canned message text; empty string clears it'
319
542
  )
320
543
 
321
544
  # Request DeviceMetadata (firmware version and hardware).
@@ -482,7 +705,8 @@ module Meshtastic
482
705
 
483
706
  # Clear the node database.
484
707
  #{self}.nodedb_reset(
485
- serial_obj: 'optional - serial handle from Meshtastic::Serial.connect'
708
+ serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
709
+ preserve_favorites: 'optional - retain favorite nodes (default true); some firmware roles always preserve favorites'
486
710
  )
487
711
 
488
712
  # Exit the firmware simulator if running.
@@ -5,9 +5,10 @@
5
5
  require 'google/protobuf'
6
6
 
7
7
  require 'meshtastic/device_ui_pb'
8
+ require 'meshtastic/field_metadata_pb'
8
9
 
9
10
 
10
- descriptor_data = "\n\x17meshtastic/config.proto\x12\nmeshtastic\x1a\x1ameshtastic/device_ui.proto\"\xe1.\n\x06\x43onfig\x12\x31\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32\x1f.meshtastic.Config.DeviceConfigH\x00\x12\x35\n\x08position\x18\x02 \x01(\x0b\x32!.meshtastic.Config.PositionConfigH\x00\x12/\n\x05power\x18\x03 \x01(\x0b\x32\x1e.meshtastic.Config.PowerConfigH\x00\x12\x33\n\x07network\x18\x04 \x01(\x0b\x32 .meshtastic.Config.NetworkConfigH\x00\x12\x33\n\x07\x64isplay\x18\x05 \x01(\x0b\x32 .meshtastic.Config.DisplayConfigH\x00\x12-\n\x04lora\x18\x06 \x01(\x0b\x32\x1d.meshtastic.Config.LoRaConfigH\x00\x12\x37\n\tbluetooth\x18\x07 \x01(\x0b\x32\".meshtastic.Config.BluetoothConfigH\x00\x12\x35\n\x08security\x18\x08 \x01(\x0b\x32!.meshtastic.Config.SecurityConfigH\x00\x12\x39\n\nsessionkey\x18\t \x01(\x0b\x32#.meshtastic.Config.SessionkeyConfigH\x00\x12/\n\tdevice_ui\x18\n \x01(\x0b\x32\x1a.meshtastic.DeviceUIConfigH\x00\x1a\xf6\x06\n\x0c\x44\x65viceConfig\x12\x32\n\x04role\x18\x01 \x01(\x0e\x32$.meshtastic.Config.DeviceConfig.Role\x12\x1a\n\x0eserial_enabled\x18\x02 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x0b\x62utton_gpio\x18\x04 \x01(\r\x12\x13\n\x0b\x62uzzer_gpio\x18\x05 \x01(\r\x12I\n\x10rebroadcast_mode\x18\x06 \x01(\x0e\x32/.meshtastic.Config.DeviceConfig.RebroadcastMode\x12 \n\x18node_info_broadcast_secs\x18\x07 \x01(\r\x12\"\n\x1a\x64ouble_tap_as_button_press\x18\x08 \x01(\x08\x12\x16\n\nis_managed\x18\t \x01(\x08\x42\x02\x18\x01\x12\x1c\n\x14\x64isable_triple_click\x18\n \x01(\x08\x12\r\n\x05tzdef\x18\x0b \x01(\t\x12\x1e\n\x16led_heartbeat_disabled\x18\x0c \x01(\x08\x12?\n\x0b\x62uzzer_mode\x18\r \x01(\x0e\x32*.meshtastic.Config.DeviceConfig.BuzzerMode\"\xd4\x01\n\x04Role\x12\n\n\x06\x43LIENT\x10\x00\x12\x0f\n\x0b\x43LIENT_MUTE\x10\x01\x12\n\n\x06ROUTER\x10\x02\x12\x15\n\rROUTER_CLIENT\x10\x03\x1a\x02\x08\x01\x12\x10\n\x08REPEATER\x10\x04\x1a\x02\x08\x01\x12\x0b\n\x07TRACKER\x10\x05\x12\n\n\x06SENSOR\x10\x06\x12\x07\n\x03TAK\x10\x07\x12\x11\n\rCLIENT_HIDDEN\x10\x08\x12\x12\n\x0eLOST_AND_FOUND\x10\t\x12\x0f\n\x0bTAK_TRACKER\x10\n\x12\x0f\n\x0bROUTER_LATE\x10\x0b\x12\x0f\n\x0b\x43LIENT_BASE\x10\x0c\"s\n\x0fRebroadcastMode\x12\x07\n\x03\x41LL\x10\x00\x12\x15\n\x11\x41LL_SKIP_DECODING\x10\x01\x12\x0e\n\nLOCAL_ONLY\x10\x02\x12\x0e\n\nKNOWN_ONLY\x10\x03\x12\x08\n\x04NONE\x10\x04\x12\x16\n\x12\x43ORE_PORTNUMS_ONLY\x10\x05\"i\n\nBuzzerMode\x12\x0f\n\x0b\x41LL_ENABLED\x10\x00\x12\x0c\n\x08\x44ISABLED\x10\x01\x12\x16\n\x12NOTIFICATIONS_ONLY\x10\x02\x12\x0f\n\x0bSYSTEM_ONLY\x10\x03\x12\x13\n\x0f\x44IRECT_MSG_ONLY\x10\x04\x1a\x91\x05\n\x0ePositionConfig\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12(\n position_broadcast_smart_enabled\x18\x02 \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\x03 \x01(\x08\x12\x17\n\x0bgps_enabled\x18\x04 \x01(\x08\x42\x02\x18\x01\x12\x1b\n\x13gps_update_interval\x18\x05 \x01(\r\x12\x1c\n\x10gps_attempt_time\x18\x06 \x01(\rB\x02\x18\x01\x12\x16\n\x0eposition_flags\x18\x07 \x01(\r\x12\x0f\n\x07rx_gpio\x18\x08 \x01(\r\x12\x0f\n\x07tx_gpio\x18\t \x01(\r\x12(\n broadcast_smart_minimum_distance\x18\n \x01(\r\x12-\n%broadcast_smart_minimum_interval_secs\x18\x0b \x01(\r\x12\x13\n\x0bgps_en_gpio\x18\x0c \x01(\r\x12;\n\x08gps_mode\x18\r \x01(\x0e\x32).meshtastic.Config.PositionConfig.GpsMode\"\xab\x01\n\rPositionFlags\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x41LTITUDE\x10\x01\x12\x10\n\x0c\x41LTITUDE_MSL\x10\x02\x12\x16\n\x12GEOIDAL_SEPARATION\x10\x04\x12\x07\n\x03\x44OP\x10\x08\x12\t\n\x05HVDOP\x10\x10\x12\r\n\tSATINVIEW\x10 \x12\n\n\x06SEQ_NO\x10@\x12\x0e\n\tTIMESTAMP\x10\x80\x01\x12\x0c\n\x07HEADING\x10\x80\x02\x12\n\n\x05SPEED\x10\x80\x04\"5\n\x07GpsMode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\x84\x02\n\x0bPowerConfig\x12\x17\n\x0fis_power_saving\x18\x01 \x01(\x08\x12&\n\x1eon_battery_shutdown_after_secs\x18\x02 \x01(\r\x12\x1f\n\x17\x61\x64\x63_multiplier_override\x18\x03 \x01(\x02\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x10\n\x08sds_secs\x18\x06 \x01(\r\x12\x0f\n\x07ls_secs\x18\x07 \x01(\r\x12\x15\n\rmin_wake_secs\x18\x08 \x01(\r\x12\"\n\x1a\x64\x65vice_battery_ina_address\x18\t \x01(\r\x12\x18\n\x10powermon_enables\x18 \x01(\x04\x1a\xe5\x03\n\rNetworkConfig\x12\x14\n\x0cwifi_enabled\x18\x01 \x01(\x08\x12\x11\n\twifi_ssid\x18\x03 \x01(\t\x12\x10\n\x08wifi_psk\x18\x04 \x01(\t\x12\x12\n\nntp_server\x18\x05 \x01(\t\x12\x13\n\x0b\x65th_enabled\x18\x06 \x01(\x08\x12\x42\n\x0c\x61\x64\x64ress_mode\x18\x07 \x01(\x0e\x32,.meshtastic.Config.NetworkConfig.AddressMode\x12@\n\x0bipv4_config\x18\x08 \x01(\x0b\x32+.meshtastic.Config.NetworkConfig.IpV4Config\x12\x16\n\x0ersyslog_server\x18\t \x01(\t\x12\x19\n\x11\x65nabled_protocols\x18\n \x01(\r\x12\x14\n\x0cipv6_enabled\x18\x0b \x01(\x08\x1a\x46\n\nIpV4Config\x12\n\n\x02ip\x18\x01 \x01(\x07\x12\x0f\n\x07gateway\x18\x02 \x01(\x07\x12\x0e\n\x06subnet\x18\x03 \x01(\x07\x12\x0b\n\x03\x64ns\x18\x04 \x01(\x07\"#\n\x0b\x41\x64\x64ressMode\x12\x08\n\x04\x44HCP\x10\x00\x12\n\n\x06STATIC\x10\x01\"4\n\rProtocolFlags\x12\x10\n\x0cNO_BROADCAST\x10\x00\x12\x11\n\rUDP_BROADCAST\x10\x01\x1a\xc2\x08\n\rDisplayConfig\x12\x16\n\x0escreen_on_secs\x18\x01 \x01(\r\x12V\n\ngps_format\x18\x02 \x01(\x0e\x32>.meshtastic.Config.DisplayConfig.DeprecatedGpsCoordinateFormatB\x02\x18\x01\x12!\n\x19\x61uto_screen_carousel_secs\x18\x03 \x01(\r\x12\x1d\n\x11\x63ompass_north_top\x18\x04 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x0b\x66lip_screen\x18\x05 \x01(\x08\x12<\n\x05units\x18\x06 \x01(\x0e\x32-.meshtastic.Config.DisplayConfig.DisplayUnits\x12\x37\n\x04oled\x18\x07 \x01(\x0e\x32).meshtastic.Config.DisplayConfig.OledType\x12\x41\n\x0b\x64isplaymode\x18\x08 \x01(\x0e\x32,.meshtastic.Config.DisplayConfig.DisplayMode\x12\x14\n\x0cheading_bold\x18\t \x01(\x08\x12\x1d\n\x15wake_on_tap_or_motion\x18\n \x01(\x08\x12P\n\x13\x63ompass_orientation\x18\x0b \x01(\x0e\x32\x33.meshtastic.Config.DisplayConfig.CompassOrientation\x12\x15\n\ruse_12h_clock\x18\x0c \x01(\x08\x12\x1a\n\x12use_long_node_name\x18\r \x01(\x08\x12\x1e\n\x16\x65nable_message_bubbles\x18\x0e \x01(\x08\"+\n\x1d\x44\x65precatedGpsCoordinateFormat\x12\n\n\x06UNUSED\x10\x00\"(\n\x0c\x44isplayUnits\x12\n\n\x06METRIC\x10\x00\x12\x0c\n\x08IMPERIAL\x10\x01\"\x7f\n\x08OledType\x12\r\n\tOLED_AUTO\x10\x00\x12\x10\n\x0cOLED_SSD1306\x10\x01\x12\x0f\n\x0bOLED_SH1106\x10\x02\x12\x0f\n\x0bOLED_SH1107\x10\x03\x12\x17\n\x13OLED_SH1107_128_128\x10\x04\x12\x17\n\x13OLED_SH1107_ROTATED\x10\x05\"A\n\x0b\x44isplayMode\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08TWOCOLOR\x10\x01\x12\x0c\n\x08INVERTED\x10\x02\x12\t\n\x05\x43OLOR\x10\x03\"\xba\x01\n\x12\x43ompassOrientation\x12\r\n\tDEGREES_0\x10\x00\x12\x0e\n\nDEGREES_90\x10\x01\x12\x0f\n\x0b\x44\x45GREES_180\x10\x02\x12\x0f\n\x0b\x44\x45GREES_270\x10\x03\x12\x16\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x12\x17\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x12\x18\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x12\x18\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\x8f\x0b\n\nLoRaConfig\x12\x12\n\nuse_preset\x18\x01 \x01(\x08\x12?\n\x0cmodem_preset\x18\x02 \x01(\x0e\x32).meshtastic.Config.LoRaConfig.ModemPreset\x12\x11\n\tbandwidth\x18\x03 \x01(\r\x12\x15\n\rspread_factor\x18\x04 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x05 \x01(\r\x12\x18\n\x10\x66requency_offset\x18\x06 \x01(\x02\x12\x38\n\x06region\x18\x07 \x01(\x0e\x32(.meshtastic.Config.LoRaConfig.RegionCode\x12\x11\n\thop_limit\x18\x08 \x01(\r\x12\x12\n\ntx_enabled\x18\t \x01(\x08\x12\x10\n\x08tx_power\x18\n \x01(\x05\x12\x13\n\x0b\x63hannel_num\x18\x0b \x01(\r\x12\x1b\n\x13override_duty_cycle\x18\x0c \x01(\x08\x12\x1e\n\x16sx126x_rx_boosted_gain\x18\r \x01(\x08\x12\x1a\n\x12override_frequency\x18\x0e \x01(\x02\x12\x17\n\x0fpa_fan_disabled\x18\x0f \x01(\x08\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x13\n\x0bignore_mqtt\x18h \x01(\x08\x12\x19\n\x11\x63onfig_ok_to_mqtt\x18i \x01(\x08\x12@\n\x0c\x66\x65m_lna_mode\x18j \x01(\x0e\x32*.meshtastic.Config.LoRaConfig.FEM_LNA_Mode\x12\x17\n\x0fserial_hal_only\x18k \x01(\x08\"\xc8\x03\n\nRegionCode\x12\t\n\x05UNSET\x10\x00\x12\x06\n\x02US\x10\x01\x12\n\n\x06\x45U_433\x10\x02\x12\n\n\x06\x45U_868\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t\x12\x06\n\x02IN\x10\n\x12\n\n\x06NZ_865\x10\x0b\x12\x06\n\x02TH\x10\x0c\x12\x0b\n\x07LORA_24\x10\r\x12\n\n\x06UA_433\x10\x0e\x12\x0e\n\x06UA_868\x10\x0f\x1a\x02\x08\x01\x12\n\n\x06MY_433\x10\x10\x12\n\n\x06MY_919\x10\x11\x12\n\n\x06SG_923\x10\x12\x12\n\n\x06PH_433\x10\x13\x12\n\n\x06PH_868\x10\x14\x12\n\n\x06PH_915\x10\x15\x12\x0b\n\x07\x41NZ_433\x10\x16\x12\n\n\x06KZ_433\x10\x17\x12\n\n\x06KZ_863\x10\x18\x12\n\n\x06NP_865\x10\x19\x12\n\n\x06\x42R_902\x10\x1a\x12\x0b\n\x07ITU1_2M\x10\x1b\x12\x0b\n\x07ITU2_2M\x10\x1c\x12\n\n\x06\x45U_866\x10\x1d\x12\n\n\x06\x45U_874\x10\x1e\x12\n\n\x06\x45U_917\x10\x1f\x12\x0c\n\x08\x45U_N_868\x10 \x12\x0b\n\x07ITU3_2M\x10!\x12\r\n\tITU1_70CM\x10\"\x12\r\n\tITU2_70CM\x10#\x12\r\n\tITU3_70CM\x10$\x12\x0e\n\nITU2_125CM\x10%\"\xad\x02\n\x0bModemPreset\x12\r\n\tLONG_FAST\x10\x00\x12\x11\n\tLONG_SLOW\x10\x01\x1a\x02\x08\x01\x12\x16\n\x0eVERY_LONG_SLOW\x10\x02\x1a\x02\x08\x01\x12\x0f\n\x0bMEDIUM_SLOW\x10\x03\x12\x0f\n\x0bMEDIUM_FAST\x10\x04\x12\x0e\n\nSHORT_SLOW\x10\x05\x12\x0e\n\nSHORT_FAST\x10\x06\x12\x11\n\rLONG_MODERATE\x10\x07\x12\x0f\n\x0bSHORT_TURBO\x10\x08\x12\x0e\n\nLONG_TURBO\x10\t\x12\r\n\tLITE_FAST\x10\n\x12\r\n\tLITE_SLOW\x10\x0b\x12\x0f\n\x0bNARROW_FAST\x10\x0c\x12\x0f\n\x0bNARROW_SLOW\x10\r\x12\r\n\tTINY_FAST\x10\x0e\x12\r\n\tTINY_SLOW\x10\x0f\x12\x10\n\x0cMEDIUM_TURBO\x10\x10\":\n\x0c\x46\x45M_LNA_Mode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\xad\x01\n\x0f\x42luetoothConfig\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12<\n\x04mode\x18\x02 \x01(\x0e\x32..meshtastic.Config.BluetoothConfig.PairingMode\x12\x11\n\tfixed_pin\x18\x03 \x01(\r\"8\n\x0bPairingMode\x12\x0e\n\nRANDOM_PIN\x10\x00\x12\r\n\tFIXED_PIN\x10\x01\x12\n\n\x06NO_PIN\x10\x02\x1a\x9c\x03\n\x0eSecurityConfig\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x13\n\x0bprivate_key\x18\x02 \x01(\x0c\x12\x11\n\tadmin_key\x18\x03 \x03(\x0c\x12\x12\n\nis_managed\x18\x04 \x01(\x08\x12\x16\n\x0eserial_enabled\x18\x05 \x01(\x08\x12\x1d\n\x15\x64\x65\x62ug_log_api_enabled\x18\x06 \x01(\x08\x12\x1d\n\x15\x61\x64min_channel_enabled\x18\x08 \x01(\x08\x12X\n\x17packet_signature_policy\x18\t \x01(\x0e\x32\x37.meshtastic.Config.SecurityConfig.PacketSignaturePolicy\"\x89\x01\n\x15PacketSignaturePolicy\x12&\n\"PACKET_SIGNATURE_POLICY_COMPATIBLE\x10\x00\x12$\n PACKET_SIGNATURE_POLICY_BALANCED\x10\x01\x12\"\n\x1ePACKET_SIGNATURE_POLICY_STRICT\x10\x02\x1a\x12\n\x10SessionkeyConfigB\x11\n\x0fpayload_variantBb\n\x14org.meshtastic.protoB\x0c\x43onfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3"
11
+ descriptor_data = "\n\x17meshtastic/config.proto\x12\nmeshtastic\x1a\x1ameshtastic/device_ui.proto\x1a\x1fmeshtastic/field_metadata.proto\"\x80\x31\n\x06\x43onfig\x12\x31\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32\x1f.meshtastic.Config.DeviceConfigH\x00\x12\x35\n\x08position\x18\x02 \x01(\x0b\x32!.meshtastic.Config.PositionConfigH\x00\x12/\n\x05power\x18\x03 \x01(\x0b\x32\x1e.meshtastic.Config.PowerConfigH\x00\x12\x33\n\x07network\x18\x04 \x01(\x0b\x32 .meshtastic.Config.NetworkConfigH\x00\x12\x33\n\x07\x64isplay\x18\x05 \x01(\x0b\x32 .meshtastic.Config.DisplayConfigH\x00\x12-\n\x04lora\x18\x06 \x01(\x0b\x32\x1d.meshtastic.Config.LoRaConfigH\x00\x12\x37\n\tbluetooth\x18\x07 \x01(\x0b\x32\".meshtastic.Config.BluetoothConfigH\x00\x12\x35\n\x08security\x18\x08 \x01(\x0b\x32!.meshtastic.Config.SecurityConfigH\x00\x12\x39\n\nsessionkey\x18\t \x01(\x0b\x32#.meshtastic.Config.SessionkeyConfigH\x00\x12/\n\tdevice_ui\x18\n \x01(\x0b\x32\x1a.meshtastic.DeviceUIConfigH\x00\x1a\xf6\x06\n\x0c\x44\x65viceConfig\x12\x32\n\x04role\x18\x01 \x01(\x0e\x32$.meshtastic.Config.DeviceConfig.Role\x12\x1a\n\x0eserial_enabled\x18\x02 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x0b\x62utton_gpio\x18\x04 \x01(\r\x12\x13\n\x0b\x62uzzer_gpio\x18\x05 \x01(\r\x12I\n\x10rebroadcast_mode\x18\x06 \x01(\x0e\x32/.meshtastic.Config.DeviceConfig.RebroadcastMode\x12 \n\x18node_info_broadcast_secs\x18\x07 \x01(\r\x12\"\n\x1a\x64ouble_tap_as_button_press\x18\x08 \x01(\x08\x12\x16\n\nis_managed\x18\t \x01(\x08\x42\x02\x18\x01\x12\x1c\n\x14\x64isable_triple_click\x18\n \x01(\x08\x12\r\n\x05tzdef\x18\x0b \x01(\t\x12\x1e\n\x16led_heartbeat_disabled\x18\x0c \x01(\x08\x12?\n\x0b\x62uzzer_mode\x18\r \x01(\x0e\x32*.meshtastic.Config.DeviceConfig.BuzzerMode\"\xd4\x01\n\x04Role\x12\n\n\x06\x43LIENT\x10\x00\x12\x0f\n\x0b\x43LIENT_MUTE\x10\x01\x12\n\n\x06ROUTER\x10\x02\x12\x15\n\rROUTER_CLIENT\x10\x03\x1a\x02\x08\x01\x12\x10\n\x08REPEATER\x10\x04\x1a\x02\x08\x01\x12\x0b\n\x07TRACKER\x10\x05\x12\n\n\x06SENSOR\x10\x06\x12\x07\n\x03TAK\x10\x07\x12\x11\n\rCLIENT_HIDDEN\x10\x08\x12\x12\n\x0eLOST_AND_FOUND\x10\t\x12\x0f\n\x0bTAK_TRACKER\x10\n\x12\x0f\n\x0bROUTER_LATE\x10\x0b\x12\x0f\n\x0b\x43LIENT_BASE\x10\x0c\"s\n\x0fRebroadcastMode\x12\x07\n\x03\x41LL\x10\x00\x12\x15\n\x11\x41LL_SKIP_DECODING\x10\x01\x12\x0e\n\nLOCAL_ONLY\x10\x02\x12\x0e\n\nKNOWN_ONLY\x10\x03\x12\x08\n\x04NONE\x10\x04\x12\x16\n\x12\x43ORE_PORTNUMS_ONLY\x10\x05\"i\n\nBuzzerMode\x12\x0f\n\x0b\x41LL_ENABLED\x10\x00\x12\x0c\n\x08\x44ISABLED\x10\x01\x12\x16\n\x12NOTIFICATIONS_ONLY\x10\x02\x12\x0f\n\x0bSYSTEM_ONLY\x10\x03\x12\x13\n\x0f\x44IRECT_MSG_ONLY\x10\x04\x1a\xf8\x05\n\x0ePositionConfig\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12(\n position_broadcast_smart_enabled\x18\x02 \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\x03 \x01(\x08\x12\x17\n\x0bgps_enabled\x18\x04 \x01(\x08\x42\x02\x18\x01\x12\x1b\n\x13gps_update_interval\x18\x05 \x01(\r\x12\x1c\n\x10gps_attempt_time\x18\x06 \x01(\rB\x02\x18\x01\x12\x16\n\x0eposition_flags\x18\x07 \x01(\r\x12\x17\n\x07rx_gpio\x18\x08 \x01(\rB\x06\xca\xf3\x18\x02\x08\x01\x12\x17\n\x07tx_gpio\x18\t \x01(\rB\x06\xca\xf3\x18\x02\x08\x01\x12(\n broadcast_smart_minimum_distance\x18\n \x01(\r\x12-\n%broadcast_smart_minimum_interval_secs\x18\x0b \x01(\r\x12\x13\n\x0bgps_en_gpio\x18\x0c \x01(\r\x12;\n\x08gps_mode\x18\r \x01(\x0e\x32).meshtastic.Config.PositionConfig.GpsMode\"\x82\x02\n\rPositionFlags\x12\t\n\x05UNSET\x10\x00\x12\x63\n\x08\x41LTITUDE\x10\x01\x1aU\xca\xf3\x18Q:\x08\x41ltitudeBEInclude an altitude value in position reports, when one is available.\x12\x10\n\x0c\x41LTITUDE_MSL\x10\x02\x12\x16\n\x12GEOIDAL_SEPARATION\x10\x04\x12\x07\n\x03\x44OP\x10\x08\x12\t\n\x05HVDOP\x10\x10\x12\r\n\tSATINVIEW\x10 \x12\n\n\x06SEQ_NO\x10@\x12\x0e\n\tTIMESTAMP\x10\x80\x01\x12\x0c\n\x07HEADING\x10\x80\x02\x12\n\n\x05SPEED\x10\x80\x04\"5\n\x07GpsMode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\x84\x02\n\x0bPowerConfig\x12\x17\n\x0fis_power_saving\x18\x01 \x01(\x08\x12&\n\x1eon_battery_shutdown_after_secs\x18\x02 \x01(\r\x12\x1f\n\x17\x61\x64\x63_multiplier_override\x18\x03 \x01(\x02\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x10\n\x08sds_secs\x18\x06 \x01(\r\x12\x0f\n\x07ls_secs\x18\x07 \x01(\r\x12\x15\n\rmin_wake_secs\x18\x08 \x01(\r\x12\"\n\x1a\x64\x65vice_battery_ina_address\x18\t \x01(\r\x12\x18\n\x10powermon_enables\x18 \x01(\x04\x1a\xe5\x03\n\rNetworkConfig\x12\x14\n\x0cwifi_enabled\x18\x01 \x01(\x08\x12\x11\n\twifi_ssid\x18\x03 \x01(\t\x12\x10\n\x08wifi_psk\x18\x04 \x01(\t\x12\x12\n\nntp_server\x18\x05 \x01(\t\x12\x13\n\x0b\x65th_enabled\x18\x06 \x01(\x08\x12\x42\n\x0c\x61\x64\x64ress_mode\x18\x07 \x01(\x0e\x32,.meshtastic.Config.NetworkConfig.AddressMode\x12@\n\x0bipv4_config\x18\x08 \x01(\x0b\x32+.meshtastic.Config.NetworkConfig.IpV4Config\x12\x16\n\x0ersyslog_server\x18\t \x01(\t\x12\x19\n\x11\x65nabled_protocols\x18\n \x01(\r\x12\x14\n\x0cipv6_enabled\x18\x0b \x01(\x08\x1a\x46\n\nIpV4Config\x12\n\n\x02ip\x18\x01 \x01(\x07\x12\x0f\n\x07gateway\x18\x02 \x01(\x07\x12\x0e\n\x06subnet\x18\x03 \x01(\x07\x12\x0b\n\x03\x64ns\x18\x04 \x01(\x07\"#\n\x0b\x41\x64\x64ressMode\x12\x08\n\x04\x44HCP\x10\x00\x12\n\n\x06STATIC\x10\x01\"4\n\rProtocolFlags\x12\x10\n\x0cNO_BROADCAST\x10\x00\x12\x11\n\rUDP_BROADCAST\x10\x01\x1a\xc2\x08\n\rDisplayConfig\x12\x16\n\x0escreen_on_secs\x18\x01 \x01(\r\x12V\n\ngps_format\x18\x02 \x01(\x0e\x32>.meshtastic.Config.DisplayConfig.DeprecatedGpsCoordinateFormatB\x02\x18\x01\x12!\n\x19\x61uto_screen_carousel_secs\x18\x03 \x01(\r\x12\x1d\n\x11\x63ompass_north_top\x18\x04 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x0b\x66lip_screen\x18\x05 \x01(\x08\x12<\n\x05units\x18\x06 \x01(\x0e\x32-.meshtastic.Config.DisplayConfig.DisplayUnits\x12\x37\n\x04oled\x18\x07 \x01(\x0e\x32).meshtastic.Config.DisplayConfig.OledType\x12\x41\n\x0b\x64isplaymode\x18\x08 \x01(\x0e\x32,.meshtastic.Config.DisplayConfig.DisplayMode\x12\x14\n\x0cheading_bold\x18\t \x01(\x08\x12\x1d\n\x15wake_on_tap_or_motion\x18\n \x01(\x08\x12P\n\x13\x63ompass_orientation\x18\x0b \x01(\x0e\x32\x33.meshtastic.Config.DisplayConfig.CompassOrientation\x12\x15\n\ruse_12h_clock\x18\x0c \x01(\x08\x12\x1a\n\x12use_long_node_name\x18\r \x01(\x08\x12\x1e\n\x16\x65nable_message_bubbles\x18\x0e \x01(\x08\"+\n\x1d\x44\x65precatedGpsCoordinateFormat\x12\n\n\x06UNUSED\x10\x00\"(\n\x0c\x44isplayUnits\x12\n\n\x06METRIC\x10\x00\x12\x0c\n\x08IMPERIAL\x10\x01\"\x7f\n\x08OledType\x12\r\n\tOLED_AUTO\x10\x00\x12\x10\n\x0cOLED_SSD1306\x10\x01\x12\x0f\n\x0bOLED_SH1106\x10\x02\x12\x0f\n\x0bOLED_SH1107\x10\x03\x12\x17\n\x13OLED_SH1107_128_128\x10\x04\x12\x17\n\x13OLED_SH1107_ROTATED\x10\x05\"A\n\x0b\x44isplayMode\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08TWOCOLOR\x10\x01\x12\x0c\n\x08INVERTED\x10\x02\x12\t\n\x05\x43OLOR\x10\x03\"\xba\x01\n\x12\x43ompassOrientation\x12\r\n\tDEGREES_0\x10\x00\x12\x0e\n\nDEGREES_90\x10\x01\x12\x0f\n\x0b\x44\x45GREES_180\x10\x02\x12\x0f\n\x0b\x44\x45GREES_270\x10\x03\x12\x16\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x12\x17\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x12\x18\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x12\x18\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\xc7\x0c\n\nLoRaConfig\x12\x12\n\nuse_preset\x18\x01 \x01(\x08\x12?\n\x0cmodem_preset\x18\x02 \x01(\x0e\x32).meshtastic.Config.LoRaConfig.ModemPreset\x12\x11\n\tbandwidth\x18\x03 \x01(\r\x12\x15\n\rspread_factor\x18\x04 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x05 \x01(\r\x12\x18\n\x10\x66requency_offset\x18\x06 \x01(\x02\x12\x38\n\x06region\x18\x07 \x01(\x0e\x32(.meshtastic.Config.LoRaConfig.RegionCode\x12\x9d\x01\n\thop_limit\x18\x08 \x01(\rB\x89\x01\xca\xf3\x18\x84\x01\x19\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00\x1c@:\tHop LimitBIHow many times a message may be repeated before it stops being forwarded.J\x1ahops|ttl|range|rebroadcast\x12\x12\n\ntx_enabled\x18\t \x01(\x08\x12\x10\n\x08tx_power\x18\n \x01(\x05\x12\x13\n\x0b\x63hannel_num\x18\x0b \x01(\r\x12\x1b\n\x13override_duty_cycle\x18\x0c \x01(\x08\x12\x1e\n\x16sx126x_rx_boosted_gain\x18\r \x01(\x08\x12\x1a\n\x12override_frequency\x18\x0e \x01(\x02\x12\x17\n\x0fpa_fan_disabled\x18\x0f \x01(\x08\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x13\n\x0bignore_mqtt\x18h \x01(\x08\x12\x19\n\x11\x63onfig_ok_to_mqtt\x18i \x01(\x08\x12@\n\x0c\x66\x65m_lna_mode\x18j \x01(\x0e\x32*.meshtastic.Config.LoRaConfig.FEM_LNA_Mode\x12\x17\n\x0fserial_hal_only\x18k \x01(\x08\"\xc8\x03\n\nRegionCode\x12\t\n\x05UNSET\x10\x00\x12\x06\n\x02US\x10\x01\x12\n\n\x06\x45U_433\x10\x02\x12\n\n\x06\x45U_868\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t\x12\x06\n\x02IN\x10\n\x12\n\n\x06NZ_865\x10\x0b\x12\x06\n\x02TH\x10\x0c\x12\x0b\n\x07LORA_24\x10\r\x12\n\n\x06UA_433\x10\x0e\x12\x0e\n\x06UA_868\x10\x0f\x1a\x02\x08\x01\x12\n\n\x06MY_433\x10\x10\x12\n\n\x06MY_919\x10\x11\x12\n\n\x06SG_923\x10\x12\x12\n\n\x06PH_433\x10\x13\x12\n\n\x06PH_868\x10\x14\x12\n\n\x06PH_915\x10\x15\x12\x0b\n\x07\x41NZ_433\x10\x16\x12\n\n\x06KZ_433\x10\x17\x12\n\n\x06KZ_863\x10\x18\x12\n\n\x06NP_865\x10\x19\x12\n\n\x06\x42R_902\x10\x1a\x12\x0b\n\x07ITU1_2M\x10\x1b\x12\x0b\n\x07ITU2_2M\x10\x1c\x12\n\n\x06\x45U_866\x10\x1d\x12\n\n\x06\x45U_874\x10\x1e\x12\n\n\x06\x45U_917\x10\x1f\x12\x0c\n\x08\x45U_N_868\x10 \x12\x0b\n\x07ITU3_2M\x10!\x12\r\n\tITU1_70CM\x10\"\x12\r\n\tITU2_70CM\x10#\x12\r\n\tITU3_70CM\x10$\x12\x0e\n\nITU2_125CM\x10%\"\xd8\x02\n\x0bModemPreset\x12\x38\n\tLONG_FAST\x10\x00\x1a)\xca\xf3\x18%:\x11Long Range - FastJ\x10longfast|default\x12\x11\n\tLONG_SLOW\x10\x01\x1a\x02\x08\x01\x12\x16\n\x0eVERY_LONG_SLOW\x10\x02\x1a\x02\x08\x01\x12\x0f\n\x0bMEDIUM_SLOW\x10\x03\x12\x0f\n\x0bMEDIUM_FAST\x10\x04\x12\x0e\n\nSHORT_SLOW\x10\x05\x12\x0e\n\nSHORT_FAST\x10\x06\x12\x11\n\rLONG_MODERATE\x10\x07\x12\x0f\n\x0bSHORT_TURBO\x10\x08\x12\x0e\n\nLONG_TURBO\x10\t\x12\r\n\tLITE_FAST\x10\n\x12\r\n\tLITE_SLOW\x10\x0b\x12\x0f\n\x0bNARROW_FAST\x10\x0c\x12\x0f\n\x0bNARROW_SLOW\x10\r\x12\r\n\tTINY_FAST\x10\x0e\x12\r\n\tTINY_SLOW\x10\x0f\x12\x10\n\x0cMEDIUM_TURBO\x10\x10\":\n\x0c\x46\x45M_LNA_Mode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\xad\x01\n\x0f\x42luetoothConfig\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12<\n\x04mode\x18\x02 \x01(\x0e\x32..meshtastic.Config.BluetoothConfig.PairingMode\x12\x11\n\tfixed_pin\x18\x03 \x01(\r\"8\n\x0bPairingMode\x12\x0e\n\nRANDOM_PIN\x10\x00\x12\r\n\tFIXED_PIN\x10\x01\x12\n\n\x06NO_PIN\x10\x02\x1a\x9c\x03\n\x0eSecurityConfig\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x13\n\x0bprivate_key\x18\x02 \x01(\x0c\x12\x11\n\tadmin_key\x18\x03 \x03(\x0c\x12\x12\n\nis_managed\x18\x04 \x01(\x08\x12\x16\n\x0eserial_enabled\x18\x05 \x01(\x08\x12\x1d\n\x15\x64\x65\x62ug_log_api_enabled\x18\x06 \x01(\x08\x12\x1d\n\x15\x61\x64min_channel_enabled\x18\x08 \x01(\x08\x12X\n\x17packet_signature_policy\x18\t \x01(\x0e\x32\x37.meshtastic.Config.SecurityConfig.PacketSignaturePolicy\"\x89\x01\n\x15PacketSignaturePolicy\x12&\n\"PACKET_SIGNATURE_POLICY_COMPATIBLE\x10\x00\x12$\n PACKET_SIGNATURE_POLICY_BALANCED\x10\x01\x12\"\n\x1ePACKET_SIGNATURE_POLICY_STRICT\x10\x02\x1a\x12\n\x10SessionkeyConfigB\x11\n\x0fpayload_variantBb\n\x14org.meshtastic.protoB\x0c\x43onfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3"
11
12
 
12
13
  pool = ::Google::Protobuf::DescriptorPool.generated_pool
13
14
  pool.add_serialized_file(descriptor_data)
@@ -233,6 +233,12 @@ module Meshtastic
233
233
  mesh_packet.want_ack = want_ack
234
234
  mesh_packet.hop_limit = hop_limit
235
235
  mesh_packet.id = generate_packet_id(last_packet_id: last_packet_id)
236
+ mesh_packet.pki_encrypted = opts[:pki_encrypted] if opts.key?(:pki_encrypted)
237
+ mesh_packet.public_key = opts[:public_key] if opts[:public_key]
238
+ if mesh_packet.pki_encrypted
239
+ raise ArgumentError, 'PKI encryption requires a radio transport without host PSK encryption' unless via == :radio && (psks.nil? || psks.empty?)
240
+ raise ArgumentError, 'recipient public_key must contain 32 bytes' unless mesh_packet.public_key.bytesize == 32
241
+ end
236
242
 
237
243
  # When psks is nil/empty, leave the packet decoded so the radio (serial/TCP)
238
244
  # device can apply the channel PSK itself. MQTT callers must pass psks so the
@@ -340,6 +346,8 @@ module Meshtastic
340
346
  channel: channel,
341
347
  want_ack: want_ack,
342
348
  hop_limit: hop_limit,
349
+ pki_encrypted: opts.fetch(:pki_encrypted, false),
350
+ public_key: opts[:public_key],
343
351
  psks: psks
344
352
  )
345
353
  rescue StandardError => e
@@ -5,7 +5,7 @@
5
5
  require 'google/protobuf'
6
6
 
7
7
 
8
- descriptor_data = "\n\x1dmeshtastic/storeforward.proto\x12\nmeshtastic\"\x9c\x07\n\x0fStoreAndForward\x12\x37\n\x02rr\x18\x01 \x01(\x0e\x32+.meshtastic.StoreAndForward.RequestResponse\x12\x37\n\x05stats\x18\x02 \x01(\x0b\x32&.meshtastic.StoreAndForward.StatisticsH\x00\x12\x36\n\x07history\x18\x03 \x01(\x0b\x32#.meshtastic.StoreAndForward.HistoryH\x00\x12:\n\theartbeat\x18\x04 \x01(\x0b\x32%.meshtastic.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBk\n\x14org.meshtastic.protoB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3"
8
+ descriptor_data = "\n\x1dmeshtastic/storeforward.proto\x12\nmeshtastic\"\xb1\x07\n\x0fStoreAndForward\x12\x37\n\x02rr\x18\x01 \x01(\x0e\x32+.meshtastic.StoreAndForward.RequestResponse\x12\x37\n\x05stats\x18\x02 \x01(\x0b\x32&.meshtastic.StoreAndForward.StatisticsH\x00\x12\x36\n\x07history\x18\x03 \x01(\x0b\x32#.meshtastic.StoreAndForward.HistoryH\x00\x12:\n\theartbeat\x18\x04 \x01(\x0b\x32%.meshtastic.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x12\x13\n\x0boriginal_id\x18\x06 \x01(\r\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBk\n\x14org.meshtastic.protoB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3"
9
9
 
10
10
  pool = ::Google::Protobuf::DescriptorPool.generated_pool
11
11
  pool.add_serialized_file(descriptor_data)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Meshtastic
4
- VERSION = '0.0.180'
4
+ VERSION = '0.0.181'
5
5
  end