meshtastic 0.0.182 → 0.0.184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/documentation/README.md +5 -0
- data/documentation/admin-backup.md +369 -0
- data/documentation/admin-channel.md +6 -6
- data/documentation/admin-config.md +5 -5
- data/documentation/admin-firmware-hex.md +57 -0
- data/documentation/admin-firmware-serial.md +1 -1
- data/documentation/admin-firmware-uf2.md +94 -0
- data/documentation/admin-firmware.md +52 -10
- data/documentation/admin.md +12 -2
- data/documentation/module-config.md +8 -4
- data/documentation/rtttl.md +8 -4
- data/lib/meshtastic/admin/backup.rb +560 -0
- data/lib/meshtastic/admin/channel.rb +6 -3
- data/lib/meshtastic/admin/config.rb +14 -14
- data/lib/meshtastic/admin/firmware/hex.rb +209 -0
- data/lib/meshtastic/admin/firmware/uf2.rb +166 -0
- data/lib/meshtastic/admin/firmware.rb +55 -13
- data/lib/meshtastic/admin.rb +60 -23
- data/lib/meshtastic/config_pb.rb +1 -1
- data/lib/meshtastic/mesh_pb.rb +1 -1
- data/lib/meshtastic/module_config.rb +22 -4
- data/lib/meshtastic/module_config_pb.rb +2 -1
- data/lib/meshtastic/rtttl.rb +22 -4
- data/lib/meshtastic/version.rb +1 -1
- data/spec/lib/meshtastic/admin/backup_spec.rb +704 -0
- data/spec/lib/meshtastic/admin/channel_spec.rb +23 -9
- data/spec/lib/meshtastic/admin/config_spec.rb +28 -12
- data/spec/lib/meshtastic/admin/firmware/hex_spec.rb +160 -0
- data/spec/lib/meshtastic/admin/firmware/uf2_spec.rb +234 -0
- data/spec/lib/meshtastic/admin/firmware_spec.rb +106 -3
- data/spec/lib/meshtastic/admin_spec.rb +106 -40
- data/spec/lib/meshtastic/module_config_spec.rb +47 -0
- data/spec/lib/meshtastic/rtttl_spec.rb +47 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 763ef1042cfde175868eab3896f7b53eaa75daf8984e64295b1b6db22a39a31e
|
|
4
|
+
data.tar.gz: eb3ba83188a23a19013c769fd8181962c8d8e0ca9e35c98a8c87efa600bef281
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03e7f64573c6baaa1a52624a184dd6c119f3e56fa0dd39dae82ed3540451fd835e6783fc59a6dd7cc2af2a3b0fe965c70c64a6049e0626f225762e6185141765
|
|
7
|
+
data.tar.gz: 394b62b327b1ca44b89d961f2896d9e1c1389a677353314cd7319a589e009372ab56868fb5ece662d84d6f2e0a1a3e29d9b13f622e89d9c85c417acbb7f2d19c
|
data/documentation/README.md
CHANGED
|
@@ -28,9 +28,14 @@ Do not open Serial and Bluetooth to the same radio at once. Always `disconnect`
|
|
|
28
28
|
## Feature modules
|
|
29
29
|
|
|
30
30
|
- [Meshtastic::Admin](admin.md)
|
|
31
|
+
- [Meshtastic::Admin::Backup](admin-backup.md)
|
|
31
32
|
- [Meshtastic::Admin::Channel](admin-channel.md)
|
|
32
33
|
- [Meshtastic::Admin::Config](admin-config.md)
|
|
33
34
|
- [Meshtastic::Admin::Firmware](admin-firmware.md)
|
|
35
|
+
- [UF2 mounted bootloader volumes](admin-firmware-uf2.md)
|
|
36
|
+
- [Intel HEX through an SWD programmer](admin-firmware-hex.md)
|
|
37
|
+
- [ESP serial ROM installer](admin-firmware-serial.md)
|
|
38
|
+
- [Nordic legacy BLE DFU packages](admin-firmware-nordic.md)
|
|
34
39
|
- [Meshtastic::ModuleConfig](module-config.md)
|
|
35
40
|
- [Meshtastic::Position](position.md)
|
|
36
41
|
- [Meshtastic::Telemetry](telemetry.md)
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Admin::Backup — native host-side configuration snapshots
|
|
2
|
+
|
|
3
|
+
`Meshtastic::Admin::Backup` exports a versioned JSON document (default) or binary
|
|
4
|
+
Meshtastic `DeviceProfile` (`format: :device_profile`) using fresh,
|
|
5
|
+
request-ID/source-correlated Admin requests. It imports validated protobuf
|
|
6
|
+
sections with bounded routing ACK waits. It does not invoke the firmware's
|
|
7
|
+
filesystem backup/restore commands or shell out to another client.
|
|
8
|
+
|
|
9
|
+
**Backups contain secrets by default**, including channel PSKs, Wi-Fi/MQTT
|
|
10
|
+
credentials, private security keys and UI PINs when firmware returns them in
|
|
11
|
+
selected sections. Keep the file and returned Hash private; do not print either
|
|
12
|
+
in logs. Firmware can redact secrets or return default/empty values. Such a
|
|
13
|
+
snapshot cannot recover values the device did not disclose. Importing those
|
|
14
|
+
values can clear existing settings. Review the selection before exporting and
|
|
15
|
+
before restoring, especially across devices or firmware versions.
|
|
16
|
+
|
|
17
|
+
## Export
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require 'meshtastic'
|
|
21
|
+
require 'meshtastic/admin/backup'
|
|
22
|
+
|
|
23
|
+
snapshot = Meshtastic::Admin::Backup.export(
|
|
24
|
+
transport_obj: connection,
|
|
25
|
+
path: '/private/directory/node-backup.json', # optional; MUST NOT exist
|
|
26
|
+
config_types: %i[DEVICE_CONFIG POSITION_CONFIG POWER_CONFIG DISPLAY_CONFIG LORA_CONFIG],
|
|
27
|
+
module_config_types: %i[MQTT_CONFIG TELEMETRY_CONFIG],
|
|
28
|
+
channel_indexes: [0, 1],
|
|
29
|
+
include_owner: true,
|
|
30
|
+
include_ui: false,
|
|
31
|
+
timeout: 10
|
|
32
|
+
)
|
|
33
|
+
# Safe to display status/count; NOT snapshot[:backup].
|
|
34
|
+
snapshot.slice(:status, :count)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`transport_obj:` is a connected Serial, Bluetooth or TCP handle with a receive
|
|
38
|
+
queue. **MQTT synchronous export/import is explicitly unsupported**, including
|
|
39
|
+
dry runs. `to:` optionally selects a unicast target; otherwise the connected
|
|
40
|
+
local node is used. `channel:` and `hop_limit:` are Admin delivery options.
|
|
41
|
+
Pause external queue consumers and do not interleave other Admin edits with a
|
|
42
|
+
backup/restore. The underlying Admin layer serializes individual requests,
|
|
43
|
+
not an entire multi-request snapshot or transaction.
|
|
44
|
+
|
|
45
|
+
JSON selection defaults (binary-specific differences are listed below):
|
|
46
|
+
|
|
47
|
+
- `include_owner: true`: only `long_name`, `short_name`, `is_licensed`.
|
|
48
|
+
Never clone User ID, MAC, hardware model, public key, role or messaging
|
|
49
|
+
capability metadata. Role belongs to device configuration; security keys
|
|
50
|
+
belong to explicitly selected security configuration.
|
|
51
|
+
- `config_types:` defaults to `DEVICE_CONFIG`, `POSITION_CONFIG`, `POWER_CONFIG`,
|
|
52
|
+
`NETWORK_CONFIG`, `DISPLAY_CONFIG`, `LORA_CONFIG`, `BLUETOOTH_CONFIG`,
|
|
53
|
+
`SECURITY_CONFIG`. Use a narrower list for firmware that lacks sections.
|
|
54
|
+
- `module_config_types: []`: opt in to supported module ConfigType symbols.
|
|
55
|
+
All 17 currently generated ModuleConfig sections are supported, including
|
|
56
|
+
`EXTNOTIF_CONFIG`, `STOREFORWARD_CONFIG`, `CANNEDMSG_CONFIG`,
|
|
57
|
+
`STATUSMESSAGE_CONFIG`, `TRAFFICMANAGEMENT_CONFIG`, `TAK_CONFIG`,
|
|
58
|
+
`MESHBEACON_CONFIG`. `Backup::MODULE_TYPES` lists the complete set.
|
|
59
|
+
- `channel_indexes: [0, 1, 2, 3, 4, 5, 6, 7]`: explicit zero-based slots,
|
|
60
|
+
including disabled channels. Wire getters use one-based indexes.
|
|
61
|
+
- `include_ui: false`: opt in to dedicated get/store UI operations.
|
|
62
|
+
- `timeout: 10`: positive finite seconds **per Admin request**, not a deadline
|
|
63
|
+
for the entire backup. Remote writes can first acquire an Admin session.
|
|
64
|
+
|
|
65
|
+
Unsupported selected sections fail the export; they are not silently skipped.
|
|
66
|
+
No file is created until every selected response has been collected and the
|
|
67
|
+
document validated. Empty JSON selections are allowed. A successful JSON return contains
|
|
68
|
+
`:status => :exported`, `:count`, `:backup` and `:warnings`. A read error raises.
|
|
69
|
+
Snapshots are sequential fresh reads, not an atomic device-wide snapshot.
|
|
70
|
+
|
|
71
|
+
Files are exclusively created with mode `0600`, `O_EXCL` and `O_NOFOLLOW`, then
|
|
72
|
+
flushed and fsynced. There is no overwrite flag. Existing files and final-path
|
|
73
|
+
symlinks fail. Use a trusted parent directory: parent-directory symlinks are not
|
|
74
|
+
resolved or pinned. A disk write/sync failure raises and can leave a partial
|
|
75
|
+
0600 file; it is never reported as a successful backup. File mode protection is
|
|
76
|
+
not encryption and does not protect against privileged host access.
|
|
77
|
+
|
|
78
|
+
## Validate, plan and import
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
plan = Meshtastic::Admin::Backup.import(
|
|
82
|
+
transport_obj: connection,
|
|
83
|
+
path: '/private/directory/node-backup.json',
|
|
84
|
+
dry_run: true
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
result = Meshtastic::Admin::Backup.import(
|
|
88
|
+
transport_obj: connection,
|
|
89
|
+
backup: snapshot[:backup], # use exactly one of backup: or path:
|
|
90
|
+
edit_transaction: true, # ONLY when target firmware supports it
|
|
91
|
+
verify: true,
|
|
92
|
+
timeout: 10
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`dry_run: true` performs **no radio requests or writes**, including no session
|
|
97
|
+
acquisition, edit begin, getters or readback. It validates the entire document
|
|
98
|
+
first. `dry_run`, `edit_transaction` and `verify` default to `false` and must be
|
|
99
|
+
booleans. Unknown options are rejected. Import paths must be regular files and
|
|
100
|
+
must not be final-path symlinks.
|
|
101
|
+
|
|
102
|
+
The importer rejects incompatible format/version, unknown document/record/
|
|
103
|
+
protobuf fields, unknown section selectors, duplicate section/slot records,
|
|
104
|
+
invalid channel indexes, section/slot mismatches, conflicting oneofs, malformed
|
|
105
|
+
protobuf scalar values, null values and noncanonical bytes/base64 **before any
|
|
106
|
+
radio request**. A version-1 document is a Hash with string keys:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"format": "meshtastic-admin-backup",
|
|
111
|
+
"version": 1,
|
|
112
|
+
"warning": "A human-readable limitations notice",
|
|
113
|
+
"records": [
|
|
114
|
+
{
|
|
115
|
+
"section": "config",
|
|
116
|
+
"slot": "DEVICE_CONFIG",
|
|
117
|
+
"value": { "device": { "serial_enabled": false } }
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Sections are `owner`, `config`, `module_config`, `channel`, `ui`. Owner/UI slots
|
|
124
|
+
are JSON null; config/module slots are uppercase enum names; channel slots are
|
|
125
|
+
integers. `value` is protobuf JSON with **snake_case protobuf field names**,
|
|
126
|
+
canonical base64 bytes, protobuf enum representations and emitted scalar
|
|
127
|
+
defaults. Known fields and protobuf presence/default semantics round-trip;
|
|
128
|
+
unknown binary wire fields are not archived. This is not an opaque protobuf
|
|
129
|
+
wire backup. Missing fields in a manually edited document mean protobuf
|
|
130
|
+
defaults, not a patch/merge. YAML and arbitrary object deserialization are never
|
|
131
|
+
used. Session envelope passkeys and `SESSIONKEY_CONFIG` are never exported or
|
|
132
|
+
accepted; automatic session state stays inside the existing Admin connection.
|
|
133
|
+
|
|
134
|
+
Writes preserve input order within priority groups: ordinary owner/module/UI/
|
|
135
|
+
core settings first, channels next, then LoRa, Bluetooth, network and security.
|
|
136
|
+
These latter settings can disrupt the connection. Other firmware-specific
|
|
137
|
+
settings can also disrupt it; ordering cannot guarantee connectivity.
|
|
138
|
+
|
|
139
|
+
## Binary DeviceProfile (`.cfg`) export and import
|
|
140
|
+
|
|
141
|
+
Select binary output explicitly; export accepts only `format: :json` (default)
|
|
142
|
+
or `format: :device_profile`, not `:auto`. The path extension does not choose
|
|
143
|
+
export format. JSON version-1 output and return keys are unchanged.
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
snapshot = Meshtastic::Admin::Backup.export(
|
|
147
|
+
transport_obj: connection,
|
|
148
|
+
format: :device_profile,
|
|
149
|
+
path: '/private/directory/nodeConfig.cfg', # optional; MUST NOT exist
|
|
150
|
+
config_types: %i[DEVICE_CONFIG POSITION_CONFIG LORA_CONFIG],
|
|
151
|
+
module_config_types: %i[MQTT_CONFIG TELEMETRY_CONFIG],
|
|
152
|
+
channel_indexes: [0, 1], # actual contiguous PRIMARY/SECONDARY slots only
|
|
153
|
+
include_owner: true,
|
|
154
|
+
include_ui: false,
|
|
155
|
+
include_ringtone: true,
|
|
156
|
+
include_canned_messages: true,
|
|
157
|
+
timeout: 10
|
|
158
|
+
)
|
|
159
|
+
# Safe status fields only. NEVER print backup bytes or decoded profile.
|
|
160
|
+
snapshot.slice(:status, :format, :count)
|
|
161
|
+
# snapshot[:backup] is a raw ASCII-8BIT String, not JSON/base64 or a protobuf object.
|
|
162
|
+
# Pass it directly to Backup.import(transport_obj: connection,
|
|
163
|
+
# backup: snapshot[:backup], dry_run: true).
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Binary returns `{ status: :exported, format: :device_profile, count: Integer,
|
|
167
|
+
backup: binary_string, warnings: [...] }`. `count` is the number of restorable
|
|
168
|
+
sections (owner counts once, each channel counts once, duplicate URL/config
|
|
169
|
+
LoRa counts once), not the byte count or number of protobuf fields. The `.cfg`
|
|
170
|
+
file contains exactly `snapshot[:backup]`. File protections are the same
|
|
171
|
+
exclusive/no-follow 0600 creation, flush and fsync as JSON. No file is created
|
|
172
|
+
until reads and strict binary validation succeed.
|
|
173
|
+
|
|
174
|
+
Binary selection uses the same core/module/owner/channel defaults as JSON,
|
|
175
|
+
with these format-specific rules:
|
|
176
|
+
|
|
177
|
+
- All eight core and 17 module selections are supported. Present empty nested
|
|
178
|
+
messages, byte fields, repeated values and scalar defaults round-trip. Local
|
|
179
|
+
container storage versions are not invented.
|
|
180
|
+
- Owner names and license flag are explicitly present, including empty names
|
|
181
|
+
and `false`. Optional `is_unmessagable` is copied only when present in the
|
|
182
|
+
fresh User response, including explicitly present `false`. ID, MAC, hardware,
|
|
183
|
+
public key and envelope session passkeys are not copied.
|
|
184
|
+
- `include_ui: true` is **rejected before any radio requests**: DeviceProfile
|
|
185
|
+
has no UI field. Use JSON for UI snapshots.
|
|
186
|
+
- ChannelSet URLs cannot preserve arbitrary indexes or disabled slots. Select
|
|
187
|
+
`channel_indexes: []` to omit channels, or an ordered contiguous prefix such
|
|
188
|
+
as `[0]` or `[0, 1]`. Sparse/reordered selections are rejected before requests.
|
|
189
|
+
Returned slot 0 must be PRIMARY, later selected slots SECONDARY, each with
|
|
190
|
+
settings; mismatched indexes, disabled slots and other roles fail export
|
|
191
|
+
without creating a file. No selected channel is silently skipped or moved.
|
|
192
|
+
**The default all-eight selection therefore fails if any slot is disabled**;
|
|
193
|
+
choose the actual enabled prefix or use JSON to preserve disabled/sparse slots.
|
|
194
|
+
Selected LoRa is included identically in both `config.lora` and the URL;
|
|
195
|
+
if LoRa is not selected, the URL does not invent or fetch it. Unselected
|
|
196
|
+
trailing channel slots are not disabled by a later binary import.
|
|
197
|
+
- `include_ringtone: true` and `include_canned_messages: true` request fresh
|
|
198
|
+
strings and preserve present empty strings. Both default to false and are
|
|
199
|
+
binary-only options; JSON rejects these keys rather than ignoring them.
|
|
200
|
+
- `fixed_position:` optionally accepts an explicit `Meshtastic::Position` or
|
|
201
|
+
field Hash, e.g. `{ latitude_i: 0, longitude_i: 0, altitude: 0 }`. It is
|
|
202
|
+
validated before any requests and copied with protobuf presence/defaults.
|
|
203
|
+
This is caller-supplied input, **not a fresh radio read**: no dedicated Admin
|
|
204
|
+
getter exists. It defaults to absent and is binary-only. No position is
|
|
205
|
+
inferred from cached GPS data or the position configuration.
|
|
206
|
+
- An entirely empty binary selection is rejected before requests because it
|
|
207
|
+
would not produce an importable profile. JSON still permits empty records.
|
|
208
|
+
|
|
209
|
+
The importer accepts the same binary `meshtastic.DeviceProfile` used by
|
|
210
|
+
Meshtastic clients, entirely in Ruby.
|
|
211
|
+
Use exactly one source and select `format: :device_profile` explicitly, or use
|
|
212
|
+
`format: :auto` (the default for import):
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
plan = Meshtastic::Admin::Backup.import(
|
|
216
|
+
transport_obj: connection,
|
|
217
|
+
path: '/private/directory/nodeConfig.cfg',
|
|
218
|
+
format: :device_profile,
|
|
219
|
+
dry_run: true
|
|
220
|
+
)
|
|
221
|
+
# Only section/slot identities and counts are returned; no profile contents.
|
|
222
|
+
plan.slice(:status, :planned, :plan)
|
|
223
|
+
|
|
224
|
+
# Alternative in-memory source, still offline validation with no radio requests:
|
|
225
|
+
plan = Meshtastic::Admin::Backup.import(
|
|
226
|
+
transport_obj: connection,
|
|
227
|
+
backup: binary_profile_string,
|
|
228
|
+
format: :device_profile,
|
|
229
|
+
dry_run: true
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`format:` accepts only `:auto`, `:json`, or `:device_profile`. In auto mode a
|
|
234
|
+
`.cfg` path (case-insensitive) selects binary, `.json` selects JSON, and a Hash
|
|
235
|
+
selects the existing JSON document API. Other paths and Strings are validated
|
|
236
|
+
as binary first, then as JSON when JSON-looking content fails binary validation.
|
|
237
|
+
This avoids confusing a valid binary name field beginning with a newline and
|
|
238
|
+
`{` with JSON. Explicit format overrides the extension. JSON Strings are also
|
|
239
|
+
accepted; versioned JSON Hash behavior and JSON export selections are unchanged.
|
|
240
|
+
No YAML, executable serialization, or implicit protobuf object input is used.
|
|
241
|
+
|
|
242
|
+
Presence-aware mapping:
|
|
243
|
+
|
|
244
|
+
- Present `long_name`, `short_name`, `is_licensed`, and `is_unmessagable` become
|
|
245
|
+
one `owner`/`set_owner` operation. Explicit `false` and empty names are not
|
|
246
|
+
treated as absent. User identity/MAC/hardware/public-key fields are never
|
|
247
|
+
synthesized. The Admin User schema has no presence for names/license, so
|
|
248
|
+
omitted members of a sent owner message carry protobuf defaults; this is not
|
|
249
|
+
a read/merge patch API. Firmware determines how those defaults are applied.
|
|
250
|
+
- Every present `LocalConfig` and `LocalModuleConfig` message becomes its own
|
|
251
|
+
writable Config/ModuleConfig oneof, including present empty submessages.
|
|
252
|
+
All eight core and 17 module sections are supported. Nested bytes, repeated
|
|
253
|
+
values, defaults and optional presence are retained. The local containers'
|
|
254
|
+
`version` fields are storage-schema metadata, not Admin settings; they are
|
|
255
|
+
validated but not written. A metadata-only/empty profile is rejected.
|
|
256
|
+
- `channel_url` must be an HTTPS `meshtastic.org/e/` or `/d/` URL with a valid
|
|
257
|
+
base64url ChannelSet and one through eight settings, no query or userinfo.
|
|
258
|
+
Settings map in order to primary slot 0 and secondary slots 1–7; unlisted
|
|
259
|
+
trailing slots are **not** disabled. PSK lengths and channel-name bounds are
|
|
260
|
+
checked. Embedded LoRa configuration is restored too. If both the profile and
|
|
261
|
+
URL carry LoRa, equal messages are deduplicated; conflicting messages reject
|
|
262
|
+
the entire profile rather than silently choosing one.
|
|
263
|
+
- Present `fixed_position`, `ringtone`, and `canned_messages` map respectively
|
|
264
|
+
to `set_fixed_position`, `set_ringtone_message`, and
|
|
265
|
+
`set_canned_message_module_messages`, with nil slots and matching section
|
|
266
|
+
names in the plan. Zero coordinates/altitude and present empty strings are
|
|
267
|
+
preserved. Absent fields are not turned into clears or removal operations.
|
|
268
|
+
|
|
269
|
+
The **entire** binary profile and nested ChannelSet are validated before any
|
|
270
|
+
Admin request, including session acquisition or begin-edit. A strict recursive
|
|
271
|
+
wire check rejects unknown tags (including sessionkey additions), unknown enum
|
|
272
|
+
values, duplicate singular fields/conflicting oneofs, invalid wire types,
|
|
273
|
+
truncation, oversized/noncanonical varints, invalid booleans and nonfinite
|
|
274
|
+
floats. Binary messages are limited to 1 MiB and 32 nested levels. Empty or
|
|
275
|
+
arbitrary bytes are not accepted as an empty restore. Strictness intentionally
|
|
276
|
+
rejects unsupported future schemas rather than dropping fields. This is format
|
|
277
|
+
validation, not authentication: protobuf has no file magic or signature, so a
|
|
278
|
+
valid nonempty DeviceProfile cannot be distinguished from another byte stream
|
|
279
|
+
that happens to encode the same schema. Session passkeys are never imported.
|
|
280
|
+
|
|
281
|
+
`verify: true` compares fresh owner/config/module/channel/ringtone/canned-message
|
|
282
|
+
responses. Fixed position has **no dedicated Admin getter**: its record reports
|
|
283
|
+
`readback: :unsupported`, making the overall status `:readback_incomplete` even
|
|
284
|
+
when all available comparisons match. No cached position or config flag is
|
|
285
|
+
misrepresented as fixed-position verification. All existing ACK, transaction,
|
|
286
|
+
no-replay, secret-handling and persistence limitations apply.
|
|
287
|
+
|
|
288
|
+
## Transactions and failure reporting
|
|
289
|
+
|
|
290
|
+
`edit_transaction: true` explicitly sends `begin_edit_settings`, waits for its
|
|
291
|
+
ACK, sends each section once with a correlated ACK wait (or matching timeout
|
|
292
|
+
readback as described below), then sends
|
|
293
|
+
`commit_edit_settings` and waits again. Current upstream
|
|
294
|
+
[AdminModule.cpp](https://github.com/meshtastic/firmware/blob/master/src/modules/AdminModule.cpp)
|
|
295
|
+
implements a begin flag and commit save of configuration/module/device/channel/
|
|
296
|
+
node database segments. Commit also disables Bluetooth. **This is not an atomic
|
|
297
|
+
rollback facility**: firmware may apply values before commit, UI has its own
|
|
298
|
+
storage operation, and the protocol has no supported cancel/rollback here.
|
|
299
|
+
There is no reliable capability probe, so enable this option only with firmware
|
|
300
|
+
you have verified supports it. Default `false` avoids claiming universal edit
|
|
301
|
+
transaction support.
|
|
302
|
+
|
|
303
|
+
Result fields:
|
|
304
|
+
|
|
305
|
+
- `status`: `dry_run`, `acknowledged`, `applied`, `partial_failure`,
|
|
306
|
+
`readback_matched` or `readback_incomplete`. `applied` means all sections
|
|
307
|
+
completed, but at least one required timeout readback instead of an ACK;
|
|
308
|
+
it does not claim every mutation was acknowledged or persisted.
|
|
309
|
+
- `planned`, `attempted`, `acknowledged`, `readback_confirmed`: section counts, excluding transaction
|
|
310
|
+
commands and automatic session requests. Attempted is an API attempt and
|
|
311
|
+
does not prove the write reached the wire.
|
|
312
|
+
- `plan`: ordered section/slot identities, including in dry runs; no secret values.
|
|
313
|
+
- `acknowledged` counts only actual successful setter ACKs. `readback_confirmed`
|
|
314
|
+
counts only setters whose ACK timed out but whose fresh getter matched.
|
|
315
|
+
These counts are separate, never double-counted, and retained on later failure.
|
|
316
|
+
- `records`: completed section/slot statuses (`acknowledged` or
|
|
317
|
+
`readback_confirmed`), without configuration values.
|
|
318
|
+
- `transaction`: `not_requested`, `begin_uncertain`, `open`, `commit_uncertain`
|
|
319
|
+
or `commit_acknowledged`.
|
|
320
|
+
- `failure`: on write/begin/commit failure, the operation, section/slot where
|
|
321
|
+
applicable, exception class and routing reason where available. Exception
|
|
322
|
+
contents and secrets are not included. Failed timeout recovery also includes
|
|
323
|
+
`readback: mismatch|unsupported|failed`; getter exceptions retain their class
|
|
324
|
+
and routing reason where available.
|
|
325
|
+
- `warnings`: secret/redaction/persistence limitations.
|
|
326
|
+
- `persistence_verified`: always `false`.
|
|
327
|
+
|
|
328
|
+
Only a section setter's `Timeout::Error` triggers recovery: if a supported
|
|
329
|
+
getter exists, import issues one **new**, request-ID/source-correlated Admin GET
|
|
330
|
+
with its own per-request timeout. This happens even with `verify: false`.
|
|
331
|
+
It continues only if the entire expected protobuf setting matches, using the
|
|
332
|
+
same presence-aware comparison as optional verification. Portable owner fields
|
|
333
|
+
are compared while generated owner identity/hardware metadata is excluded.
|
|
334
|
+
No cached value, unrelated response, late setter ACK, or transport submission
|
|
335
|
+
is substituted for that fresh read. The mutation is **never resent**.
|
|
336
|
+
|
|
337
|
+
Mismatch (including redacted/empty secrets in place of expected nonempty
|
|
338
|
+
secrets), absent getter, getter timeout/error, and correlated routing rejection
|
|
339
|
+
remain partial failures. Secret fields are not ignored or treated as wildcards;
|
|
340
|
+
the report never includes expected/actual values. Firmware normalization can
|
|
341
|
+
also prevent exact matching. A matching empty/default value cannot establish
|
|
342
|
+
that firmware disclosed a secret; existing backup completeness limitations
|
|
343
|
+
still apply. Fixed-position writes have no supported getter. Begin and commit
|
|
344
|
+
timeouts do not use this recovery, and routing errors do not trigger it.
|
|
345
|
+
|
|
346
|
+
After an unresolved failed write, no later sections, commit, rollback or replay are sent.
|
|
347
|
+
A transaction may remain open. A missing commit ACK is uncertain even when all
|
|
348
|
+
section ACKs arrived; the node may have committed and rebooted. Inspect the
|
|
349
|
+
node and establish a fresh connection before deciding on any manual recovery.
|
|
350
|
+
Do not automatically rerun the import.
|
|
351
|
+
|
|
352
|
+
`verify: true` performs another round of fresh correlated reads **after** all
|
|
353
|
+
sections complete and any requested commit ACK arrives. Timeout-recovery reads
|
|
354
|
+
do not replace this optional post-commit check.
|
|
355
|
+
It compares protobuf values (only portable owner fields), adds per-record
|
|
356
|
+
`readback: matched|mismatch|failed` and `readback_matched` count. Redaction,
|
|
357
|
+
normalization, link loss or reboot can prevent a match. Readback failures never
|
|
358
|
+
replay writes. Even a match proves only current reported state, not persistence
|
|
359
|
+
across reboot or successful firmware-side storage.
|
|
360
|
+
|
|
361
|
+
## Verification scope
|
|
362
|
+
|
|
363
|
+
The automated peer decodes production Serial framing/ToRadio/Admin protobufs,
|
|
364
|
+
maintains configuration state, and returns encoded correlated FromRadio Admin
|
|
365
|
+
or routing packets through the real Admin queue. Tests cover round-trip bytes
|
|
366
|
+
and defaults, dry runs, validation, secret handling, file permissions/symlinks,
|
|
367
|
+
ACK failures, timeouts, commit uncertainty, optional readback and help
|
|
368
|
+
conventions. No hardware, firmware reboot/persistence, radio-link reliability,
|
|
369
|
+
Bluetooth GATT or live TCP endpoint was exercised for this feature.
|
|
@@ -19,15 +19,15 @@ settings = Meshtastic::Admin::Channel.build_settings(
|
|
|
19
19
|
module_settings: { position_precision: 13, is_muted: false }
|
|
20
20
|
)
|
|
21
21
|
Meshtastic::Admin::Channel.set(
|
|
22
|
-
|
|
22
|
+
transport_obj: connection, index: 1, role: :SECONDARY, settings: settings
|
|
23
23
|
)
|
|
24
|
-
Meshtastic::Admin::Channel.get(
|
|
24
|
+
Meshtastic::Admin::Channel.get(transport_obj: connection, index: 1)
|
|
25
25
|
|
|
26
26
|
# Explicitly disable a slot.
|
|
27
|
-
Meshtastic::Admin::Channel.set(
|
|
27
|
+
Meshtastic::Admin::Channel.set(transport_obj: connection, index: 1, role: :DISABLED)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Admin transport/routing/authentication options pass through (`
|
|
30
|
+
Admin transport/routing/authentication options pass through (`transport_obj: connection`, `to`, `from`, `session_passkey`, etc.). Here `channel:` means a **Channel protobuf**, not the outgoing mesh transport channel selector; it is removed before delivery. Use the lower-level `Admin.set_channel(channel_settings: protobuf, channel: numeric_index, ...)` when a particular transport channel is necessary.
|
|
31
31
|
|
|
32
32
|
## Channel URLs
|
|
33
33
|
|
|
@@ -45,14 +45,14 @@ url = Meshtastic::Admin::Channel.export_url(
|
|
|
45
45
|
channel_set = Meshtastic::Admin::Channel.import_url(url: url)
|
|
46
46
|
|
|
47
47
|
# Only when replacement of slots from zero is intended:
|
|
48
|
-
Meshtastic::Admin::Channel.apply_url(
|
|
48
|
+
Meshtastic::Admin::Channel.apply_url(transport_obj: connection, url: url)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
**URLs contain channel keys.** Treat them as credentials: do not log, publish, or send them to third-party QR services. Export does not redact PSKs. A URL carries settings, not original slot indexes/roles: its first entry becomes primary when applied.
|
|
52
52
|
|
|
53
53
|
## Operational limits
|
|
54
54
|
|
|
55
|
-
Writes are submissions, not delivery/persistence acknowledgements. No live hardware was exercised.
|
|
55
|
+
Writes are submissions, not delivery/persistence acknowledgements. No live hardware was exercised. Admin's automatic remote session-key acquisition applies. No automatic write-acknowledgment collection, readback, edit transaction, rollback, or add-only merge is performed. URL application can partially succeed if transport or firmware fails mid-sequence, and changing LoRa/primary settings can disconnect remote administration. For radio use, manage edit transactions and ACK/readback through Admin as appropriate for the firmware; verify slots and LoRa configuration after writing.
|
|
56
56
|
|
|
57
57
|
## Protocol evidence
|
|
58
58
|
|
|
@@ -27,23 +27,23 @@ ModuleConfig is a different protobuf: use `Admin.get_module_config` / `Admin.set
|
|
|
27
27
|
|
|
28
28
|
## Transport and authorization
|
|
29
29
|
|
|
30
|
-
Read/write helpers pass through Admin options, including `
|
|
30
|
+
Read/write helpers pass through Admin options, including `transport_obj: connection`, `to`, `from`, numeric transport `channel`, `want_ack`, `want_response`, `hop_limit`, and `session_passkey`. Use a supported connected transport. Session authentication and remote-node routing follow [Admin](admin.md).
|
|
31
31
|
|
|
32
|
-
A return value means transport submission, **not confirmed persistence**. These methods
|
|
32
|
+
A return value means transport submission, **not confirmed persistence**. These methods inherit Admin's automatic remote session-key acquisition, but do not wait for write acknowledgments or read settings back. Obtain the prior configuration, edit it, write, and request it again to confirm; configuration writes replace a whole section rather than patching only non-default fields. Omitting a field in a Hash can reset that setting to its protobuf default. Firmware version and hardware determine which fields are applied, and writes may reboot/disconnect the node.
|
|
33
33
|
|
|
34
34
|
## Example
|
|
35
35
|
|
|
36
36
|
```ruby
|
|
37
|
-
Meshtastic::Admin::Config.get_lora(
|
|
37
|
+
Meshtastic::Admin::Config.get_lora(transport_obj: connection)
|
|
38
38
|
|
|
39
39
|
# Supply the full desired section; prefer editing the returned protobuf.
|
|
40
40
|
Meshtastic::Admin::Config.set_position(
|
|
41
|
-
|
|
41
|
+
transport_obj: connection,
|
|
42
42
|
position: Meshtastic::Config::PositionConfig.new(position_broadcast_secs: 900)
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
Meshtastic::Admin::Config.set_device_ui(
|
|
46
|
-
|
|
46
|
+
transport_obj: connection,
|
|
47
47
|
device_ui: Meshtastic::DeviceUIConfig.new
|
|
48
48
|
)
|
|
49
49
|
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Intel HEX over SWD — nRF52840 only
|
|
2
|
+
|
|
3
|
+
`Meshtastic::Admin::Firmware::Hex` validates Intel HEX in native Ruby and programs an explicitly selected **nRF52840** through a separately installed, trusted **OpenOCD 0.12+ executable**. No Python flashing tools are used. Other nRF52 chips, other MCU families, serial HEX upload, and automatic probe/board discovery are unsupported.
|
|
4
|
+
|
|
5
|
+
## Safety and prerequisites
|
|
6
|
+
|
|
7
|
+
Calling `install` is destructive: OpenOCD erases sectors containing image data, writes, verifies, and resets the target. Erasing a sector can remove existing bytes outside the image's populated addresses in that sector. Select the exact firmware for your board, and include any required bootloader/SoftDevice components. A syntactically valid HEX is not authenticated firmware and does not prove board compatibility. UICR records can change bootloader/protection configuration.
|
|
8
|
+
|
|
9
|
+
Supply absolute paths to an installed OpenOCD executable and **trusted local interface and nRF52 target configuration files**. Configurations are executable Tcl, not sandboxed data; never use untrusted downloaded configuration files. They must configure one intended SWD target and its flash banks without independently programming/erasing hardware. Supply its exact OpenOCD target name, normally `nrf52.cpu`, and explicit `expected_chip: :nrf52840`. The implementation selects that target and checks FICR PART (`0x52840`) and flash geometry (256 × 4096 bytes) before issuing the flash write command. This identifies the chip, not the commercial board or probe serial number. Configure probe selection in the trusted interface file when multiple probes exist.
|
|
10
|
+
|
|
11
|
+
A supported probe, correct board-specific SWD wiring/power, permissions, and working OpenOCD configuration are operator prerequisites. Protected/debug-locked targets fail: no automatic recovery, mass erase, protection bypass, or retry is performed. Runtime uses POSIX process groups for timeout cleanup.
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require 'meshtastic/admin/firmware/hex'
|
|
17
|
+
|
|
18
|
+
# Validation only: no subprocess or hardware access.
|
|
19
|
+
metadata = Meshtastic::Admin::Firmware::Hex.validate(
|
|
20
|
+
bytes: File.binread('/absolute/path/firmware-board.hex'),
|
|
21
|
+
expected_chip: :nrf52840
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Destructive: run only when intentionally installing onto the selected hardware.
|
|
25
|
+
result = Meshtastic::Admin::Firmware::Hex.install(
|
|
26
|
+
protocol: :swd,
|
|
27
|
+
firmware: '/absolute/path/firmware-board.hex',
|
|
28
|
+
expected_chip: :nrf52840,
|
|
29
|
+
expected_target: 'nrf52.cpu',
|
|
30
|
+
openocd: '/usr/bin/openocd',
|
|
31
|
+
interface_config: '/absolute/path/trusted-interface.cfg',
|
|
32
|
+
target_config: '/absolute/path/trusted-nrf52.cfg',
|
|
33
|
+
timeout: 120
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use exactly one of `firmware:` (regular file) or `bytes:` (String). Input is limited to 16 MiB. `timeout` is positive finite seconds, at most 3600 (default 120). Unknown installation options are rejected. `Hex.install` does not consume a `format:` option; the parent firmware dispatcher selects `format: :hex` and forwards backend options.
|
|
38
|
+
|
|
39
|
+
Validation checks every record's ASCII hexadecimal syntax, byte count and checksum; requires exactly one terminal EOF and nonempty data; rejects blank lines, trailing records, unknown types, empty data records, 64 KiB record crossings, and overlapping physical address ranges. Types 00/01/02/04 implement data, EOF, segment and linear addressing. Types 03/05 validate a single optional start address within flash; start metadata is not used to override the reset vector. Data may occupy internal flash `[0, 0x100000)` or UICR `[0x10001000, 0x10002000)` only. LF and CRLF are accepted. `validate` returns populated byte count, sorted exclusive-end address ranges and optional start address.
|
|
40
|
+
|
|
41
|
+
The installer snapshots validated bytes in a private temporary HEX file. It launches OpenOCD with separate argv entries, never a shell command; paths embedded in Tcl are escaped including braces, quotes, backslashes and substitution characters. It disables normal GDB/Telnet/Tcl listener ports before loading configuration. Its guarded script executes:
|
|
42
|
+
|
|
43
|
+
1. `init`, target selection, `reset init`, `halt`;
|
|
44
|
+
2. FICR chip/geometry checks;
|
|
45
|
+
3. `flash write_image erase <snapshot> 0 ihex`;
|
|
46
|
+
4. `verify_image <snapshot> 0 ihex`;
|
|
47
|
+
5. `reset run`, then a unique completion marker and `shutdown`.
|
|
48
|
+
|
|
49
|
+
Any script error invokes `shutdown error`. Both successful process termination and the exact post-verification/reset marker are required. Missing/nonexecutable dependencies and failed, incomplete or timed-out subprocesses raise; uncertain flash state is never automatically retried. Temporary files are removed. Returned success is `status: :verified`, `flash_verified: true`, **`reboot_verified: false`**, with image SHA-256, image byte count, chip/target and validation metadata. Reset command completion is not evidence of a healthy Meshtastic application; perform a separate fresh PhoneAPI health/version check.
|
|
50
|
+
|
|
51
|
+
## Official workflow and limits
|
|
52
|
+
|
|
53
|
+
The [official Meshtastic nRF52 SWD guide](https://meshtastic.org/docs/getting-started/flashing-firmware/nrf52/swdio/) describes an external SWD probe, OpenOCD interface configuration, `transport select swd`, `target/nrf52.cfg`, HEX firmware, and erase/program/verify/reset commands. Its recovery example uses `nrf5 mass_erase`; this narrower installer deliberately **does not mass-erase**. Recovery requiring full erase remains an explicit separate operator workflow.
|
|
54
|
+
|
|
55
|
+
[OpenOCD flash programming](https://openocd.org/doc/html/Flash-Programming.html) documents the reset-init, flash-write, verify-image and reset-run sequence. [OpenOCD general commands](https://openocd.org/doc/html/General-Commands.html) documents failure exit via `shutdown error`.
|
|
56
|
+
|
|
57
|
+
All five `.hex` files in the official `firmware-nrf52840-2.7.26.54e0d8d.zip` [release](https://github.com/meshtastic/firmware/releases/tag/v2.7.26.54e0d8d) were validated locally, including `firmware-wio-sdk-wm1110-2.7.26.54e0d8d.merged.hex` and four RAK4631 variants. This is parser compatibility evidence, not hardware installation evidence. Tests exercise actual fake executable processes and Tcl command simulation (test dependency: `tclsh`); no development test connects to or erases hardware. No claim is made that every Meshtastic board or every historical/future HEX variant is supported.
|
|
@@ -47,7 +47,7 @@ Supply exactly one of `firmware:` (path) or `bytes:` (binary String). `protocol:
|
|
|
47
47
|
is optional on the direct backend and defaults to `:esp_rom`. Unknown options
|
|
48
48
|
raise `ArgumentError` before opening the serial device.
|
|
49
49
|
|
|
50
|
-
- `port:` is a required dedicated serial device path, **not** `
|
|
50
|
+
- `port:` is a required dedicated serial device path, **not** `transport_obj:` from
|
|
51
51
|
an active Meshtastic PhoneAPI connection. Close that connection and stop its
|
|
52
52
|
reader before installing. Never flash while another process uses this port.
|
|
53
53
|
- `chip:` is required and must match both the image header and the connected ROM.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# UF2 mass-storage firmware submission
|
|
2
|
+
|
|
3
|
+
`Meshtastic::Admin::Firmware::UF2.install(opts)` validates an original UF2 image and copies it to an **explicitly selected bootloader directory**. It does not discover drives, mount filesystems, enter bootloader mode, convert `.bin` files, erase devices, or verify installed flash. Implementation and filesystem safety are currently supported on **Linux with procfs and `O_NOFOLLOW`**; other platforms fail closed rather than use an unpinned destination path.
|
|
4
|
+
|
|
5
|
+
## Prepare the device yourself
|
|
6
|
+
|
|
7
|
+
The official Meshtastic workflow is to download and extract the firmware for the **exact board**, then:
|
|
8
|
+
|
|
9
|
+
- **nRF52840:** double-click reset to enter the USB UF2 bootloader. The bootloader drive exposes `INFO_UF2.TXT`, usually alongside `CURRENT.UF2` and `INDEX.HTM`.
|
|
10
|
+
- **RP2040:** hold BOOTSEL while attaching USB. The drive exposes `INFO_UF2.TXT` and `INDEX.HTM`.
|
|
11
|
+
|
|
12
|
+
Read `INFO_UF2.TXT`, select its mounted directory yourself, and supply its exact `Board-ID`. Do not select a normal disk or copy identification files there. The marker is the UF2-standard identification mechanism, **not cryptographic device authentication or proof of an OS mount**. A directory deliberately populated with a matching marker can pass validation (including the temporary directories used by tests). Run as a normal user, and do not let untrusted users modify the selected directory.
|
|
13
|
+
|
|
14
|
+
## API
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require 'meshtastic/admin/firmware/uf2'
|
|
18
|
+
|
|
19
|
+
result = Meshtastic::Admin::Firmware::UF2.install(
|
|
20
|
+
protocol: :uf2,
|
|
21
|
+
firmware: '/absolute/path/firmware-for-your-board.uf2',
|
|
22
|
+
mount: '/media/operator/BOOTLOADER',
|
|
23
|
+
family_id: 0xada52840,
|
|
24
|
+
board_id: 'nRF52840-YourBoard-v1' # replace with the exact observed Board-ID
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
|
|
30
|
+
| Key | Contract |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `protocol` | Required, exactly `:uf2`; no extension-based dispatch. |
|
|
33
|
+
| `firmware` | Regular source file, mutually exclusive with `bytes`. Contents, not filename extension, determine validity. |
|
|
34
|
+
| `bytes` | Complete original UF2 binary String, mutually exclusive with `firmware`. Snapshotted before validation/copy. |
|
|
35
|
+
| `mount` | Required existing canonical absolute directory; no root directory, symlink components, `..`, automatic discovery or directory creation. |
|
|
36
|
+
| `family_id` | Required Integer: `0xada52840` (nRF52840) or `0xe48bff56` (RP2040). |
|
|
37
|
+
| `board_id` | Required exact case-sensitive `Board-ID` in `INFO_UF2.TXT`. nRF52840 IDs must begin with `nRF52840-` (prefix case-insensitive); RP2040 must identify as `RPI-RP2`. |
|
|
38
|
+
| `flash_size` | Required **only for RP2040**, actual installed flash capacity in bytes, positive 4096-byte multiple from 4096 through 16 MiB. Check the board documentation; the generic RP2040 marker cannot report physical capacity. Rejected for nRF52840. |
|
|
39
|
+
|
|
40
|
+
RP2040 example options: `family_id: 0xe48bff56, board_id: 'RPI-RP2', flash_size: 2 * 1024 * 1024` for a board actually fitted with 2 MiB flash.
|
|
41
|
+
|
|
42
|
+
Unknown options are rejected. Reboot and PhoneAPI verification belong to the parent `Firmware.install` orchestration, not this backend.
|
|
43
|
+
|
|
44
|
+
## Accepted format and address policy
|
|
45
|
+
|
|
46
|
+
This is a deliberately narrow installer, not a universal UF2 interpreter:
|
|
47
|
+
|
|
48
|
+
- Source limit: 32 MiB, nonempty and an exact multiple of 512 bytes.
|
|
49
|
+
- Every block must have both start magic words and the end magic word, little-endian fields, and flags **exactly `0x2000`** (family present, main flash).
|
|
50
|
+
- Every block family must match the explicitly selected supported MCU. Concatenated mixed-family UF2 files are rejected even though the general UF2 format permits them.
|
|
51
|
+
- Payloads must be 256 bytes and target addresses 256-byte aligned, the supported bootloader profile (stricter than the generic UF2 format's four-byte alignment).
|
|
52
|
+
- Every block count must equal the physical file block count. Block numbers must cover `0...count` exactly once. Duplicates (even identical), missing numbers, inconsistent totals and overlapping address ranges are rejected. Out-of-order blocks and address holes are allowed and copied unchanged.
|
|
53
|
+
- **nRF52840:** conservative application-only address window `[0x27000, 0xf4000)`, excluding MBR/SoftDevice and bootloader/UICR space. This supports the S140-v7 application layout; old S140-v6 images beginning at `0x26000`, merged SoftDevice images and custom flash layouts are deliberately unsupported. Address validation alone cannot prove application/SoftDevice compatibility. Select the board's matching release and bootloader.
|
|
54
|
+
- **RP2040:** XIP flash only, `[0x10000000, 0x10000000 + flash_size)`. RAM downloads are unsupported.
|
|
55
|
+
- Non-main-flash metadata, file containers, MD5 descriptors, extension tags, reserved flags, other MCUs (including RP2350), and separately identified bootloader-update families are rejected. Raw BIN/HEX/ZIP data is never converted or reinterpreted as UF2. This API does not implement factory erase; an erase program encoded as an otherwise ordinary application image cannot be distinguished by structural checks, so only submit trusted firmware chosen for this board.
|
|
56
|
+
|
|
57
|
+
The UF2 family identifies the MCU, **not the precise board/pinout**. INFO target checks cannot prove that a family-only image was built for that exact board; correct release selection remains the operator's responsibility. Structural validation and the returned local SHA-256 are not authenticity or device-flash verification.
|
|
58
|
+
|
|
59
|
+
## Copy and result semantics
|
|
60
|
+
|
|
61
|
+
The installer checks one case-insensitive `INFO_UF2.TXT` filename, a bounded regular nonsymlink marker, the bootloader header, an unambiguous exact Board-ID and matching MCU. It pins the selected directory with an open file descriptor and writes relative to `/proc/self/fd/<fd>` so unplugging, replacing or unmounting the selected path cannot redirect firmware into the underlying host directory.
|
|
62
|
+
|
|
63
|
+
It creates only `FIRMWARE.UF2`, using exclusive creation and no symlink following. Existing files or symlinks are never overwritten. All image and target checks occur before destination creation. It writes the original UF2 bytes without padding, address relocation or reordering, flushes, calls `fsync`, and closes the file. A successful result contains:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
{
|
|
67
|
+
status: :copied, protocol: :uf2,
|
|
68
|
+
bytes: 512, # actual source byte count, not payload count
|
|
69
|
+
sha256: '...', # digest of the submitted file
|
|
70
|
+
family_id: 0xada52840,
|
|
71
|
+
board_id: 'nRF52840-YourBoard-v1',
|
|
72
|
+
destination: '/media/operator/BOOTLOADER/FIRMWARE.UF2',
|
|
73
|
+
flash_verified: false,
|
|
74
|
+
reboot_verified: false
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`:copied` means host filesystem submission completed, **not that the bootloader accepted or flashed it**. UF2 mass storage has no universal flash-acknowledgment protocol. Readback of the created virtual file would not establish flash integrity, and the bootloader may disappear/reboot while the host closes or syncs the file. Any write, flush, sync or close error propagates; no disconnect is converted into success and no transfer is automatically retried. A partial file is left alone because deleting/retrying on a disappeared mount could act on the wrong filesystem or replay a partly flashed image. Inspect the device and re-enter its bootloader manually before deciding what to do next.
|
|
79
|
+
|
|
80
|
+
## Verification and limitations
|
|
81
|
+
|
|
82
|
+
RSpec uses actual temporary source files and bootloader directories, including symlinks/FIFOs, valid nRF52840/RP2040 images, invalid magic, truncation, flags, family/count/address errors, duplicate blocks, destination preservation and readback of the copied bytes. Mount replacement and disconnect-at-sync are injected around real filesystem operations. No USB device, physical flash, bootloader reboot, or on-device application health has been tested.
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
bundle exec rspec spec/lib/meshtastic/admin/firmware/uf2_spec.rb spec/conventions_spec.rb
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Sources
|
|
89
|
+
|
|
90
|
+
- [Microsoft UF2 specification](https://github.com/microsoft/uf2/blob/master/README.md): block layout, flags, alignment, family semantics and INFO identification.
|
|
91
|
+
- [Official UF2 family identifiers](https://github.com/microsoft/uf2/blob/master/utils/uf2families.json).
|
|
92
|
+
- [Meshtastic drag-and-drop nRF52/RP2040 workflow](https://meshtastic.org/docs/getting-started/flashing-firmware/nrf52/drag-n-drop/).
|
|
93
|
+
- [Adafruit nRF52 bootloader documentation](https://github.com/adafruit/Adafruit_nRF52_Bootloader): application start addresses, bootloader entry and separate bootloader family.
|
|
94
|
+
- [Adafruit UF2 bootloader implementation](https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/src/usb/uf2/ghostfat.c): 256-byte payload/address checks, family dispatch and INFO fields.
|