meshtastic 0.0.179 → 0.0.180

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/README.md +3 -2
  3. data/documentation/admin-channel.md +33 -0
  4. data/documentation/admin-config.md +28 -0
  5. data/documentation/admin-firmware.md +69 -0
  6. data/documentation/admin.md +2 -1
  7. data/documentation/atak.md +93 -10
  8. data/documentation/channel.md +1 -28
  9. data/documentation/config.md +1 -27
  10. data/documentation/meshtastic.md +2 -2
  11. data/lib/meshtastic/admin/channel.rb +90 -0
  12. data/lib/meshtastic/admin/config.rb +154 -0
  13. data/lib/meshtastic/admin/firmware.rb +173 -0
  14. data/lib/meshtastic/admin.rb +458 -28
  15. data/lib/meshtastic/apponly.rb +19 -7
  16. data/lib/meshtastic/atak.rb +295 -17
  17. data/lib/meshtastic/bluetooth/bluez.rb +20 -1
  18. data/lib/meshtastic/bluetooth.rb +70 -59
  19. data/lib/meshtastic/cannedmessages.rb +13 -6
  20. data/lib/meshtastic/channel.rb +3 -25
  21. data/lib/meshtastic/clientonly.rb +19 -7
  22. data/lib/meshtastic/config.rb +3 -24
  23. data/lib/meshtastic/connection_status.rb +12 -5
  24. data/lib/meshtastic/deviceonly.rb +19 -7
  25. data/lib/meshtastic/localonly.rb +19 -7
  26. data/lib/meshtastic/mesh_interface.rb +11 -0
  27. data/lib/meshtastic/module_config.rb +13 -7
  28. data/lib/meshtastic/mqtt.rb +72 -35
  29. data/lib/meshtastic/paxcount.rb +17 -6
  30. data/lib/meshtastic/portnums.rb +13 -6
  31. data/lib/meshtastic/position.rb +16 -6
  32. data/lib/meshtastic/remote_hardware.rb +27 -11
  33. data/lib/meshtastic/rtttl.rb +17 -8
  34. data/lib/meshtastic/serial.rb +81 -70
  35. data/lib/meshtastic/storeforward.rb +13 -6
  36. data/lib/meshtastic/stream_interface.rb +11 -0
  37. data/lib/meshtastic/tcp.rb +73 -22
  38. data/lib/meshtastic/telemetry.rb +15 -6
  39. data/lib/meshtastic/traceroute.rb +15 -6
  40. data/lib/meshtastic/util.rb +3 -1
  41. data/lib/meshtastic/version.rb +1 -1
  42. data/lib/meshtastic/xmodem.rb +12 -5
  43. data/lib/meshtastic.rb +22 -3
  44. data/spec/conventions_spec.rb +321 -0
  45. data/spec/lib/meshtastic/admin/channel_spec.rb +45 -0
  46. data/spec/lib/meshtastic/admin/config_spec.rb +48 -0
  47. data/spec/lib/meshtastic/admin/firmware_spec.rb +118 -0
  48. data/spec/lib/meshtastic/admin_spec.rb +40 -0
  49. data/spec/lib/meshtastic/apponly_spec.rb +1 -1
  50. data/spec/lib/meshtastic/atak_spec.rb +129 -4
  51. data/spec/lib/meshtastic/bluetooth_spec.rb +1 -1
  52. data/spec/lib/meshtastic/channel_spec.rb +2 -20
  53. data/spec/lib/meshtastic/config_spec.rb +2 -20
  54. data/spec/lib/meshtastic/connection_status_spec.rb +1 -1
  55. data/spec/lib/meshtastic/deviceonly_spec.rb +1 -1
  56. data/spec/lib/meshtastic/localonly_spec.rb +1 -1
  57. data/spec/lib/meshtastic/portnums_spec.rb +2 -2
  58. metadata +11 -1
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'meshtastic/xmodem_pb'
5
+
6
+ module Meshtastic
7
+ module Admin
8
+ module Firmware
9
+ SOH_SIZE = 128
10
+ PAD = 0x1A
11
+
12
+ public_class_method def self.sha256(opts = {})
13
+ Digest::SHA256.digest(firmware_bytes(opts.merge({})))
14
+ end
15
+
16
+ public_class_method def self.request_ota(opts = {})
17
+ hash = opts[:ota_hash] || sha256(opts)
18
+ raise ArgumentError, 'ota_hash must be 32 bytes' unless hash.to_s.bytesize == 32
19
+
20
+ event = Meshtastic::AdminMessage::OTAEvent.new(
21
+ reboot_ota_mode: opts[:mode] || :OTA_BLE,
22
+ ota_hash: hash.to_s.b
23
+ )
24
+ Admin.send(opts.merge(ota_request: event))
25
+ end
26
+
27
+ public_class_method def self.enter_dfu(opts = {})
28
+ Admin.send(opts.merge(enter_dfu_mode_request: true))
29
+ end
30
+
31
+ public_class_method def self.reboot_ota(opts = {})
32
+ Admin.send(opts.merge(reboot_ota_seconds: opts[:seconds] || 10))
33
+ end
34
+
35
+ public_class_method def self.xmodem_blocks(opts = {})
36
+ payload = opts[:bytes].to_s.b
37
+ block_size = opts[:block_size] || SOH_SIZE
38
+ raise ArgumentError, 'firmware bytes are empty' if payload.empty?
39
+
40
+ blocks = []
41
+ seq = 1
42
+ offset = 0
43
+ while offset < payload.bytesize
44
+ chunk = payload.byteslice(offset, block_size).to_s.b
45
+ chunk = chunk.ljust(block_size, PAD.chr) if chunk.bytesize < block_size
46
+ blocks << Meshtastic::XModem.new(
47
+ control: block_size > SOH_SIZE ? :STX : :SOH,
48
+ seq: seq,
49
+ crc16: crc16(data: chunk),
50
+ buffer: chunk
51
+ )
52
+ seq += 1
53
+ offset += block_size
54
+ end
55
+ blocks << Meshtastic::XModem.new(control: :EOT, seq: seq)
56
+ blocks
57
+ end
58
+
59
+ public_class_method def self.send_xmodem(opts = {})
60
+ packet = opts[:xmodem]
61
+ to_radio = Meshtastic::ToRadio.new
62
+ to_radio.xmodemPacket = packet
63
+ send_phone(opts.merge(to_radio: to_radio))
64
+ end
65
+
66
+ public_class_method def self.install(opts = {})
67
+ bytes = firmware_bytes(opts.merge({}))
68
+ request_ota(opts.merge(bytes: bytes))
69
+ return if opts[:mqtt_obj]
70
+
71
+ xmodem_blocks(bytes: bytes).each do |packet|
72
+ send_xmodem(opts.merge(xmodem: packet))
73
+ end
74
+ end
75
+
76
+ public_class_method def self.authors
77
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n "
78
+ end
79
+
80
+ public_class_method def self.help
81
+ puts "USAGE:
82
+ # SHA-256 the firmware image bytes or file.
83
+ #{self}.sha256(
84
+ firmware: 'optional - path to a firmware .bin on disk',
85
+ bytes: 'optional - raw firmware image bytes if no path is given'
86
+ )
87
+
88
+ # Send Admin ota_request with the image hash and OTA mode.
89
+ #{self}.request_ota(
90
+ serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
91
+ mqtt_obj: 'optional - MQTT client from Meshtastic::MQTT.connect',
92
+ firmware: 'optional - path to a firmware .bin on disk',
93
+ ota_hash: 'optional - 32-byte SHA-256 digest if not hashing firmware',
94
+ mode: 'optional - :OTA_BLE or :OTA_WIFI (default: :OTA_BLE)'
95
+ )
96
+
97
+ # Ask the node to enter DFU / UF2 bootloader mode.
98
+ #{self}.enter_dfu(
99
+ serial_obj: 'optional - serial handle from Meshtastic::Serial.connect'
100
+ )
101
+
102
+ # Send the legacy reboot_ota_seconds admin field.
103
+ #{self}.reboot_ota(
104
+ serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
105
+ seconds: 'optional - delay before OTA reboot in seconds (default: 10)'
106
+ )
107
+
108
+ # Split firmware bytes into XModem SOH blocks plus EOT.
109
+ #{self}.xmodem_blocks(
110
+ bytes: 'required - raw firmware image bytes to chunk',
111
+ block_size: 'optional - XModem block size in bytes (default: 128)'
112
+ )
113
+
114
+ # Write one XModem protobuf as a PhoneAPI ToRadio frame.
115
+ #{self}.send_xmodem(
116
+ xmodem: 'required - Meshtastic::XModem protobuf to write',
117
+ serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
118
+ tcp_obj: 'optional - TCP handle from Meshtastic::TCP.connect',
119
+ bluetooth_obj: 'optional - BLE handle from Meshtastic::Bluetooth.connect'
120
+ )
121
+
122
+ # Hash, send ota_request, then stream XModem on serial/TCP/BLE.
123
+ #{self}.install(
124
+ firmware: 'optional - path to a firmware .bin on disk',
125
+ bytes: 'optional - raw firmware image bytes if no path is given',
126
+ serial_obj: 'optional - serial handle from Meshtastic::Serial.connect',
127
+ tcp_obj: 'optional - TCP handle from Meshtastic::TCP.connect',
128
+ bluetooth_obj: 'optional - BLE handle from Meshtastic::Bluetooth.connect',
129
+ mqtt_obj: 'optional - MQTT client; publishes ota_request only',
130
+ mode: 'optional - :OTA_BLE or :OTA_WIFI (default: :OTA_BLE)'
131
+ )
132
+
133
+ # Print the AUTHOR(S) string for this module.
134
+ #{self}.authors
135
+ "
136
+ end
137
+
138
+ private_class_method def self.firmware_bytes(opts = {})
139
+ if opts[:bytes]
140
+ opts[:bytes].to_s.b
141
+ elsif opts[:firmware]
142
+ File.binread(opts[:firmware])
143
+ else
144
+ raise ArgumentError, 'firmware path or bytes is required'
145
+ end
146
+ end
147
+
148
+ private_class_method def self.crc16(opts = {})
149
+ crc = 0
150
+ opts[:data].to_s.b.each_byte do |byte|
151
+ crc ^= byte << 8
152
+ 8.times do
153
+ crc = crc[15] == 1 ? ((crc << 1) ^ 0x1021) : (crc << 1)
154
+ crc &= 0xffff
155
+ end
156
+ end
157
+ crc
158
+ end
159
+
160
+ private_class_method def self.send_phone(opts = {})
161
+ if opts[:serial_obj]
162
+ Serial.send_to_radio(opts)
163
+ elsif opts[:tcp_obj]
164
+ TCP.send_to_radio(opts)
165
+ elsif opts[:bluetooth_obj]
166
+ Bluetooth.send_to_radio(opts)
167
+ else
168
+ raise ArgumentError, 'serial_obj, bluetooth_obj, or tcp_obj is required for XModem'
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end