meshtastic 0.0.186 → 0.0.188

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/documentation/README.md +2 -1
  4. data/documentation/admin-backup.md +82 -3
  5. data/documentation/admin-config.md +28 -1
  6. data/documentation/admin-firmware-migration.md +171 -0
  7. data/documentation/admin-firmware.md +7 -3
  8. data/documentation/admin.md +16 -0
  9. data/documentation/mesh-interface.md +46 -18
  10. data/documentation/mqtt.md +63 -71
  11. data/documentation/waypoint.md +54 -0
  12. data/lib/meshtastic/admin/backup.rb +44 -5
  13. data/lib/meshtastic/admin/channel.rb +3 -8
  14. data/lib/meshtastic/admin/config.rb +63 -1
  15. data/lib/meshtastic/admin/firmware.rb +43 -10
  16. data/lib/meshtastic/admin/validation.rb +78 -0
  17. data/lib/meshtastic/admin.rb +28 -22
  18. data/lib/meshtastic/config_pb.rb +1 -1
  19. data/lib/meshtastic/mesh_interface.rb +123 -43
  20. data/lib/meshtastic/mesh_pb.rb +2 -1
  21. data/lib/meshtastic/module_config.rb +2 -0
  22. data/lib/meshtastic/module_config_pb.rb +1 -1
  23. data/lib/meshtastic/mqtt.rb +21 -12
  24. data/lib/meshtastic/version.rb +1 -1
  25. data/lib/meshtastic/waypoint.rb +85 -0
  26. data/lib/meshtastic.rb +1 -0
  27. data/spec/lib/meshtastic/admin/backup_spec.rb +153 -9
  28. data/spec/lib/meshtastic/admin/config_spec.rb +70 -1
  29. data/spec/lib/meshtastic/admin/firmware_migration_spec.rb +121 -0
  30. data/spec/lib/meshtastic/admin/firmware_spec.rb +1 -1
  31. data/spec/lib/meshtastic/admin/firmware_target_selection_spec.rb +44 -0
  32. data/spec/lib/meshtastic/admin/validation_spec.rb +89 -0
  33. data/spec/lib/meshtastic/admin_spec.rb +77 -0
  34. data/spec/lib/meshtastic/bluetooth_spec.rb +26 -1
  35. data/spec/lib/meshtastic/mesh_interface_spec.rb +54 -0
  36. data/spec/lib/meshtastic/module_config_spec.rb +1 -1
  37. data/spec/lib/meshtastic/mqtt_spec.rb +165 -0
  38. data/spec/lib/meshtastic/serial_spec.rb +26 -0
  39. data/spec/lib/meshtastic/tcp_spec.rb +29 -0
  40. data/spec/lib/meshtastic/waypoint_spec.rb +55 -0
  41. data/spec/support/channel_crypto_reference.rb +29 -0
  42. data/spec/support/firmware_release_artifact_audit.rb +39 -0
  43. metadata +16 -6
@@ -3,6 +3,7 @@
3
3
  require 'timeout'
4
4
  require 'monitor'
5
5
  require 'meshtastic/admin_pb'
6
+ require 'meshtastic/admin/validation'
6
7
  require 'meshtastic/mqtt'
7
8
 
8
9
  module Meshtastic
@@ -19,7 +20,7 @@ module Meshtastic
19
20
  end
20
21
 
21
22
  SKIP = %i[
22
- message transport_obj to from channel want_ack hop_limit
23
+ message transport_obj to from channel channel_id want_ack hop_limit
23
24
  want_response port_num data via psks seconds owner long_name short_name index config_type
24
25
  module_config_type path node_num lat lon altitude time channel_settings channel_pb
25
26
  config module_config messages ringtone scale location event event_code kb_char touch_x
@@ -70,7 +71,7 @@ module Meshtastic
70
71
 
71
72
  message.session_passkey = passkey
72
73
  end
73
- message
74
+ Validation.validate(message: message)
74
75
  end
75
76
 
76
77
  public_class_method def self.send(opts = {})
@@ -86,11 +87,7 @@ module Meshtastic
86
87
  message.session_passkey = acquire_session(opts.merge(connection: connection, target: destination))
87
88
  end
88
89
  want_response = opts.fetch(:want_response) { message.payload_variant.to_s.match?(/\Aget_.*_request\z/) }
89
- data = Meshtastic::Data.new(
90
- portnum: :ADMIN_APP,
91
- payload: message.to_proto,
92
- want_response: want_response
93
- )
90
+ data = Meshtastic::Data.new(portnum: :ADMIN_APP, payload: message.to_proto, want_response: want_response)
94
91
  delivery = opts.except(:transport_obj).merge(data: data, port_num: Meshtastic::PortNum::ADMIN_APP, to: destination)
95
92
  delivery[:"#{type}_obj"] = opts[:transport_obj]
96
93
  delivery[:from] = 0 if connection && !opts.key?(:from)
@@ -176,10 +173,17 @@ module Meshtastic
176
173
  [opts[:target], opts[:connection][:my_node_num]].include?(packet.from)
177
174
  reason = Meshtastic::Routing.decode(packet.decoded.payload).error_reason
178
175
  raise RoutingError.new(reason, opts[:request_id], packet.from) if reason != :NONE && [opts[:target], opts[:connection][:my_node_num]].include?(packet.from)
179
- return { variant: :routing, value: reason, request_id: opts[:request_id], from: packet.from } if reason == :NONE && packet.from == opts[:target] && opts[:variant] == :routing
176
+ if reason == :NONE && packet.from == opts[:target] && opts[:variant] == :routing
177
+ return { variant: :routing, value: reason, request_id: opts[:request_id], from: packet.from,
178
+ delivery: packet.ack_proof_status == :ACK_PROOF_VALID ? :device_verified : :acknowledged,
179
+ security_provenance: { source: :radio, verification: :device_reported, reported: { ack_proof_status: packet.ack_proof_status, xeddsa_signed: packet.xeddsa_signed } } }
180
+ end
180
181
  end
181
182
  reply = response(packet: incoming, request_id: opts[:request_id], from: opts[:target])
182
- return reply if reply && reply[:variant] == opts[:variant]
183
+ if reply && reply[:variant] == opts[:variant]
184
+ reply[:security_provenance].merge!(source: :radio, verification: :device_reported)
185
+ return reply
186
+ end
183
187
 
184
188
  deferred << incoming
185
189
  end
@@ -227,8 +231,9 @@ module Meshtastic
227
231
  return nil unless variant.to_s.end_with?('_response')
228
232
 
229
233
  {
230
- message: message, variant: variant, value: message.public_send(variant),
231
- session_passkey: message.session_passkey, request_id: packet.decoded.request_id, from: packet.from
234
+ message: message, variant: variant, value: message.public_send(variant), delivery: :readback,
235
+ session_passkey: message.session_passkey, request_id: packet.decoded.request_id, from: packet.from,
236
+ security_provenance: { source: :unknown, verification: :untrusted, reported: { ack_proof_status: packet.ack_proof_status, xeddsa_signed: packet.xeddsa_signed } }
232
237
  }
233
238
  end
234
239
 
@@ -338,12 +343,8 @@ module Meshtastic
338
343
  end
339
344
 
340
345
  public_class_method def self.send_input_event(opts = {})
341
- event = opts[:event] || Meshtastic::AdminMessage::InputEvent.new(
342
- event_code: opts[:event_code].to_i,
343
- kb_char: opts[:kb_char].to_i,
344
- touch_x: opts[:touch_x].to_i,
345
- touch_y: opts[:touch_y].to_i
346
- )
346
+ event = opts[:event] || Meshtastic::AdminMessage::InputEvent.new(event_code: opts[:event_code].to_i, kb_char: opts[:kb_char].to_i,
347
+ touch_x: opts[:touch_x].to_i, touch_y: opts[:touch_y].to_i)
347
348
  send(opts.merge(send_input_event: event))
348
349
  end
349
350
 
@@ -471,7 +472,8 @@ module Meshtastic
471
472
  channel: 'optional - routing channel index (default zero)',
472
473
  want_ack: 'optional - request routing acknowledgment, distinct from an admin reply',
473
474
  hop_limit: 'optional - maximum radio hop count',
474
- psks: 'optional - MQTT channel key map; not public-key admin authorization',
475
+ channel_id: 'optional - exact MQTT channel name selecting one PSK and explicit AEAD mode',
476
+ psks: 'optional - MQTT name to Base64 legacy PSK or {psk: Base64, use_aead: boolean} map; not public-key admin authorization',
475
477
  pki_encrypted: 'optional - enable device-owned PKI on serial BLE or TCP; unsupported over MQTT',
476
478
  public_key: 'optional - recipient public-key bytes for radio PKI',
477
479
  last_packet_id: 'optional - previous packet number used by transport packet generation',
@@ -488,6 +490,10 @@ module Meshtastic
488
490
  timeout: 'optional - positive finite total response budget in seconds including session acquisition (default ten)'
489
491
  )
490
492
 
493
+ # Results distinguish :acknowledged, :device_verified and :readback.
494
+ # :device_verified requires a target/request-correlated ACK_PROOF_VALID from the radio queue.
495
+ # security_provenance retains raw proof/signature reports; verification is device_reported, never host-verified.
496
+
491
497
  # Decode protobuf bytes or an incoming admin packet.
492
498
  #{self}.decode(
493
499
  payload: 'optional - serialized AdminMessage bytes when packet is omitted',
@@ -526,8 +532,8 @@ module Meshtastic
526
532
  # Set the node owner User protobuf.
527
533
  #{self}.set_owner(
528
534
  owner: 'optional - Meshtastic::User protobuf to send as set_owner',
529
- long_name: 'optional - owner long name when owner is omitted',
530
- short_name: 'optional - owner short name when owner is omitted'
535
+ long_name: 'optional - owner long name, at most 24 UTF-8 bytes; empty means unchanged on firmware v2.8.1.8e6a88d',
536
+ short_name: 'optional - owner short name, at most four UTF-8 bytes; empty means unchanged'
531
537
  )
532
538
 
533
539
  # Request the node owner User protobuf.
@@ -643,11 +649,11 @@ module Meshtastic
643
649
  # Enable ham-radio identity parameters.
644
650
  #{self}.set_ham_mode(
645
651
  ham: 'optional - HamParameters protobuf to send',
646
- call_sign: 'optional - amateur callsign when ham is omitted',
652
+ call_sign: 'optional - required within ham parameters, nonblank callsign at most seven UTF-8 bytes',
647
653
  tx_power: 'optional - transmit power when ham is omitted',
648
654
  frequency: 'optional - frequency in MHz when ham is omitted',
649
655
  short_name: 'optional - short name when ham is omitted',
650
- long_name: 'optional - long name when ham is omitted'
656
+ long_name: 'optional - ham suffix at most fourteen UTF-8 bytes; composed CALL//suffix must fit 24 bytes; rejected, never truncated'
651
657
  )
652
658
 
653
659
  # Remove a node from the local nodedb by number.
@@ -8,7 +8,7 @@ require 'meshtastic/device_ui_pb'
8
8
  require 'meshtastic/field_metadata_pb'
9
9
 
10
10
 
11
- descriptor_data = "\n\x17meshtastic/config.proto\x12\nmeshtastic\x1a\x1ameshtastic/device_ui.proto\x1a\x1fmeshtastic/field_metadata.proto\"\xdb\x81\x01\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\x8f\x1e\n\x0c\x44\x65viceConfig\x12\x45\n\x04role\x18\x01 \x01(\x0e\x32$.meshtastic.Config.DeviceConfig.RoleB\x11\xca\xf3\x18\r:\x0b\x44\x65vice Role\x12\x1a\n\x0eserial_enabled\x18\x02 \x01(\x08\x42\x02\x18\x01\x12u\n\x0b\x62utton_gpio\x18\x04 \x01(\rB`\xca\xf3\x18\\:\x0b\x42utton GPIOBMGPIO pin for the user button, can be remapped on boards with multiple buttons\x12\x43\n\x0b\x62uzzer_gpio\x18\x05 \x01(\rB.\xca\xf3\x18*:\x0b\x42uzzer GPIOB\x1bGPIO pin for the PWM buzzer\x12\x61\n\x10rebroadcast_mode\x18\x06 \x01(\x0e\x32/.meshtastic.Config.DeviceConfig.RebroadcastModeB\x16\xca\xf3\x18\x12:\x10Rebroadcast Mode\x12\x85\x01\n\x18node_info_broadcast_secs\x18\x07 \x01(\rBc\xca\xf3\x18_*\x01s:\x1cNode Info Broadcast IntervalB<How often node information is sent. Defaults to 900 seconds.\x12\x84\x01\n\x1a\x64ouble_tap_as_button_press\x18\x08 \x01(\x08\x42`\xca\xf3\x18\\:\x14\x44ouble Tap as ButtonBDTreat double tap on supported accelerometers as a user button press.\x12\x16\n\nis_managed\x18\t \x01(\x08\x42\x02\x18\x01\x12i\n\x14\x64isable_triple_click\x18\n \x01(\x08\x42K\xca\xf3\x18G:\x14\x44isable Triple ClickB/Disables the user button triple-press shortcut.\x12@\n\x05tzdef\x18\x0b \x01(\tB1\xca\xf3\x18-:\tTime ZoneB POSIX timezone definition string\x12\xcc\x01\n\x16led_heartbeat_disabled\x18\x0c \x01(\x08\x42\xab\x01\xca\xf3\x18\xa6\x01:\rLED HeartbeatB\x94\x01\x43ontrols the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.\x12L\n\x0b\x62uzzer_mode\x18\r \x01(\x0e\x32*.meshtastic.Config.DeviceConfig.BuzzerModeB\x0b\xca\xf3\x18\x07R\x05\x32.7.0\"\xd4\x0b\n\x04Role\x12H\n\x06\x43LIENT\x10\x00\x1a<\xca\xf3\x18\x38:\x06\x43lientB.App connected or stand alone messaging device.\x12\\\n\x0b\x43LIENT_MUTE\x10\x01\x1aK\xca\xf3\x18G:\x0b\x43lient MuteB8Device that does not forward packets from other devices.\x12\xb0\x01\n\x06ROUTER\x10\x02\x1a\xa3\x01\xca\xf3\x18\x9e\x01:\x06RouterB\x93\x01Infrastructure node on a tower or mountain top only. Not to be used for roofs or mobile nodes. Needs exceptional coverage. Visible in Nodes list.\x12(\n\rROUTER_CLIENT\x10\x03\x1a\x15\x08\x01\xca\xf3\x18\x0f:\rRouter Client\x12\xb3\x01\n\x08REPEATER\x10\x04\x1a\xa4\x01\x08\x01\xca\xf3\x18\x9d\x01:\x08RepeaterB\x90\x01\x44\x65precated infrastructure role that creates gaps in the mesh rebroadcast chain. Switch this node to a Router-based role (Router or Router Late).\x12H\n\x07TRACKER\x10\x05\x1a;\xca\xf3\x18\x37:\x07TrackerB,Broadcasts GPS position packets as priority.\x12\x43\n\x06SENSOR\x10\x06\x1a\x37\xca\xf3\x18\x33:\x06SensorB)Broadcasts telemetry packets as priority.\x12X\n\x03TAK\x10\x07\x1aO\xca\xf3\x18K:\x03TAKBDOptimized for ATAK system communication, reduces routine broadcasts.\x12k\n\rCLIENT_HIDDEN\x10\x08\x1aX\xca\xf3\x18T:\rClient HiddenBCDevice that only broadcasts as needed for stealth or power savings.\x12\x89\x01\n\x0eLOST_AND_FOUND\x10\t\x1au\xca\xf3\x18q:\x0eLost and FoundB_Broadcasts location as message to default channel regularly for to assist with device recovery.\x12h\n\x0bTAK_TRACKER\x10\n\x1aW\xca\xf3\x18S:\x0bTAK TrackerBDEnables automatic TAK PLI broadcasts and reduces routine broadcasts.\x12\xbc\x01\n\x0bROUTER_LATE\x10\x0b\x1a\xaa\x01\xca\xf3\x18\xa5\x01:\x0bRouter LateB\x95\x01Infrastructure node that always rebroadcasts packets once but only after all other modes. Visible in Nodes list. Not a good choice for rooftop nodes.\x12\x85\x01\n\x0b\x43LIENT_BASE\x10\x0c\x1at\xca\xf3\x18p:\x0b\x43lient BaseBaUsed for rooftop nodes to distribute messages more widely from multiple nearby client mute nodes.\"\xc9\x08\n\x0fRebroadcastMode\x12\x8a\x01\n\x03\x41LL\x10\x00\x1a\x80\x01\xca\xf3\x18|:\x03\x41llBuRebroadcast any observed message, if it was on our private channel or from another channel with the same lora params.\x12\xe0\x01\n\x11\x41LL_SKIP_DECODING\x10\x01\x1a\xc8\x01\xca\xf3\x18\xc3\x01:\x11\x41ll Skip DecodingB\xad\x01Same as behavior as ALL but skips packet decoding and simply rebroadcasts them. Only available in Repeater role. Setting this on any other roles will result in ALL behavior.\x12\xcd\x01\n\nLOCAL_ONLY\x10\x02\x1a\xbc\x01\xca\xf3\x18\xb7\x01:\nLocal OnlyB\xa8\x01Ignores observed messages from foreign meshes that are open or those which it cannot decrypt. Only rebroadcasts message on the nodes local primary / secondary channels.\x12\xc8\x01\n\nKNOWN_ONLY\x10\x03\x1a\xb7\x01\xca\xf3\x18\xb2\x01:\nKnown OnlyB\xa3\x01Ignores observed messages from foreign meshes like Local Only, but takes it step further by also ignoring messages from nodes not already in the node\'s known list.\x12\x92\x01\n\x04NONE\x10\x04\x1a\x87\x01\xca\xf3\x18\x82\x01:\x04NoneBzOnly permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.\x12\x95\x01\n\x12\x43ORE_PORTNUMS_ONLY\x10\x05\x1a}\xca\xf3\x18y:\x12\x43ore Portnums OnlyBcOnly rebroadcasts packets from the core portnums: NodeInfo, Text, Position, Telemetry, and Routing.\"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\x96\x0f\n\x0ePositionConfig\x12y\n\x17position_broadcast_secs\x18\x01 \x01(\rBX\xca\xf3\x18T*\x01s:\x12\x42roadcast IntervalB;The longest a node will go without broadcasting a position.\x12>\n position_broadcast_smart_enabled\x18\x02 \x01(\x08\x42\x14\xca\xf3\x18\x10:\x0eSmart Position\x12\xb2\x01\n\x0e\x66ixed_position\x18\x03 \x01(\x08\x42\x99\x01\xca\xf3\x18\x94\x01:\x0e\x46ixed PositionB\x81\x01The last known latitude, longitude and altitude are broadcast over the mesh on the position interval, rather than a live GPS fix.\x12\x17\n\x0bgps_enabled\x18\x04 \x01(\x08\x42\x02\x18\x01\x12^\n\x13gps_update_interval\x18\x05 \x01(\rBA\xca\xf3\x18=*\x01s:\x0fUpdate IntervalB\'How often to try to get a GPS position.\x12\x1c\n\x10gps_attempt_time\x18\x06 \x01(\rB\x02\x18\x01\x12j\n\x0eposition_flags\x18\x07 \x01(\rBR\xca\xf3\x18N:\x0ePosition FlagsB<Optional fields to include when assembling position messages\x12>\n\x07rx_gpio\x18\x08 \x01(\rB-\xca\xf3\x18)\x08\x01:\x10GPS Receive GPIOB\x13GPIO pin for GPS RX\x12?\n\x07tx_gpio\x18\t \x01(\rB.\xca\xf3\x18*\x08\x01:\x11GPS Transmit GPIOB\x13GPIO pin for GPS TX\x12\x94\x01\n broadcast_smart_minimum_distance\x18\n \x01(\rBj\xca\xf3\x18\x66*\x01m:\x10Minimum DistanceBOThe minimum change in distance before a smart position broadcast is considered.\x12\xa0\x01\n%broadcast_smart_minimum_interval_secs\x18\x0b \x01(\rBq\xca\xf3\x18m*\x01s:\x10Minimum IntervalBVThe shortest interval between position updates once the minimum distance has been met.\x12\x41\n\x0bgps_en_gpio\x18\x0c \x01(\rB,\xca\xf3\x18(\x08\x01:\x0bGPS EN GPIOB\x17GPIO pin for GPS enable\x12K\n\x08gps_mode\x18\r \x01(\x0e\x32).meshtastic.Config.PositionConfig.GpsModeB\x0e\xca\xf3\x18\n:\x08GPS Mode\"\xdc\x04\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\x32\n\x0c\x41LTITUDE_MSL\x10\x02\x1a \xca\xf3\x18\x1c:\x1a\x41ltitude is Mean Sea Level\x12\x39\n\x12GEOIDAL_SEPARATION\x10\x04\x1a!\xca\xf3\x18\x1d:\x1b\x41ltitude Geoidal Separation\x12U\n\x03\x44OP\x10\x08\x1aL\xca\xf3\x18H:\x03\x44OPBAInclude the dilution of precision value. PDOP is used by default.\x12`\n\x05HVDOP\x10\x10\x1aU\xca\xf3\x18Q:\x0bHDOP / VDOPBBIf DOP is set, send separate HDOP and VDOP values instead of PDOP.\x12)\n\tSATINVIEW\x10 \x1a\x1a\xca\xf3\x18\x16:\x14Number of satellites\x12!\n\x06SEQ_NO\x10@\x1a\x15\xca\xf3\x18\x11:\x0fSequence number\x12\x1f\n\tTIMESTAMP\x10\x80\x01\x1a\x0f\xca\xf3\x18\x0b:\tTimestamp\x12#\n\x07HEADING\x10\x80\x02\x1a\x15\xca\xf3\x18\x11:\x0fVehicle heading\x12\x1f\n\x05SPEED\x10\x80\x04\x1a\x13\xca\xf3\x18\x0f:\rVehicle speed\"g\n\x07GpsMode\x12\x1c\n\x08\x44ISABLED\x10\x00\x1a\x0e\xca\xf3\x18\n:\x08\x44isabled\x12\x1a\n\x07\x45NABLED\x10\x01\x1a\r\xca\xf3\x18\t:\x07\x45nabled\x12\"\n\x0bNOT_PRESENT\x10\x02\x1a\x11\xca\xf3\x18\r:\x0bNot Present\x1a\xb8\x05\n\x0bPowerConfig\x12\x98\x02\n\x0fis_power_saving\x18\x01 \x01(\x08\x42\xfe\x01\xca\xf3\x18\xf9\x01:\x0cPower SavingB\xe8\x01Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don\'t use this setting if you want to use your device with the phone apps or are using a device without a user button.\x12\xa0\x01\n\x1eon_battery_shutdown_after_secs\x18\x02 \x01(\rBx\xca\xf3\x18t*\x01s:\x16Shutdown on Power LossBWHow long after external power is removed before the device powers off. Zero to disable.\x12\x33\n\x17\x61\x64\x63_multiplier_override\x18\x03 \x01(\x02\x42\x12\xca\xf3\x18\x0e:\x0c\x41\x44\x43 Override\x12>\n\x13wait_bluetooth_secs\x18\x04 \x01(\rB!\xca\xf3\x18\x1d:\x1bWait for Bluetooth Duration\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\xc3\x08\n\rNetworkConfig\x12i\n\x0cwifi_enabled\x18\x01 \x01(\x08\x42S\xca\xf3\x18O:\x0cWiFi EnabledB?Enabling WiFi will disable the bluetooth connection to the app.\x12>\n\twifi_ssid\x18\x03 \x01(\tB+\xca\xf3\x18\':\x04SSIDB\x1fWiFi network name to connect to\x12\x42\n\x08wifi_psk\x18\x04 \x01(\tB0\xca\xf3\x18,:\x08PasswordB WiFi password for authentication\x12]\n\nntp_server\x18\x05 \x01(\tBI\xca\xf3\x18\x45:\nNTP ServerB7NTP server address. Defaults to meshtastic.pool.ntp.org\x12p\n\x0b\x65th_enabled\x18\x06 \x01(\x08\x42[\xca\xf3\x18W:\x10\x45thernet EnabledBCEnabling Ethernet will disable the bluetooth connection to the app.\x12V\n\x0c\x61\x64\x64ress_mode\x18\x07 \x01(\x0e\x32,.meshtastic.Config.NetworkConfig.AddressModeB\x12\xca\xf3\x18\x0e:\x0c\x41\x64\x64ress Mode\x12@\n\x0bipv4_config\x18\x08 \x01(\x0b\x32+.meshtastic.Config.NetworkConfig.IpV4Config\x12,\n\x0ersyslog_server\x18\t \x01(\tB\x14\xca\xf3\x18\x10:\x0eRsyslog Server\x12v\n\x11\x65nabled_protocols\x18\n \x01(\rB[\xca\xf3\x18W:\x11\x45nabled ProtocolsB;Enable broadcasting packets via UDP over the local network.R\x05\x32.6.0\x12\"\n\x0cipv6_enabled\x18\x0b \x01(\x08\x42\x0c\xca\xf3\x18\x08R\x06\x32.7.16\x1ax\n\nIpV4Config\x12\x14\n\x02ip\x18\x01 \x01(\x07\x42\x08\xca\xf3\x18\x04:\x02IP\x12\x1e\n\x07gateway\x18\x02 \x01(\x07\x42\r\xca\xf3\x18\t:\x07Gateway\x12\x1c\n\x06subnet\x18\x03 \x01(\x07\x42\x0c\xca\xf3\x18\x08:\x06Subnet\x12\x16\n\x03\x64ns\x18\x04 \x01(\x07\x42\t\xca\xf3\x18\x05:\x03\x44NS\"=\n\x0b\x41\x64\x64ressMode\x12\x14\n\x04\x44HCP\x10\x00\x1a\n\xca\xf3\x18\x06:\x04\x44HCP\x12\x18\n\x06STATIC\x10\x01\x1a\x0c\xca\xf3\x18\x08:\x06Static\"U\n\rProtocolFlags\x12\x1c\n\x0cNO_BROADCAST\x10\x00\x1a\n\xca\xf3\x18\x06:\x04None\x12&\n\rUDP_BROADCAST\x10\x01\x1a\x13\xca\xf3\x18\x0f:\rUDP Broadcast\x1a\x8b\x13\n\rDisplayConfig\x12\x89\x01\n\x0escreen_on_secs\x18\x01 \x01(\rBq\xca\xf3\x18m*\x01s:\rScreen on forBYHow long the screen remains on after the user button is pressed or messages are received.\x12\x62\n\ngps_format\x18\x02 \x01(\x0e\x32>.meshtastic.Config.DisplayConfig.DeprecatedGpsCoordinateFormatB\x0e\x18\x01\xca\xf3\x18\x08Z\x06\x32.7.10\x12\x8e\x01\n\x19\x61uto_screen_carousel_secs\x18\x03 \x01(\rBk\xca\xf3\x18g*\x01s:\x11\x43\x61rousel IntervalBOAutomatically moves to the next screen page, like a carousel, on this interval.\x12\x8e\x01\n\x11\x63ompass_north_top\x18\x04 \x01(\x08\x42s\x18\x01\xca\xf3\x18m:\x12\x41lways point northBPThe compass heading on the screen outside of the circle will always point north.Z\x05\x32.7.1\x12>\n\x0b\x66lip_screen\x18\x05 \x01(\x08\x42)\xca\xf3\x18%:\x0b\x46lip ScreenB\x16\x46lip screen vertically\x12t\n\x05units\x18\x06 \x01(\x0e\x32-.meshtastic.Config.DisplayConfig.DisplayUnitsB6\xca\xf3\x18\x32:\rDisplay UnitsB!Units shown on the device screen.\x12s\n\x04oled\x18\x07 \x01(\x0e\x32).meshtastic.Config.DisplayConfig.OledTypeB:\xca\xf3\x18\x36:\tOLED TypeB)Override automatic OLED screen detection.\x12v\n\x0b\x64isplaymode\x18\x08 \x01(\x0e\x32,.meshtastic.Config.DisplayConfig.DisplayModeB3\xca\xf3\x18/:\x0c\x44isplay ModeB\x1fOverride default screen layout.\x12N\n\x0cheading_bold\x18\t \x01(\x08\x42\x38\xca\xf3\x18\x34:\x0c\x42old HeadingB$Bold the heading text on the screen.\x12z\n\x15wake_on_tap_or_motion\x18\n \x01(\x08\x42[\xca\xf3\x18W:\x1cWake Screen on tap or motionB7Requires that there be an accelerometer on your device.\x12\xb7\x01\n\x13\x63ompass_orientation\x18\x0b \x01(\x0e\x32\x33.meshtastic.Config.DisplayConfig.CompassOrientationBe\xca\xf3\x18\x61:\x13\x43ompass OrientationBJIndicates how to rotate or invert the compass output for accurate display.\x12\\\n\ruse_12h_clock\x18\x0c \x01(\x08\x42\x45\xca\xf3\x18\x41:\r12 Hour ClockB(Sets the screen clock format to 12-hour.R\x06\x32.5.22\x12(\n\x12use_long_node_name\x18\r \x01(\x08\x42\x0c\xca\xf3\x18\x08R\x06\x32.7.13\x12,\n\x16\x65nable_message_bubbles\x18\x0e \x01(\x08\x42\x0c\xca\xf3\x18\x08R\x06\x32.7.19\"+\n\x1d\x44\x65precatedGpsCoordinateFormat\x12\n\n\x06UNUSED\x10\x00\"F\n\x0c\x44isplayUnits\x12\x18\n\x06METRIC\x10\x00\x1a\x0c\xca\xf3\x18\x08:\x06Metric\x12\x1c\n\x08IMPERIAL\x10\x01\x1a\x0e\xca\xf3\x18\n:\x08Imperial\"\xf7\x01\n\x08OledType\x12)\n\tOLED_AUTO\x10\x00\x1a\x1a\xca\xf3\x18\x16:\x14\x44\x65tect Automatically\x12 \n\x0cOLED_SSD1306\x10\x01\x1a\x0e\xca\xf3\x18\n:\x08SSD 1306\x12\x1e\n\x0bOLED_SH1106\x10\x02\x1a\r\xca\xf3\x18\t:\x07SH 1106\x12\x1e\n\x0bOLED_SH1107\x10\x03\x1a\r\xca\xf3\x18\t:\x07SH 1107\x12.\n\x13OLED_SH1107_128_128\x10\x04\x1a\x15\xca\xf3\x18\x11:\x0fSH 1107 128x128\x12.\n\x13OLED_SH1107_ROTATED\x10\x05\x1a\x15\xca\xf3\x18\x11:\x0fSH 1107 Rotated\"\xd6\x01\n\x0b\x44isplayMode\x12/\n\x07\x44\x45\x46\x41ULT\x10\x00\x1a\"\xca\xf3\x18\x1e:\x1c\x44\x65\x66\x61ult 128x64 screen layout\x12\x32\n\x08TWOCOLOR\x10\x01\x1a$\xca\xf3\x18 :\x1eOptimized for 2 color displays\x12\x38\n\x08INVERTED\x10\x02\x1a*\xca\xf3\x18&:$Inverted top bar for 2 Color display\x12(\n\x05\x43OLOR\x10\x03\x1a\x1d\xca\xf3\x18\x19:\x17TFT Full Color Displays\"\xc0\x02\n\x12\x43ompassOrientation\x12\x18\n\tDEGREES_0\x10\x00\x1a\t\xca\xf3\x18\x05:\x03\x30\xc2\xb0\x12\x1a\n\nDEGREES_90\x10\x01\x1a\n\xca\xf3\x18\x06:\x04\x39\x30\xc2\xb0\x12\x1c\n\x0b\x44\x45GREES_180\x10\x02\x1a\x0b\xca\xf3\x18\x07:\x05\x31\x38\x30\xc2\xb0\x12\x1c\n\x0b\x44\x45GREES_270\x10\x03\x1a\x0b\xca\xf3\x18\x07:\x05\x32\x37\x30\xc2\xb0\x12*\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x1a\x12\xca\xf3\x18\x0e:\x0c\x30\xc2\xb0 Inverted\x12,\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x1a\x13\xca\xf3\x18\x0f:\r90\xc2\xb0 Inverted\x12.\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x1a\x14\xca\xf3\x18\x10:\x0e\x31\x38\x30\xc2\xb0 Inverted\x12.\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\x14\xca\xf3\x18\x10:\x0e\x32\x37\x30\xc2\xb0 Inverted\x1a\x85 \n\nLoRaConfig\x12\x80\x01\n\nuse_preset\x18\x01 \x01(\x08\x42l\xca\xf3\x18h:\nUse PresetBZUse the modem preset settings instead of a manual bandwidth, spread factor and coding rate\x12N\n\x0cmodem_preset\x18\x02 \x01(\x0e\x32).meshtastic.Config.LoRaConfig.ModemPresetB\r\xca\xf3\x18\t:\x07Presets\x12\'\n\tbandwidth\x18\x03 \x01(\rB\x14\xca\xf3\x18\x10*\x03kHz:\tBandwidth\x12u\n\rspread_factor\x18\x04 \x01(\rB^\xca\xf3\x18Z\x19\x00\x00\x00\x00\x00\x00\x14@!\x00\x00\x00\x00\x00\x00(@:\rSpread FactorB7Number of chirps per symbol, as 2 raised to this value.\x12&\n\x0b\x63oding_rate\x18\x05 \x01(\rB\x11\xca\xf3\x18\r:\x0b\x43oding Rate\x12\x18\n\x10\x66requency_offset\x18\x06 \x01(\x02\x12w\n\x06region\x18\x07 \x01(\x0e\x32(.meshtastic.Config.LoRaConfig.RegionCodeB=\xca\xf3\x18\x39:\x06RegionB/The region where you will be using your radios.\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\x84\x01\n\ntx_enabled\x18\t \x01(\x08\x42p\xca\xf3\x18l:\x10Transmit EnabledBXAllow the LoRa radio to transmit. Turn off while hot-swapping antennas or bench testing.\x12\xd3\x01\n\x08tx_power\x18\n \x01(\x05\x42\xc0\x01\xca\xf3\x18\xbb\x01\x19\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00>@*\x03\x64\x42m:\x0eTransmit PowerBxRadio transmit power. Leave at zero to use the highest level legal for the region, which is what most radios should use.J\x18tx|power|dbm|output|gain\x12\xe0\x01\n\x0b\x63hannel_num\x18\x0b \x01(\rB\xca\x01\xca\xf3\x18\xc5\x01:\x0e\x46requency SlotB\xb2\x01Your node\xe2\x80\x99s operating frequency is calculated based on the region, modem preset, and this field. When 0, the slot is automatically calculated based on the primary channel name.\x12\x36\n\x13override_duty_cycle\x18\x0c \x01(\x08\x42\x19\xca\xf3\x18\x15:\x13Override Duty Cycle\x12i\n\x16sx126x_rx_boosted_gain\x18\r \x01(\x08\x42I\xca\xf3\x18\x45:\x0fRX Boosted GainB2Enable RX boosted gain mode on SX126X based radios\x12\x34\n\x12override_frequency\x18\x0e \x01(\x02\x42\x18\xca\xf3\x18\x14:\x12\x46requency Override\x12.\n\x0fpa_fan_disabled\x18\x0f \x01(\x08\x42\x15\xca\xf3\x18\x11:\x0fPA Fan Disabled\x12.\n\x0fignore_incoming\x18g \x03(\rB\x15\xca\xf3\x18\x11:\x0fIgnore Incoming\x12y\n\x0bignore_mqtt\x18h \x01(\x08\x42\x64\xca\xf3\x18`:\x0bIgnore MQTTBQIgnore packets received over LoRa that travelled via MQTT anywhere on their path.\x12+\n\x11\x63onfig_ok_to_mqtt\x18i \x01(\x08\x42\x10\xca\xf3\x18\x0c:\nOk to MQTT\x12N\n\x0c\x66\x65m_lna_mode\x18j \x01(\x0e\x32*.meshtastic.Config.LoRaConfig.FEM_LNA_ModeB\x0c\xca\xf3\x18\x08R\x06\x32.7.20\x12\x17\n\x0fserial_hal_only\x18k \x01(\x08\"\x86\x0b\n\nRegionCode\x12$\n\x05UNSET\x10\x00\x1a\x19\xca\xf3\x18\x15:\x13Please set a region\x12\x1b\n\x02US\x10\x01\x1a\x13\xca\xf3\x18\x0f:\rUnited States\x12\'\n\x06\x45U_433\x10\x02\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 433MHz\x12\'\n\x06\x45U_868\x10\x03\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 868MHz\x12\x13\n\x02\x43N\x10\x04\x1a\x0b\xca\xf3\x18\x07:\x05\x43hina\x12\x13\n\x02JP\x10\x05\x1a\x0b\xca\xf3\x18\x07:\x05Japan\x12&\n\x03\x41NZ\x10\x06\x1a\x1d\xca\xf3\x18\x19:\x17\x41ustralia / New Zealand\x12\x13\n\x02KR\x10\x07\x1a\x0b\xca\xf3\x18\x07:\x05Korea\x12\x14\n\x02TW\x10\x08\x1a\x0c\xca\xf3\x18\x08:\x06Taiwan\x12\x14\n\x02RU\x10\t\x1a\x0c\xca\xf3\x18\x08:\x06Russia\x12\x13\n\x02IN\x10\n\x1a\x0b\xca\xf3\x18\x07:\x05India\x12$\n\x06NZ_865\x10\x0b\x1a\x18\xca\xf3\x18\x14:\x12New Zealand 865MHz\x12\x16\n\x02TH\x10\x0c\x1a\x0e\xca\xf3\x18\n:\x08Thailand\x12\x1a\n\x07LORA_24\x10\r\x1a\r\xca\xf3\x18\t:\x07\x32.4 Ghz\x12 \n\x06UA_433\x10\x0e\x1a\x14\xca\xf3\x18\x10:\x0eUkraine 433MHz\x12\"\n\x06UA_868\x10\x0f\x1a\x16\x08\x01\xca\xf3\x18\x10:\x0eUkraine 868MHz\x12!\n\x06MY_433\x10\x10\x1a\x15\xca\xf3\x18\x11:\x0fMalaysia 433MHz\x12!\n\x06MY_919\x10\x11\x1a\x15\xca\xf3\x18\x11:\x0fMalaysia 919MHz\x12\"\n\x06SG_923\x10\x12\x1a\x16\xca\xf3\x18\x12:\x10Singapore 923MHz\x12$\n\x06PH_433\x10\x13\x1a\x18\xca\xf3\x18\x14:\x12Philippines 433MHz\x12$\n\x06PH_868\x10\x14\x1a\x18\xca\xf3\x18\x14:\x12Philippines 868MHz\x12$\n\x06PH_915\x10\x15\x1a\x18\xca\xf3\x18\x14:\x12Philippines 915MHz\x12\x31\n\x07\x41NZ_433\x10\x16\x1a$\xca\xf3\x18 :\x1e\x41ustralia / New Zealand 433MHz\x12#\n\x06KZ_433\x10\x17\x1a\x17\xca\xf3\x18\x13:\x11Kazakhstan 433MHz\x12#\n\x06KZ_863\x10\x18\x1a\x17\xca\xf3\x18\x13:\x11Kazakhstan 863MHz\x12\x1e\n\x06NP_865\x10\x19\x1a\x12\xca\xf3\x18\x0e:\x0cNepal 865MHz\x12\x1f\n\x06\x42R_902\x10\x1a\x1a\x13\xca\xf3\x18\x0f:\rBrazil 902MHz\x12,\n\x07ITU1_2M\x10\x1b\x1a\x1f\xca\xf3\x18\x1b:\x19ITU Region 1 / Amateur 2m\x12,\n\x07ITU2_2M\x10\x1c\x1a\x1f\xca\xf3\x18\x1b:\x19ITU Region 2 / Amateur 2m\x12\'\n\x06\x45U_866\x10\x1d\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 866MHz\x12\'\n\x06\x45U_874\x10\x1e\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 874MHz\x12\'\n\x06\x45U_917\x10\x1f\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 917MHz\x12\x32\n\x08\x45U_N_868\x10 \x1a$\xca\xf3\x18 :\x1e\x45uropean Union 868MHz (Narrow)\x12,\n\x07ITU3_2M\x10!\x1a\x1f\xca\xf3\x18\x1b:\x19ITU Region 3 / Amateur 2m\x12\x30\n\tITU1_70CM\x10\"\x1a!\xca\xf3\x18\x1d:\x1bITU Region 1 / Amateur 70cm\x12\x30\n\tITU2_70CM\x10#\x1a!\xca\xf3\x18\x1d:\x1bITU Region 2 / Amateur 70cm\x12\x30\n\tITU3_70CM\x10$\x1a!\xca\xf3\x18\x1d:\x1bITU Region 3 / Amateur 70cm\x12\x32\n\nITU2_125CM\x10%\x1a\"\xca\xf3\x18\x1e:\x1cITU Region 2 / Amateur 1.25m\"\xd9\x05\n\x0bModemPreset\x12\x38\n\tLONG_FAST\x10\x00\x1a)\xca\xf3\x18%:\x11Long Range - FastJ\x10longfast|default\x12(\n\tLONG_SLOW\x10\x01\x1a\x19\x08\x01\xca\xf3\x18\x13:\x11Long Range - Slow\x12\x32\n\x0eVERY_LONG_SLOW\x10\x02\x1a\x1e\x08\x01\xca\xf3\x18\x18:\x16Very Long Range - Slow\x12*\n\x0bMEDIUM_SLOW\x10\x03\x1a\x19\xca\xf3\x18\x15:\x13Medium Range - Slow\x12*\n\x0bMEDIUM_FAST\x10\x04\x1a\x19\xca\xf3\x18\x15:\x13Medium Range - Fast\x12(\n\nSHORT_SLOW\x10\x05\x1a\x18\xca\xf3\x18\x14:\x12Short Range - Slow\x12(\n\nSHORT_FAST\x10\x06\x1a\x18\xca\xf3\x18\x14:\x12Short Range - Fast\x12.\n\rLONG_MODERATE\x10\x07\x1a\x1b\xca\xf3\x18\x17:\x15Long Range - Moderate\x12*\n\x0bSHORT_TURBO\x10\x08\x1a\x19\xca\xf3\x18\x15:\x13Short Range - Turbo\x12(\n\nLONG_TURBO\x10\t\x1a\x18\xca\xf3\x18\x14:\x12Long Range - Turbo\x12 \n\tLITE_FAST\x10\n\x1a\x11\xca\xf3\x18\r:\x0bLite - Fast\x12 \n\tLITE_SLOW\x10\x0b\x1a\x11\xca\xf3\x18\r:\x0bLite - Slow\x12$\n\x0bNARROW_FAST\x10\x0c\x1a\x13\xca\xf3\x18\x0f:\rNarrow - Fast\x12$\n\x0bNARROW_SLOW\x10\r\x1a\x13\xca\xf3\x18\x0f:\rNarrow - Slow\x12 \n\tTINY_FAST\x10\x0e\x1a\x11\xca\xf3\x18\r:\x0bTiny - Fast\x12 \n\tTINY_SLOW\x10\x0f\x1a\x11\xca\xf3\x18\r:\x0bTiny - Slow\x12,\n\x0cMEDIUM_TURBO\x10\x10\x1a\x1a\xca\xf3\x18\x16:\x14Medium Range - Turbo\":\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\xb2\x03\n\x0f\x42luetoothConfig\x12H\n\x07\x65nabled\x18\x01 \x01(\x08\x42\x37\xca\xf3\x18\x33:\x11\x42luetooth EnabledB\x1e\x45nable Bluetooth on the device\x12l\n\x04mode\x18\x02 \x01(\x0e\x32..meshtastic.Config.BluetoothConfig.PairingModeB.\xca\xf3\x18*:\x0cPairing ModeB\x1a\x42luetooth pairing strategy\x12o\n\tfixed_pin\x18\x03 \x01(\rB\\\xca\xf3\x18X:\tFixed PinBKFixed PIN for Bluetooth pairing. Used when pairing mode is set to fixed PIN\"v\n\x0bPairingMode\x12 \n\nRANDOM_PIN\x10\x00\x1a\x10\xca\xf3\x18\x0c:\nRandom Pin\x12\x1e\n\tFIXED_PIN\x10\x01\x1a\x0f\xca\xf3\x18\x0b:\tFixed Pin\x12%\n\x06NO_PIN\x10\x02\x1a\x19\xca\xf3\x18\x15:\x13No PIN (Just Works)\x1a\xa2\x0b\n\x0eSecurityConfig\x12\x9c\x01\n\npublic_key\x18\x01 \x01(\x0c\x42\x87\x01\xca\xf3\x18\x82\x01:\nPublic KeyBtGenerated from your private key and sent out to other nodes on the mesh to allow them to compute a shared secret key\x12X\n\x0bprivate_key\x18\x02 \x01(\x0c\x42\x43\xca\xf3\x18?:\x0bPrivate KeyB0Used to create a shared key with a remote device\x12\x61\n\tadmin_key\x18\x03 \x03(\x0c\x42N\xca\xf3\x18J:\tAdmin KeyB=The public key authorized to send admin messages to this node\x12\x8d\x01\n\nis_managed\x18\x04 \x01(\x08\x42y\xca\xf3\x18u:\x0eManaged DeviceBcDevice is managed by a mesh administrator, the user is unable to access any of the device settings.\x12Q\n\x0eserial_enabled\x18\x05 \x01(\x08\x42\x39\xca\xf3\x18\x35:\x0eSerial ConsoleB#Serial Console over the Stream API.\x12\x95\x01\n\x15\x64\x65\x62ug_log_api_enabled\x18\x06 \x01(\x08\x42v\xca\xf3\x18r:\nDebug LogsBdOutput live debug logging over serial, view and export position-redacted device logs over Bluetooth.\x12\x1d\n\x15\x61\x64min_channel_enabled\x18\x08 \x01(\x08\x12\x65\n\x17packet_signature_policy\x18\t \x01(\x0e\x32\x37.meshtastic.Config.SecurityConfig.PacketSignaturePolicyB\x0b\xca\xf3\x18\x07R\x05\x32.8.0\"\xb2\x04\n\x15PacketSignaturePolicy\x12\xa5\x01\n\"PACKET_SIGNATURE_POLICY_COMPATIBLE\x10\x00\x1a}\xca\xf3\x18y:\x1c\x43ompatible - Accept UnsignedBYAuthenticate packets when possible, but accept unsigned traffic for maximum compatibility\x12\xac\x01\n PACKET_SIGNATURE_POLICY_BALANCED\x10\x01\x1a\x85\x01\xca\xf3\x18\x80\x01:\x1f\x42\x61lanced - Prefer AuthenticatedB]Prefer authenticated packets, but still accept unsigned traffic from nodes not known to sign.\x12\xc1\x01\n\x1ePACKET_SIGNATURE_POLICY_STRICT\x10\x02\x1a\x9c\x01\xca\xf3\x18\x97\x01:\x1fStrict - Require AuthenticationBtAccept only packets with a verified signature or successful PKI decryption. Packets from older nodes may be ignored.\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\"\xf9\x82\x01\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\x8f\x1e\n\x0c\x44\x65viceConfig\x12\x45\n\x04role\x18\x01 \x01(\x0e\x32$.meshtastic.Config.DeviceConfig.RoleB\x11\xca\xf3\x18\r:\x0b\x44\x65vice Role\x12\x1a\n\x0eserial_enabled\x18\x02 \x01(\x08\x42\x02\x18\x01\x12u\n\x0b\x62utton_gpio\x18\x04 \x01(\rB`\xca\xf3\x18\\:\x0b\x42utton GPIOBMGPIO pin for the user button, can be remapped on boards with multiple buttons\x12\x43\n\x0b\x62uzzer_gpio\x18\x05 \x01(\rB.\xca\xf3\x18*:\x0b\x42uzzer GPIOB\x1bGPIO pin for the PWM buzzer\x12\x61\n\x10rebroadcast_mode\x18\x06 \x01(\x0e\x32/.meshtastic.Config.DeviceConfig.RebroadcastModeB\x16\xca\xf3\x18\x12:\x10Rebroadcast Mode\x12\x85\x01\n\x18node_info_broadcast_secs\x18\x07 \x01(\rBc\xca\xf3\x18_*\x01s:\x1cNode Info Broadcast IntervalB<How often node information is sent. Defaults to 900 seconds.\x12\x84\x01\n\x1a\x64ouble_tap_as_button_press\x18\x08 \x01(\x08\x42`\xca\xf3\x18\\:\x14\x44ouble Tap as ButtonBDTreat double tap on supported accelerometers as a user button press.\x12\x16\n\nis_managed\x18\t \x01(\x08\x42\x02\x18\x01\x12i\n\x14\x64isable_triple_click\x18\n \x01(\x08\x42K\xca\xf3\x18G:\x14\x44isable Triple ClickB/Disables the user button triple-press shortcut.\x12@\n\x05tzdef\x18\x0b \x01(\tB1\xca\xf3\x18-:\tTime ZoneB POSIX timezone definition string\x12\xcc\x01\n\x16led_heartbeat_disabled\x18\x0c \x01(\x08\x42\xab\x01\xca\xf3\x18\xa6\x01:\rLED HeartbeatB\x94\x01\x43ontrols the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.\x12L\n\x0b\x62uzzer_mode\x18\r \x01(\x0e\x32*.meshtastic.Config.DeviceConfig.BuzzerModeB\x0b\xca\xf3\x18\x07R\x05\x32.7.0\"\xd4\x0b\n\x04Role\x12H\n\x06\x43LIENT\x10\x00\x1a<\xca\xf3\x18\x38:\x06\x43lientB.App connected or stand alone messaging device.\x12\\\n\x0b\x43LIENT_MUTE\x10\x01\x1aK\xca\xf3\x18G:\x0b\x43lient MuteB8Device that does not forward packets from other devices.\x12\xb0\x01\n\x06ROUTER\x10\x02\x1a\xa3\x01\xca\xf3\x18\x9e\x01:\x06RouterB\x93\x01Infrastructure node on a tower or mountain top only. Not to be used for roofs or mobile nodes. Needs exceptional coverage. Visible in Nodes list.\x12(\n\rROUTER_CLIENT\x10\x03\x1a\x15\x08\x01\xca\xf3\x18\x0f:\rRouter Client\x12\xb3\x01\n\x08REPEATER\x10\x04\x1a\xa4\x01\x08\x01\xca\xf3\x18\x9d\x01:\x08RepeaterB\x90\x01\x44\x65precated infrastructure role that creates gaps in the mesh rebroadcast chain. Switch this node to a Router-based role (Router or Router Late).\x12H\n\x07TRACKER\x10\x05\x1a;\xca\xf3\x18\x37:\x07TrackerB,Broadcasts GPS position packets as priority.\x12\x43\n\x06SENSOR\x10\x06\x1a\x37\xca\xf3\x18\x33:\x06SensorB)Broadcasts telemetry packets as priority.\x12X\n\x03TAK\x10\x07\x1aO\xca\xf3\x18K:\x03TAKBDOptimized for ATAK system communication, reduces routine broadcasts.\x12k\n\rCLIENT_HIDDEN\x10\x08\x1aX\xca\xf3\x18T:\rClient HiddenBCDevice that only broadcasts as needed for stealth or power savings.\x12\x89\x01\n\x0eLOST_AND_FOUND\x10\t\x1au\xca\xf3\x18q:\x0eLost and FoundB_Broadcasts location as message to default channel regularly for to assist with device recovery.\x12h\n\x0bTAK_TRACKER\x10\n\x1aW\xca\xf3\x18S:\x0bTAK TrackerBDEnables automatic TAK PLI broadcasts and reduces routine broadcasts.\x12\xbc\x01\n\x0bROUTER_LATE\x10\x0b\x1a\xaa\x01\xca\xf3\x18\xa5\x01:\x0bRouter LateB\x95\x01Infrastructure node that always rebroadcasts packets once but only after all other modes. Visible in Nodes list. Not a good choice for rooftop nodes.\x12\x85\x01\n\x0b\x43LIENT_BASE\x10\x0c\x1at\xca\xf3\x18p:\x0b\x43lient BaseBaUsed for rooftop nodes to distribute messages more widely from multiple nearby client mute nodes.\"\xc9\x08\n\x0fRebroadcastMode\x12\x8a\x01\n\x03\x41LL\x10\x00\x1a\x80\x01\xca\xf3\x18|:\x03\x41llBuRebroadcast any observed message, if it was on our private channel or from another channel with the same lora params.\x12\xe0\x01\n\x11\x41LL_SKIP_DECODING\x10\x01\x1a\xc8\x01\xca\xf3\x18\xc3\x01:\x11\x41ll Skip DecodingB\xad\x01Same as behavior as ALL but skips packet decoding and simply rebroadcasts them. Only available in Repeater role. Setting this on any other roles will result in ALL behavior.\x12\xcd\x01\n\nLOCAL_ONLY\x10\x02\x1a\xbc\x01\xca\xf3\x18\xb7\x01:\nLocal OnlyB\xa8\x01Ignores observed messages from foreign meshes that are open or those which it cannot decrypt. Only rebroadcasts message on the nodes local primary / secondary channels.\x12\xc8\x01\n\nKNOWN_ONLY\x10\x03\x1a\xb7\x01\xca\xf3\x18\xb2\x01:\nKnown OnlyB\xa3\x01Ignores observed messages from foreign meshes like Local Only, but takes it step further by also ignoring messages from nodes not already in the node\'s known list.\x12\x92\x01\n\x04NONE\x10\x04\x1a\x87\x01\xca\xf3\x18\x82\x01:\x04NoneBzOnly permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.\x12\x95\x01\n\x12\x43ORE_PORTNUMS_ONLY\x10\x05\x1a}\xca\xf3\x18y:\x12\x43ore Portnums OnlyBcOnly rebroadcasts packets from the core portnums: NodeInfo, Text, Position, Telemetry, and Routing.\"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\x96\x0f\n\x0ePositionConfig\x12y\n\x17position_broadcast_secs\x18\x01 \x01(\rBX\xca\xf3\x18T*\x01s:\x12\x42roadcast IntervalB;The longest a node will go without broadcasting a position.\x12>\n position_broadcast_smart_enabled\x18\x02 \x01(\x08\x42\x14\xca\xf3\x18\x10:\x0eSmart Position\x12\xb2\x01\n\x0e\x66ixed_position\x18\x03 \x01(\x08\x42\x99\x01\xca\xf3\x18\x94\x01:\x0e\x46ixed PositionB\x81\x01The last known latitude, longitude and altitude are broadcast over the mesh on the position interval, rather than a live GPS fix.\x12\x17\n\x0bgps_enabled\x18\x04 \x01(\x08\x42\x02\x18\x01\x12^\n\x13gps_update_interval\x18\x05 \x01(\rBA\xca\xf3\x18=*\x01s:\x0fUpdate IntervalB\'How often to try to get a GPS position.\x12\x1c\n\x10gps_attempt_time\x18\x06 \x01(\rB\x02\x18\x01\x12j\n\x0eposition_flags\x18\x07 \x01(\rBR\xca\xf3\x18N:\x0ePosition FlagsB<Optional fields to include when assembling position messages\x12>\n\x07rx_gpio\x18\x08 \x01(\rB-\xca\xf3\x18)\x08\x01:\x10GPS Receive GPIOB\x13GPIO pin for GPS RX\x12?\n\x07tx_gpio\x18\t \x01(\rB.\xca\xf3\x18*\x08\x01:\x11GPS Transmit GPIOB\x13GPIO pin for GPS TX\x12\x94\x01\n broadcast_smart_minimum_distance\x18\n \x01(\rBj\xca\xf3\x18\x66*\x01m:\x10Minimum DistanceBOThe minimum change in distance before a smart position broadcast is considered.\x12\xa0\x01\n%broadcast_smart_minimum_interval_secs\x18\x0b \x01(\rBq\xca\xf3\x18m*\x01s:\x10Minimum IntervalBVThe shortest interval between position updates once the minimum distance has been met.\x12\x41\n\x0bgps_en_gpio\x18\x0c \x01(\rB,\xca\xf3\x18(\x08\x01:\x0bGPS EN GPIOB\x17GPIO pin for GPS enable\x12K\n\x08gps_mode\x18\r \x01(\x0e\x32).meshtastic.Config.PositionConfig.GpsModeB\x0e\xca\xf3\x18\n:\x08GPS Mode\"\xdc\x04\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\x32\n\x0c\x41LTITUDE_MSL\x10\x02\x1a \xca\xf3\x18\x1c:\x1a\x41ltitude is Mean Sea Level\x12\x39\n\x12GEOIDAL_SEPARATION\x10\x04\x1a!\xca\xf3\x18\x1d:\x1b\x41ltitude Geoidal Separation\x12U\n\x03\x44OP\x10\x08\x1aL\xca\xf3\x18H:\x03\x44OPBAInclude the dilution of precision value. PDOP is used by default.\x12`\n\x05HVDOP\x10\x10\x1aU\xca\xf3\x18Q:\x0bHDOP / VDOPBBIf DOP is set, send separate HDOP and VDOP values instead of PDOP.\x12)\n\tSATINVIEW\x10 \x1a\x1a\xca\xf3\x18\x16:\x14Number of satellites\x12!\n\x06SEQ_NO\x10@\x1a\x15\xca\xf3\x18\x11:\x0fSequence number\x12\x1f\n\tTIMESTAMP\x10\x80\x01\x1a\x0f\xca\xf3\x18\x0b:\tTimestamp\x12#\n\x07HEADING\x10\x80\x02\x1a\x15\xca\xf3\x18\x11:\x0fVehicle heading\x12\x1f\n\x05SPEED\x10\x80\x04\x1a\x13\xca\xf3\x18\x0f:\rVehicle speed\"g\n\x07GpsMode\x12\x1c\n\x08\x44ISABLED\x10\x00\x1a\x0e\xca\xf3\x18\n:\x08\x44isabled\x12\x1a\n\x07\x45NABLED\x10\x01\x1a\r\xca\xf3\x18\t:\x07\x45nabled\x12\"\n\x0bNOT_PRESENT\x10\x02\x1a\x11\xca\xf3\x18\r:\x0bNot Present\x1a\xb8\x05\n\x0bPowerConfig\x12\x98\x02\n\x0fis_power_saving\x18\x01 \x01(\x08\x42\xfe\x01\xca\xf3\x18\xf9\x01:\x0cPower SavingB\xe8\x01Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don\'t use this setting if you want to use your device with the phone apps or are using a device without a user button.\x12\xa0\x01\n\x1eon_battery_shutdown_after_secs\x18\x02 \x01(\rBx\xca\xf3\x18t*\x01s:\x16Shutdown on Power LossBWHow long after external power is removed before the device powers off. Zero to disable.\x12\x33\n\x17\x61\x64\x63_multiplier_override\x18\x03 \x01(\x02\x42\x12\xca\xf3\x18\x0e:\x0c\x41\x44\x43 Override\x12>\n\x13wait_bluetooth_secs\x18\x04 \x01(\rB!\xca\xf3\x18\x1d:\x1bWait for Bluetooth Duration\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\xc3\x08\n\rNetworkConfig\x12i\n\x0cwifi_enabled\x18\x01 \x01(\x08\x42S\xca\xf3\x18O:\x0cWiFi EnabledB?Enabling WiFi will disable the bluetooth connection to the app.\x12>\n\twifi_ssid\x18\x03 \x01(\tB+\xca\xf3\x18\':\x04SSIDB\x1fWiFi network name to connect to\x12\x42\n\x08wifi_psk\x18\x04 \x01(\tB0\xca\xf3\x18,:\x08PasswordB WiFi password for authentication\x12]\n\nntp_server\x18\x05 \x01(\tBI\xca\xf3\x18\x45:\nNTP ServerB7NTP server address. Defaults to meshtastic.pool.ntp.org\x12p\n\x0b\x65th_enabled\x18\x06 \x01(\x08\x42[\xca\xf3\x18W:\x10\x45thernet EnabledBCEnabling Ethernet will disable the bluetooth connection to the app.\x12V\n\x0c\x61\x64\x64ress_mode\x18\x07 \x01(\x0e\x32,.meshtastic.Config.NetworkConfig.AddressModeB\x12\xca\xf3\x18\x0e:\x0c\x41\x64\x64ress Mode\x12@\n\x0bipv4_config\x18\x08 \x01(\x0b\x32+.meshtastic.Config.NetworkConfig.IpV4Config\x12,\n\x0ersyslog_server\x18\t \x01(\tB\x14\xca\xf3\x18\x10:\x0eRsyslog Server\x12v\n\x11\x65nabled_protocols\x18\n \x01(\rB[\xca\xf3\x18W:\x11\x45nabled ProtocolsB;Enable broadcasting packets via UDP over the local network.R\x05\x32.6.0\x12\"\n\x0cipv6_enabled\x18\x0b \x01(\x08\x42\x0c\xca\xf3\x18\x08R\x06\x32.7.16\x1ax\n\nIpV4Config\x12\x14\n\x02ip\x18\x01 \x01(\x07\x42\x08\xca\xf3\x18\x04:\x02IP\x12\x1e\n\x07gateway\x18\x02 \x01(\x07\x42\r\xca\xf3\x18\t:\x07Gateway\x12\x1c\n\x06subnet\x18\x03 \x01(\x07\x42\x0c\xca\xf3\x18\x08:\x06Subnet\x12\x16\n\x03\x64ns\x18\x04 \x01(\x07\x42\t\xca\xf3\x18\x05:\x03\x44NS\"=\n\x0b\x41\x64\x64ressMode\x12\x14\n\x04\x44HCP\x10\x00\x1a\n\xca\xf3\x18\x06:\x04\x44HCP\x12\x18\n\x06STATIC\x10\x01\x1a\x0c\xca\xf3\x18\x08:\x06Static\"U\n\rProtocolFlags\x12\x1c\n\x0cNO_BROADCAST\x10\x00\x1a\n\xca\xf3\x18\x06:\x04None\x12&\n\rUDP_BROADCAST\x10\x01\x1a\x13\xca\xf3\x18\x0f:\rUDP Broadcast\x1a\x8b\x13\n\rDisplayConfig\x12\x89\x01\n\x0escreen_on_secs\x18\x01 \x01(\rBq\xca\xf3\x18m*\x01s:\rScreen on forBYHow long the screen remains on after the user button is pressed or messages are received.\x12\x62\n\ngps_format\x18\x02 \x01(\x0e\x32>.meshtastic.Config.DisplayConfig.DeprecatedGpsCoordinateFormatB\x0e\x18\x01\xca\xf3\x18\x08Z\x06\x32.7.10\x12\x8e\x01\n\x19\x61uto_screen_carousel_secs\x18\x03 \x01(\rBk\xca\xf3\x18g*\x01s:\x11\x43\x61rousel IntervalBOAutomatically moves to the next screen page, like a carousel, on this interval.\x12\x8e\x01\n\x11\x63ompass_north_top\x18\x04 \x01(\x08\x42s\x18\x01\xca\xf3\x18m:\x12\x41lways point northBPThe compass heading on the screen outside of the circle will always point north.Z\x05\x32.7.1\x12>\n\x0b\x66lip_screen\x18\x05 \x01(\x08\x42)\xca\xf3\x18%:\x0b\x46lip ScreenB\x16\x46lip screen vertically\x12t\n\x05units\x18\x06 \x01(\x0e\x32-.meshtastic.Config.DisplayConfig.DisplayUnitsB6\xca\xf3\x18\x32:\rDisplay UnitsB!Units shown on the device screen.\x12s\n\x04oled\x18\x07 \x01(\x0e\x32).meshtastic.Config.DisplayConfig.OledTypeB:\xca\xf3\x18\x36:\tOLED TypeB)Override automatic OLED screen detection.\x12v\n\x0b\x64isplaymode\x18\x08 \x01(\x0e\x32,.meshtastic.Config.DisplayConfig.DisplayModeB3\xca\xf3\x18/:\x0c\x44isplay ModeB\x1fOverride default screen layout.\x12N\n\x0cheading_bold\x18\t \x01(\x08\x42\x38\xca\xf3\x18\x34:\x0c\x42old HeadingB$Bold the heading text on the screen.\x12z\n\x15wake_on_tap_or_motion\x18\n \x01(\x08\x42[\xca\xf3\x18W:\x1cWake Screen on tap or motionB7Requires that there be an accelerometer on your device.\x12\xb7\x01\n\x13\x63ompass_orientation\x18\x0b \x01(\x0e\x32\x33.meshtastic.Config.DisplayConfig.CompassOrientationBe\xca\xf3\x18\x61:\x13\x43ompass OrientationBJIndicates how to rotate or invert the compass output for accurate display.\x12\\\n\ruse_12h_clock\x18\x0c \x01(\x08\x42\x45\xca\xf3\x18\x41:\r12 Hour ClockB(Sets the screen clock format to 12-hour.R\x06\x32.5.22\x12(\n\x12use_long_node_name\x18\r \x01(\x08\x42\x0c\xca\xf3\x18\x08R\x06\x32.7.13\x12,\n\x16\x65nable_message_bubbles\x18\x0e \x01(\x08\x42\x0c\xca\xf3\x18\x08R\x06\x32.7.19\"+\n\x1d\x44\x65precatedGpsCoordinateFormat\x12\n\n\x06UNUSED\x10\x00\"F\n\x0c\x44isplayUnits\x12\x18\n\x06METRIC\x10\x00\x1a\x0c\xca\xf3\x18\x08:\x06Metric\x12\x1c\n\x08IMPERIAL\x10\x01\x1a\x0e\xca\xf3\x18\n:\x08Imperial\"\xf7\x01\n\x08OledType\x12)\n\tOLED_AUTO\x10\x00\x1a\x1a\xca\xf3\x18\x16:\x14\x44\x65tect Automatically\x12 \n\x0cOLED_SSD1306\x10\x01\x1a\x0e\xca\xf3\x18\n:\x08SSD 1306\x12\x1e\n\x0bOLED_SH1106\x10\x02\x1a\r\xca\xf3\x18\t:\x07SH 1106\x12\x1e\n\x0bOLED_SH1107\x10\x03\x1a\r\xca\xf3\x18\t:\x07SH 1107\x12.\n\x13OLED_SH1107_128_128\x10\x04\x1a\x15\xca\xf3\x18\x11:\x0fSH 1107 128x128\x12.\n\x13OLED_SH1107_ROTATED\x10\x05\x1a\x15\xca\xf3\x18\x11:\x0fSH 1107 Rotated\"\xd6\x01\n\x0b\x44isplayMode\x12/\n\x07\x44\x45\x46\x41ULT\x10\x00\x1a\"\xca\xf3\x18\x1e:\x1c\x44\x65\x66\x61ult 128x64 screen layout\x12\x32\n\x08TWOCOLOR\x10\x01\x1a$\xca\xf3\x18 :\x1eOptimized for 2 color displays\x12\x38\n\x08INVERTED\x10\x02\x1a*\xca\xf3\x18&:$Inverted top bar for 2 Color display\x12(\n\x05\x43OLOR\x10\x03\x1a\x1d\xca\xf3\x18\x19:\x17TFT Full Color Displays\"\xc0\x02\n\x12\x43ompassOrientation\x12\x18\n\tDEGREES_0\x10\x00\x1a\t\xca\xf3\x18\x05:\x03\x30\xc2\xb0\x12\x1a\n\nDEGREES_90\x10\x01\x1a\n\xca\xf3\x18\x06:\x04\x39\x30\xc2\xb0\x12\x1c\n\x0b\x44\x45GREES_180\x10\x02\x1a\x0b\xca\xf3\x18\x07:\x05\x31\x38\x30\xc2\xb0\x12\x1c\n\x0b\x44\x45GREES_270\x10\x03\x1a\x0b\xca\xf3\x18\x07:\x05\x32\x37\x30\xc2\xb0\x12*\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x1a\x12\xca\xf3\x18\x0e:\x0c\x30\xc2\xb0 Inverted\x12,\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x1a\x13\xca\xf3\x18\x0f:\r90\xc2\xb0 Inverted\x12.\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x1a\x14\xca\xf3\x18\x10:\x0e\x31\x38\x30\xc2\xb0 Inverted\x12.\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\x14\xca\xf3\x18\x10:\x0e\x32\x37\x30\xc2\xb0 Inverted\x1a\x83!\n\nLoRaConfig\x12\x80\x01\n\nuse_preset\x18\x01 \x01(\x08\x42l\xca\xf3\x18h:\nUse PresetBZUse the modem preset settings instead of a manual bandwidth, spread factor and coding rate\x12N\n\x0cmodem_preset\x18\x02 \x01(\x0e\x32).meshtastic.Config.LoRaConfig.ModemPresetB\r\xca\xf3\x18\t:\x07Presets\x12\'\n\tbandwidth\x18\x03 \x01(\rB\x14\xca\xf3\x18\x10*\x03kHz:\tBandwidth\x12u\n\rspread_factor\x18\x04 \x01(\rB^\xca\xf3\x18Z\x19\x00\x00\x00\x00\x00\x00\x14@!\x00\x00\x00\x00\x00\x00(@:\rSpread FactorB7Number of chirps per symbol, as 2 raised to this value.\x12\xa3\x01\n\x0b\x63oding_rate\x18\x05 \x01(\rB\x8d\x01\xca\xf3\x18\x88\x01:\x0b\x43oding RateByError-correction redundancy, as the denominator of 4/n. Higher values survive noisier links but make every packet longer.\x12\x18\n\x10\x66requency_offset\x18\x06 \x01(\x02\x12w\n\x06region\x18\x07 \x01(\x0e\x32(.meshtastic.Config.LoRaConfig.RegionCodeB=\xca\xf3\x18\x39:\x06RegionB/The region where you will be using your radios.\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\x84\x01\n\ntx_enabled\x18\t \x01(\x08\x42p\xca\xf3\x18l:\x10Transmit EnabledBXAllow the LoRa radio to transmit. Turn off while hot-swapping antennas or bench testing.\x12\xd3\x01\n\x08tx_power\x18\n \x01(\x05\x42\xc0\x01\xca\xf3\x18\xbb\x01\x19\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00>@*\x03\x64\x42m:\x0eTransmit PowerBxRadio transmit power. Leave at zero to use the highest level legal for the region, which is what most radios should use.J\x18tx|power|dbm|output|gain\x12\xe0\x01\n\x0b\x63hannel_num\x18\x0b \x01(\rB\xca\x01\xca\xf3\x18\xc5\x01:\x0e\x46requency SlotB\xb2\x01Your node\xe2\x80\x99s operating frequency is calculated based on the region, modem preset, and this field. When 0, the slot is automatically calculated based on the primary channel name.\x12\x36\n\x13override_duty_cycle\x18\x0c \x01(\x08\x42\x19\xca\xf3\x18\x15:\x13Override Duty Cycle\x12i\n\x16sx126x_rx_boosted_gain\x18\r \x01(\x08\x42I\xca\xf3\x18\x45:\x0fRX Boosted GainB2Enable RX boosted gain mode on SX126X based radios\x12\x34\n\x12override_frequency\x18\x0e \x01(\x02\x42\x18\xca\xf3\x18\x14:\x12\x46requency Override\x12.\n\x0fpa_fan_disabled\x18\x0f \x01(\x08\x42\x15\xca\xf3\x18\x11:\x0fPA Fan Disabled\x12.\n\x0fignore_incoming\x18g \x03(\rB\x15\xca\xf3\x18\x11:\x0fIgnore Incoming\x12y\n\x0bignore_mqtt\x18h \x01(\x08\x42\x64\xca\xf3\x18`:\x0bIgnore MQTTBQIgnore packets received over LoRa that travelled via MQTT anywhere on their path.\x12+\n\x11\x63onfig_ok_to_mqtt\x18i \x01(\x08\x42\x10\xca\xf3\x18\x0c:\nOk to MQTT\x12N\n\x0c\x66\x65m_lna_mode\x18j \x01(\x0e\x32*.meshtastic.Config.LoRaConfig.FEM_LNA_ModeB\x0c\xca\xf3\x18\x08R\x06\x32.7.20\x12\x17\n\x0fserial_hal_only\x18k \x01(\x08\"\x86\x0b\n\nRegionCode\x12$\n\x05UNSET\x10\x00\x1a\x19\xca\xf3\x18\x15:\x13Please set a region\x12\x1b\n\x02US\x10\x01\x1a\x13\xca\xf3\x18\x0f:\rUnited States\x12\'\n\x06\x45U_433\x10\x02\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 433MHz\x12\'\n\x06\x45U_868\x10\x03\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 868MHz\x12\x13\n\x02\x43N\x10\x04\x1a\x0b\xca\xf3\x18\x07:\x05\x43hina\x12\x13\n\x02JP\x10\x05\x1a\x0b\xca\xf3\x18\x07:\x05Japan\x12&\n\x03\x41NZ\x10\x06\x1a\x1d\xca\xf3\x18\x19:\x17\x41ustralia / New Zealand\x12\x13\n\x02KR\x10\x07\x1a\x0b\xca\xf3\x18\x07:\x05Korea\x12\x14\n\x02TW\x10\x08\x1a\x0c\xca\xf3\x18\x08:\x06Taiwan\x12\x14\n\x02RU\x10\t\x1a\x0c\xca\xf3\x18\x08:\x06Russia\x12\x13\n\x02IN\x10\n\x1a\x0b\xca\xf3\x18\x07:\x05India\x12$\n\x06NZ_865\x10\x0b\x1a\x18\xca\xf3\x18\x14:\x12New Zealand 865MHz\x12\x16\n\x02TH\x10\x0c\x1a\x0e\xca\xf3\x18\n:\x08Thailand\x12\x1a\n\x07LORA_24\x10\r\x1a\r\xca\xf3\x18\t:\x07\x32.4 Ghz\x12 \n\x06UA_433\x10\x0e\x1a\x14\xca\xf3\x18\x10:\x0eUkraine 433MHz\x12\"\n\x06UA_868\x10\x0f\x1a\x16\x08\x01\xca\xf3\x18\x10:\x0eUkraine 868MHz\x12!\n\x06MY_433\x10\x10\x1a\x15\xca\xf3\x18\x11:\x0fMalaysia 433MHz\x12!\n\x06MY_919\x10\x11\x1a\x15\xca\xf3\x18\x11:\x0fMalaysia 919MHz\x12\"\n\x06SG_923\x10\x12\x1a\x16\xca\xf3\x18\x12:\x10Singapore 923MHz\x12$\n\x06PH_433\x10\x13\x1a\x18\xca\xf3\x18\x14:\x12Philippines 433MHz\x12$\n\x06PH_868\x10\x14\x1a\x18\xca\xf3\x18\x14:\x12Philippines 868MHz\x12$\n\x06PH_915\x10\x15\x1a\x18\xca\xf3\x18\x14:\x12Philippines 915MHz\x12\x31\n\x07\x41NZ_433\x10\x16\x1a$\xca\xf3\x18 :\x1e\x41ustralia / New Zealand 433MHz\x12#\n\x06KZ_433\x10\x17\x1a\x17\xca\xf3\x18\x13:\x11Kazakhstan 433MHz\x12#\n\x06KZ_863\x10\x18\x1a\x17\xca\xf3\x18\x13:\x11Kazakhstan 863MHz\x12\x1e\n\x06NP_865\x10\x19\x1a\x12\xca\xf3\x18\x0e:\x0cNepal 865MHz\x12\x1f\n\x06\x42R_902\x10\x1a\x1a\x13\xca\xf3\x18\x0f:\rBrazil 902MHz\x12,\n\x07ITU1_2M\x10\x1b\x1a\x1f\xca\xf3\x18\x1b:\x19ITU Region 1 / Amateur 2m\x12,\n\x07ITU2_2M\x10\x1c\x1a\x1f\xca\xf3\x18\x1b:\x19ITU Region 2 / Amateur 2m\x12\'\n\x06\x45U_866\x10\x1d\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 866MHz\x12\'\n\x06\x45U_874\x10\x1e\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 874MHz\x12\'\n\x06\x45U_917\x10\x1f\x1a\x1b\xca\xf3\x18\x17:\x15\x45uropean Union 917MHz\x12\x32\n\x08\x45U_N_868\x10 \x1a$\xca\xf3\x18 :\x1e\x45uropean Union 868MHz (Narrow)\x12,\n\x07ITU3_2M\x10!\x1a\x1f\xca\xf3\x18\x1b:\x19ITU Region 3 / Amateur 2m\x12\x30\n\tITU1_70CM\x10\"\x1a!\xca\xf3\x18\x1d:\x1bITU Region 1 / Amateur 70cm\x12\x30\n\tITU2_70CM\x10#\x1a!\xca\xf3\x18\x1d:\x1bITU Region 2 / Amateur 70cm\x12\x30\n\tITU3_70CM\x10$\x1a!\xca\xf3\x18\x1d:\x1bITU Region 3 / Amateur 70cm\x12\x32\n\nITU2_125CM\x10%\x1a\"\xca\xf3\x18\x1e:\x1cITU Region 2 / Amateur 1.25m\"\xd9\x05\n\x0bModemPreset\x12\x38\n\tLONG_FAST\x10\x00\x1a)\xca\xf3\x18%:\x11Long Range - FastJ\x10longfast|default\x12(\n\tLONG_SLOW\x10\x01\x1a\x19\x08\x01\xca\xf3\x18\x13:\x11Long Range - Slow\x12\x32\n\x0eVERY_LONG_SLOW\x10\x02\x1a\x1e\x08\x01\xca\xf3\x18\x18:\x16Very Long Range - Slow\x12*\n\x0bMEDIUM_SLOW\x10\x03\x1a\x19\xca\xf3\x18\x15:\x13Medium Range - Slow\x12*\n\x0bMEDIUM_FAST\x10\x04\x1a\x19\xca\xf3\x18\x15:\x13Medium Range - Fast\x12(\n\nSHORT_SLOW\x10\x05\x1a\x18\xca\xf3\x18\x14:\x12Short Range - Slow\x12(\n\nSHORT_FAST\x10\x06\x1a\x18\xca\xf3\x18\x14:\x12Short Range - Fast\x12.\n\rLONG_MODERATE\x10\x07\x1a\x1b\xca\xf3\x18\x17:\x15Long Range - Moderate\x12*\n\x0bSHORT_TURBO\x10\x08\x1a\x19\xca\xf3\x18\x15:\x13Short Range - Turbo\x12(\n\nLONG_TURBO\x10\t\x1a\x18\xca\xf3\x18\x14:\x12Long Range - Turbo\x12 \n\tLITE_FAST\x10\n\x1a\x11\xca\xf3\x18\r:\x0bLite - Fast\x12 \n\tLITE_SLOW\x10\x0b\x1a\x11\xca\xf3\x18\r:\x0bLite - Slow\x12$\n\x0bNARROW_FAST\x10\x0c\x1a\x13\xca\xf3\x18\x0f:\rNarrow - Fast\x12$\n\x0bNARROW_SLOW\x10\r\x1a\x13\xca\xf3\x18\x0f:\rNarrow - Slow\x12 \n\tTINY_FAST\x10\x0e\x1a\x11\xca\xf3\x18\r:\x0bTiny - Fast\x12 \n\tTINY_SLOW\x10\x0f\x1a\x11\xca\xf3\x18\r:\x0bTiny - Slow\x12,\n\x0cMEDIUM_TURBO\x10\x10\x1a\x1a\xca\xf3\x18\x16:\x14Medium Range - Turbo\":\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\xb2\x03\n\x0f\x42luetoothConfig\x12H\n\x07\x65nabled\x18\x01 \x01(\x08\x42\x37\xca\xf3\x18\x33:\x11\x42luetooth EnabledB\x1e\x45nable Bluetooth on the device\x12l\n\x04mode\x18\x02 \x01(\x0e\x32..meshtastic.Config.BluetoothConfig.PairingModeB.\xca\xf3\x18*:\x0cPairing ModeB\x1a\x42luetooth pairing strategy\x12o\n\tfixed_pin\x18\x03 \x01(\rB\\\xca\xf3\x18X:\tFixed PinBKFixed PIN for Bluetooth pairing. Used when pairing mode is set to fixed PIN\"v\n\x0bPairingMode\x12 \n\nRANDOM_PIN\x10\x00\x1a\x10\xca\xf3\x18\x0c:\nRandom Pin\x12\x1e\n\tFIXED_PIN\x10\x01\x1a\x0f\xca\xf3\x18\x0b:\tFixed Pin\x12%\n\x06NO_PIN\x10\x02\x1a\x19\xca\xf3\x18\x15:\x13No PIN (Just Works)\x1a\xc2\x0b\n\x0eSecurityConfig\x12\x9c\x01\n\npublic_key\x18\x01 \x01(\x0c\x42\x87\x01\xca\xf3\x18\x82\x01:\nPublic KeyBtGenerated from your private key and sent out to other nodes on the mesh to allow them to compute a shared secret key\x12X\n\x0bprivate_key\x18\x02 \x01(\x0c\x42\x43\xca\xf3\x18?:\x0bPrivate KeyB0Used to create a shared key with a remote device\x12\x61\n\tadmin_key\x18\x03 \x03(\x0c\x42N\xca\xf3\x18J:\tAdmin KeyB=The public key authorized to send admin messages to this node\x12\x8d\x01\n\nis_managed\x18\x04 \x01(\x08\x42y\xca\xf3\x18u:\x0eManaged DeviceBcDevice is managed by a mesh administrator, the user is unable to access any of the device settings.\x12Q\n\x0eserial_enabled\x18\x05 \x01(\x08\x42\x39\xca\xf3\x18\x35:\x0eSerial ConsoleB#Serial Console over the Stream API.\x12\x95\x01\n\x15\x64\x65\x62ug_log_api_enabled\x18\x06 \x01(\x08\x42v\xca\xf3\x18r:\nDebug LogsBdOutput live debug logging over serial, view and export position-redacted device logs over Bluetooth.\x12\x1d\n\x15\x61\x64min_channel_enabled\x18\x08 \x01(\x08\x12\x65\n\x17packet_signature_policy\x18\t \x01(\x0e\x32\x37.meshtastic.Config.SecurityConfig.PacketSignaturePolicyB\x0b\xca\xf3\x18\x07R\x05\x32.8.0\"\xd2\x04\n\x15PacketSignaturePolicy\x12\xc5\x01\n\"PACKET_SIGNATURE_POLICY_COMPATIBLE\x10\x00\x1a\x9c\x01\xca\xf3\x18\x97\x01:\x1c\x43ompatible - Accept UnsignedBwAccept unsigned traffic for maximum compatibility. A signature that can be checked and is wrong still drops the packet.\x12\xac\x01\n PACKET_SIGNATURE_POLICY_BALANCED\x10\x01\x1a\x85\x01\xca\xf3\x18\x80\x01:\x1f\x42\x61lanced - Prefer AuthenticatedB]Prefer authenticated packets, but still accept unsigned traffic from nodes not known to sign.\x12\xc1\x01\n\x1ePACKET_SIGNATURE_POLICY_STRICT\x10\x02\x1a\x9c\x01\xca\xf3\x18\x97\x01:\x1fStrict - Require AuthenticationBtAccept only packets with a verified signature or successful PKI decryption. Packets from older nodes may be ignored.\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"
12
12
 
13
13
  pool = ::Google::Protobuf::DescriptorPool.generated_pool
14
14
  pool.add_serialized_file(descriptor_data)
@@ -113,14 +113,14 @@ module Meshtastic
113
113
  def get_cipher_keys(opts = {})
114
114
  psks = opts[:psks]
115
115
 
116
- psks.each_key do |key|
117
- psk = psks[key]
118
- padded_psk = psk.ljust(psk.length + ((4 - (psk.length % 4)) % 4), '=')
119
- replaced_psk = padded_psk.gsub('-', '+').gsub('_', '/')
120
- psks[key] = replaced_psk
121
- end
116
+ psks.to_h do |name, value|
117
+ entry = value.is_a?(Hash) ? value.dup : { psk: value }
118
+ psk = entry[:psk]
119
+ raise ArgumentError, 'PSK must be a Base64 string' unless psk.is_a?(String)
122
120
 
123
- psks
121
+ entry[:psk] = psk.tr('-_', '+/').ljust(psk.length + ((4 - (psk.length % 4)) % 4), '=')
122
+ [name, value.is_a?(Hash) ? entry : entry[:psk]]
123
+ end
124
124
  rescue StandardError => e
125
125
  raise e
126
126
  end
@@ -212,6 +212,8 @@ module Meshtastic
212
212
  # psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: "AQ==" })'
213
213
  # )
214
214
  def send_packet(opts = {})
215
+ raise ArgumentError, 'use_aead belongs inside each psks channel record' if opts.key?(:use_aead)
216
+
215
217
  mesh_packet = opts[:mesh_packet]
216
218
  from = opts[:from] ||= '!00000b0b'
217
219
  # from_hex = from.delete('!').bytes.map { |b| b.to_s(16).rjust(2, '0') }.join if from.is_a?(String)
@@ -245,6 +247,9 @@ module Meshtastic
245
247
  # my_info = Meshtastic::FromRadio.my_info
246
248
  # wait_connected if to != my_info.my_node_num && my_info.is_a(Meshtastic::Deviceonly::MyInfo)
247
249
 
250
+ raise ArgumentError, 'MQTT does not support recipient public_key PKI encryption' if via == :mqtt && opts[:public_key] && !opts[:pki_encrypted]
251
+ raise ArgumentError, 'MQTT direct text requires unsupported PKI encryption; use a radio transport' if via == :mqtt && to != 0xffffffff && mesh_packet.decoded&.portnum == :TEXT_MESSAGE_APP
252
+
248
253
  mesh_packet.from = from
249
254
  mesh_packet.to = to
250
255
  mesh_packet.channel = channel
@@ -258,26 +263,24 @@ module Meshtastic
258
263
  raise ArgumentError, 'recipient public_key must contain 32 bytes' unless mesh_packet.public_key.bytesize == 32
259
264
  end
260
265
 
261
- # When psks is nil/empty, leave the packet decoded so the radio (serial/TCP)
262
- # device can apply the channel PSK itself. MQTT callers must pass psks so the
263
- # ServiceEnvelope payload is pre-encrypted for the mesh.
266
+ # Select by exact name, never Hash insertion order when there are multiple keys.
267
+ selected_name = opts[:channel_id]&.to_s
264
268
  if psks && !psks.empty?
265
- nonce_packet_id = [mesh_packet.id].pack('V').ljust(8, "\x00")
266
- nonce_from_node = [from].pack('V').ljust(8, "\x00")
267
- nonce = "#{nonce_packet_id}#{nonce_from_node}"
268
-
269
- psk = psks[psks.keys.first]
270
- dec_psk = Base64.strict_decode64(psk)
271
- cipher = OpenSSL::Cipher.new('AES-128-CTR')
272
- cipher = OpenSSL::Cipher.new('AES-256-CTR') if dec_psk.length == 32
273
- cipher.encrypt
274
- cipher.key = dec_psk
275
- cipher.iv = nonce
276
-
277
- decrypted_payload = mesh_packet.decoded.to_proto
278
- encrypted_payload = cipher.update(decrypted_payload) + cipher.final
279
-
280
- mesh_packet.encrypted = encrypted_payload
269
+ raise ArgumentError, 'Ambiguous duplicate channel names in psks' unless psks.keys.map(&:to_s).uniq.size == psks.size
270
+
271
+ selected_name ||= psks.keys.first.to_s if psks.size == 1
272
+ raise ArgumentError, 'channel_id required for ambiguous PSKs' unless selected_name
273
+ raise ArgumentError, 'MQTT topic conflicts with selected channel_id' if via == :mqtt && opts[:topic] && opts[:topic].split('/')[-2] != selected_name
274
+
275
+ psk = psks[selected_name] || psks[selected_name.to_sym]
276
+ raise ArgumentError, 'No PSK for selected channel_id' unless psk
277
+
278
+ config = channel_crypto(name: selected_name, value: psk)
279
+ mesh_packet.channel = config[:hash] if via == :mqtt
280
+ mesh_packet.encrypted = crypt_channel(config: config, message: mesh_packet.to_h,
281
+ bytes: mesh_packet.decoded.to_proto, encrypt: true)
282
+ elsif via == :mqtt
283
+ raise ArgumentError, 'MQTT requires an explicit channel PSK; plaintext fallback is disabled'
281
284
  end
282
285
  # puts mesh_packet.to_h
283
286
 
@@ -290,8 +293,7 @@ module Meshtastic
290
293
  when :mqtt
291
294
  service_envelope = Meshtastic::ServiceEnvelope.new
292
295
  service_envelope.packet = mesh_packet
293
- # TODO: Add support for multiple PSKs by accepting channel_id
294
- service_envelope.channel_id = psks.keys.first
296
+ service_envelope.channel_id = selected_name
295
297
  service_envelope.gateway_id = "!#{from.to_s(16).downcase}"
296
298
  send_to_mqtt(service_envelope: service_envelope)
297
299
  else
@@ -315,6 +317,8 @@ module Meshtastic
315
317
  # psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: "AQ==" })'
316
318
  # )
317
319
  def send_data(opts = {})
320
+ raise ArgumentError, 'use_aead belongs inside each psks channel record' if opts.key?(:use_aead)
321
+
318
322
  # Send a text message to a node
319
323
  from = opts[:from] ||= '!00000b0b'
320
324
  # from_hex = from.delete('!').bytes.map { |b| b.to_s(16).rjust(2, '0') }.join if from.is_a?(String)
@@ -366,6 +370,8 @@ module Meshtastic
366
370
  hop_limit: hop_limit,
367
371
  pki_encrypted: opts.fetch(:pki_encrypted, false),
368
372
  public_key: opts[:public_key],
373
+ channel_id: opts[:channel_id],
374
+ topic: opts[:topic],
369
375
  psks: psks
370
376
  )
371
377
  rescue StandardError => e
@@ -387,6 +393,8 @@ module Meshtastic
387
393
  # psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: "AQ==" })'
388
394
  # )
389
395
  def send_text(opts = {})
396
+ raise ArgumentError, 'use_aead belongs inside each psks channel record' if opts.key?(:use_aead)
397
+
390
398
  # Send a text message to a node
391
399
  from = opts[:from] ||= '!00000b0b'
392
400
  # from_hex = from.delete('!').bytes.map { |b| b.to_s(16).rjust(2, '0') }.join if from.is_a?(String)
@@ -443,6 +451,10 @@ module Meshtastic
443
451
  hop_limit: hop_limit,
444
452
  port_num: port_num,
445
453
  on_response: on_response,
454
+ pki_encrypted: opts.fetch(:pki_encrypted, false),
455
+ public_key: opts[:public_key],
456
+ channel_id: opts[:channel_id],
457
+ topic: opts[:topic],
446
458
  psks: psks
447
459
  )
448
460
  rescue StandardError => e
@@ -544,10 +556,71 @@ module Meshtastic
544
556
 
545
557
  private :decode_application_payload
546
558
 
559
+ # Firmware v2.8.1.8e6a88d: Channels::generateHash and CryptoEngine CCM.
560
+ def channel_crypto(opts = {})
561
+ value = opts[:value]
562
+ entry = value.is_a?(Hash) ? value : { psk: value, use_aead: false }
563
+ raise ArgumentError, 'Channel records require psk and explicit use_aead only' unless entry.keys.sort == %i[psk use_aead]
564
+
565
+ aead = entry[:use_aead]
566
+ raise ArgumentError, 'use_aead must be true or false' unless [true, false].include?(aead)
567
+
568
+ encoded = entry[:psk]
569
+ raise ArgumentError, 'PSK must be a Base64 string' unless encoded.is_a?(String)
570
+
571
+ encoded = encoded.tr('-_', '+/').ljust(encoded.length + ((4 - (encoded.length % 4)) % 4), '=')
572
+ key = Base64.strict_decode64(encoded)
573
+ if key.bytesize == 1 && key.getbyte(0).positive?
574
+ index = key.getbyte(0)
575
+ key = Base64.strict_decode64('1PG7OiApB1nwvP+rz05pAQ==')
576
+ key.setbyte(15, (key.getbyte(15) + index - 1) & 0xff)
577
+ end
578
+ raise ArgumentError, 'PSK must contain 16 or 32 bytes (or a nonzero one-byte firmware alias)' unless [16, 32].include?(key.bytesize)
579
+
580
+ { key: key, aead: aead, hash: (opts[:name].to_s.bytes + key.bytes).reduce(0, :^) ^ (aead ? 0xae : 0) }
581
+ end
582
+
583
+ def crypt_channel(opts = {})
584
+ config = opts[:config]
585
+ message = opts[:message]
586
+ encrypt = opts[:encrypt]
587
+ bytes = opts[:bytes]
588
+ aead = config[:aead]
589
+ key = config[:key]
590
+ cipher = OpenSSL::Cipher.new("AES-#{key.bytesize * 8}-#{aead ? 'CCM' : 'CTR'}")
591
+ encrypt ? cipher.encrypt : cipher.decrypt
592
+ if aead
593
+ raise ArgumentError, 'Truncated AEAD ciphertext' if !encrypt && bytes.bytesize <= 12
594
+
595
+ cipher.iv_len = 13
596
+ cipher.auth_tag_len = 12
597
+ unless encrypt
598
+ cipher.auth_tag = bytes.byteslice(-12, 12)
599
+ bytes = bytes.byteslice(0, bytes.bytesize - 12)
600
+ end
601
+ end
602
+ cipher.key = key
603
+ nonce = [message.fetch(:id, 0), 0, message.fetch(:from, 0), 0].pack('V4')
604
+ cipher.iv = aead ? nonce.byteslice(0, 13) : nonce
605
+ if aead
606
+ cipher.ccm_data_len = bytes.bytesize
607
+ cipher.auth_data = [message.fetch(:from, 0), message.fetch(:to, 0)].pack('V2')
608
+ end
609
+ result = (bytes.empty? ? ''.b : cipher.update(bytes)) + cipher.final
610
+ result += cipher.auth_tag(12) if aead && encrypt
611
+ result
612
+ end
613
+
614
+ private :channel_crypto, :crypt_channel
615
+
547
616
  # Decode only the selected channel key; never reinterpret PKI as PSK crypto.
548
617
  # AES-CTR parsing is not authentication or proof of origin.
549
618
  def decrypt_packet(opts = {})
550
619
  message = opts[:message]
620
+ source = opts.fetch(:source, :radio)
621
+ reported = message.slice(:xeddsa_signed, :ack_proof_status)
622
+ message[:security_provenance] = { source: source, verification: source == :radio ? :device_reported : :untrusted, reported: reported }
623
+ reported.each_key { |key| message.delete(key) } unless source == :radio
551
624
  return message if message[:decoded] || !message.key?(:encrypted)
552
625
 
553
626
  if message[:pki_encrypted]
@@ -557,32 +630,29 @@ module Meshtastic
557
630
 
558
631
  channel = opts[:channel]
559
632
  psks = opts[:psks] || {}
633
+ raise ArgumentError, 'Ambiguous duplicate channel names in psks' unless psks.keys.map(&:to_s).uniq.size == psks.size
634
+
560
635
  psk = psks[channel] || psks[channel.to_s] || psks[channel.to_s.to_sym] unless channel.nil?
561
636
  if channel.nil?
562
637
  # Encrypted radio packets carry an eight-bit name/key XOR hash, not a slot.
563
638
  candidates = psks.select do |name, value|
564
- bytes = Base64.strict_decode64(value)
565
- [16, 32].include?(bytes.bytesize) && (name.to_s.bytes + bytes.bytes).reduce(0, :^) == message.fetch(:channel, 0)
639
+ channel_crypto(name: name, value: value)[:hash] == message.fetch(:channel, 0)
566
640
  rescue ArgumentError
567
641
  false
568
642
  end
569
- psk = candidates.values.first if candidates.size == 1
643
+ channel, psk = candidates.first if candidates.size == 1
570
644
  end
571
645
  unless psk
572
646
  message[:decryption_error] = 'No PSK for the selected channel'
573
647
  return message
574
648
  end
575
649
 
576
- key = Base64.strict_decode64(psk)
577
- raise ArgumentError, 'PSK must contain 16 or 32 bytes' unless [16, 32].include?(key.bytesize)
650
+ config = channel_crypto(name: channel, value: psk)
651
+ raise ArgumentError, 'Channel hash does not match selected key and mode' if opts[:source] == :mqtt && config[:hash] != message.fetch(:channel, 0)
578
652
 
579
- cipher = OpenSSL::Cipher.new(key.bytesize == 32 ? 'AES-256-CTR' : 'AES-128-CTR')
580
- cipher.decrypt
581
- cipher.key = key
582
- cipher.iv = [message.fetch(:id, 0), 0, message.fetch(:from, 0), 0].pack('V4')
583
- ciphertext = message[:encrypted]
584
- plaintext = (ciphertext.empty? ? ''.b : cipher.update(ciphertext)) + cipher.final
653
+ plaintext = crypt_channel(config: config, message: message, bytes: message[:encrypted], encrypt: false)
585
654
  message[:decoded] = Meshtastic::Data.decode(plaintext).to_h
655
+ message[:security_provenance][:channel_authentication] = config[:aead] ? :aead : :none
586
656
  message[:encrypted] = :decrypted
587
657
  message
588
658
  rescue ArgumentError, OpenSSL::Cipher::CipherError, Google::Protobuf::ParseError
@@ -623,7 +693,10 @@ module Meshtastic
623
693
  channel: 'optional - Channel (Default: 0)',
624
694
  want_ack: 'optional - Want Acknowledgement (Default: false)',
625
695
  hop_limit: 'optional - Hop Limit (Default: 3)',
626
- psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: \"AQ==\" })'
696
+ pki_encrypted: 'optional - radio-owned PKI; requires psks: nil and a 32-byte recipient public_key',
697
+ public_key: 'optional - raw recipient key forwarded to the radio without host encryption',
698
+ channel_id: 'optional - exact channel name; required when multiple PSKs are supplied',
699
+ psks: 'optional - names to Base64 keys or { psk: Base64, use_aead: true/false }; nil delegates to radio'
627
700
  )
628
701
 
629
702
  #{self}.send_data(
@@ -636,7 +709,10 @@ module Meshtastic
636
709
  want_ack: 'optional - Want Acknowledgement (Default: false)',
637
710
  hop_limit: 'optional - Hop Limit (Default: 3)',
638
711
  port_num: 'optional - (Default: Meshtastic::PortNum::PRIVATE_APP)',
639
- psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: \"AQ==\" })'
712
+ pki_encrypted: 'optional - radio-owned PKI; requires psks: nil and a 32-byte recipient public_key',
713
+ public_key: 'optional - raw recipient key forwarded to the radio without host encryption',
714
+ channel_id: 'optional - exact channel name; required when multiple PSKs are supplied',
715
+ psks: 'optional - names to Base64 keys or { psk: Base64, use_aead: true/false }; nil delegates to radio'
640
716
  )
641
717
 
642
718
  #{self}.send_text(
@@ -650,7 +726,10 @@ module Meshtastic
650
726
  want_response: 'optional - Want Response (Default: false)',
651
727
  hop_limit: 'optional - Hop Limit (Default: 3)',
652
728
  on_response: 'optional - Callback on Response',
653
- psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: \"AQ==\" })'
729
+ pki_encrypted: 'optional - radio-owned PKI; requires psks: nil and a 32-byte recipient public_key',
730
+ public_key: 'optional - raw recipient key forwarded to the radio without host encryption',
731
+ channel_id: 'optional - exact channel name; required when multiple PSKs are supplied',
732
+ psks: 'optional - names to Base64 keys or { psk: Base64, use_aead: true/false }; nil delegates to radio'
654
733
  )
655
734
 
656
735
  # Binary codecs: Unishox2, ATAK V1/V2, Forwarder, Reticulum and PayloadFormats.
@@ -664,7 +743,8 @@ module Meshtastic
664
743
 
665
744
  #{self}.decrypt_packet(
666
745
  message: 'required - decoded MeshPacket hash, updated in place',
667
- psks: 'required - channel names mapped to full Base64 AES keys',
746
+ psks: 'required - names to Base64 keys or { psk: Base64, use_aead: true/false }',
747
+ source: 'optional - :radio (device reports) or :mqtt (untrusted broker claims)',
668
748
  channel: 'optional - exact MQTT channel name; omitted selects unique radio channel hash'
669
749
  )
670
750