feat: import whole device configuration (stacked on #1940)#2
Closed
bruschill wants to merge 3 commits into
Closed
Conversation
Adds an "Import Device Configuration" action to Settings → Tools, the inverse of the Export Configuration feature (meshtastic#1940). It applies a saved binary DeviceProfile (.cfg) file to the connected node, matching the Android app and meshtastic CLI so a backup can be restored across the ecosystem. - DeviceProfileImport.swift — pure, device-free core: defensive parse (size-cap before decode), and DeviceProfileImportPlan, the has*-gated inverse of exportDeviceProfile(). Emits an ordered [ImportItem] plan (owner, radio/device, modules, personalization, fixed position, network, bluetooth, security, then Channels & LoRa last because it reboots) with per-item isSensitive/causesReboot flags and coarse ImportSection groups. Dedupes the LoRa config that export writes into both config.lora and the channel URL, preferring the URL when it's a valid replace-mode set (else the standalone LoRa config). - DeviceProfileImporter.swift — ProfileApplyGateway seam + apply engine (sequential, isConnected pre-checks, abort-on-first-failure). The production gateway forwards each item to the existing save*Config/saveUser/saveChannelSet/setFixedPosition calls; the standalone-LoRa reboot disconnect is swallowed only when the link actually drops. - ImportDeviceProfileView.swift — review sheet with per-section toggles (Security & Identity defaults OFF), live sensitive-material + reboot banners, a confirmation gate, per-item progress, and an applied/failed/skipped result summary. - Tools.swift — Import section + .fileImporter([.meshtasticDeviceProfile]) with a capped read, parse, and the review sheet. - AccessoryManager+ToRadio.swift — new setFixedPosition(_:fromUser:toUser:) overload to restore explicit coordinates from a profile (the existing overload only uses the phone's GPS). Tests: DeviceProfileImportTests (Swift Testing) — 22 tests covering parse/round-trip, coverage vs. export, apply ordering, LoRa/channel dedupe fallbacks, fixed-position and owner-identity preservation, sensitivity/reboot flags, and the engine's abort/skip/reboot semantics via a mock gateway. All pass; full app build succeeds for the iOS simulator.
5 tasks
Add a Tools section to docs/user/settings.md covering the NFC contact tag, Export Configuration, and the new Import Configuration flow (per-section review, Security defaults off, reboot/secret warnings). Regenerate the bundled HTML via scripts/build-docs.sh.
Owner
Author
|
No longer needed — the export and import work is combined in the upstream PR meshtastic#2023. |
garthvh
pushed a commit
that referenced
this pull request
Jul 15, 2026
…shtastic#2060) (meshtastic#2082) * feat(event-firmware): add off-device event-firmware metadata pipeline (meshtastic#2060) Aligns Meshtastic-Apple with the cross-platform event-firmware spec (design#120 / Meshtastic-Android #6162/#6163). A device only reports which event edition it runs via MyNodeInfo.firmwareEdition; the display data now lives off-device so a new event ships without an App Store release. Pipeline + cache (gap #1, unblocks the rest): - New EventFirmwareEntity @model caches the v2 payload (name, welcome message, dates, IANA time zone, location, accent color, icon, links, theme, firmware build) with derived helpers (hex-color parsing, links JSON round-trip, timezone-aware hasEnded lifecycle). Registered in MeshtasticSchemaV1. - MeshtasticAPI fetches https://api.meshtastic.org/resource/eventFirmware: bundled event_firmware.json seed at launch (offline-first) + a background live refresh with NO short local timeout (the endpoint is 20-60s; a short deadline would pin users to the seed). An empty/failed response is a no-op that leaves the cache intact; a successful payload upserts + prunes orphans on the main context. - FirmwareEditions gains editionKey <-> init?(editionKey:) mapping as the stable join key; lastEventFirmwareAPIUpdate tracks refreshes. Consumption: - Connect screen: EventFirmwareBadge replaces the hardcoded orange label with the fetched displayName tinted by accentColor (falling back to the enum name and .accentColor), plus the welcome message (gap #3, minimal). - Post-event nudge (gap #2): a persistent banner appears when the connected device runs an event edition whose eventEnd (in its IANA zone) has passed, linking to the firmware-update flow; clears on return to vanilla. A missing/ unparseable eventEnd never counts as ended. Tests: 16 passing (edition-key mapping, hex byte-order, links round-trip, hasEnded past/future/missing/unparseable + end-of-day zone boundary). Docs: swiftdata.md documents the new runtime-cache entity (+ regenerated bundled docs). Deferred follow-ups (tracked by meshtastic#2060): ambient theme accent wash + opt-out toggle, event info sheet (links/location/dates), theme.fonts, and the firmware{}.zipUrl "flash the event firmware" flow. * feat(event-firmware): event info sheet, ambient theme, fonts & firmware compare (meshtastic#2060) Completes the remaining event-firmware gaps on top of the metadata pipeline, so meshtastic#2060 is fully covered rather than pipeline-only. Event info surface (gap #3): - New EventFirmwareInfoView presented by tapping the (now tappable, icon- bearing) event badge: welcome message, location, dates, theme name/tagline, palette swatches, links, and the event firmware build (version + comparison to the connected device + a NavigationLink into the Firmware update flow + release notes disclosure). Ambient theme + opt-out (gap #3): - Subtle accent wash over the Connect grouped background (not a full recolor), gated on the connected event edition and the new UserDefaults.useEventTheme opt-in (default on). The "Use Event Theme" toggle lives in the info sheet and is shared via @AppStorage; opting out keeps the branding visible so it can be re-enabled. - Event icon (iconUrl) via AsyncImage with an accent-tinted sparkles fallback. v2 field consumption (gap #4): - theme.fonts: EventFirmwareFontResolver maps Google font *family names* to SwiftUI fonts only when the family is registered on-device, else falls back to the system font (families aren't bundled today, so this resolves to system until a font provider ships — the resolver is future-proof). - firmware{}: version comparison (dot-boundary prefix match so a truncated device version lines up with the full event build without a bare-substring false positive), release notes, and the update entry point. paletteColors and a locale-aware formattedDateRange (DateIntervalFormatter) helpers added. Tests: 27 passing (adds version-comparison incl. the dot-boundary regression, font-resolver availability/fallback, palette filtering, date range). Docs: docs/user/firmware.md gains an "Event Firmware" section (+ regenerated bundled docs). * fix(event-firmware): address CodeRabbit review — reactive cache + no bundle prune Two Major "quick win" findings on meshtastic#2082: - Bundled reseed no longer purges live-cached editions. importEventEditions gains a pruneMissing flag: the authoritative live-API import prunes retired editions (true), but the bundled seed is a floor and only upserts/seeds (false). Previously the bundle reseed on every launch deleted any edition cached from a prior successful API refresh but absent from the bundled JSON, so a newly-announced event cached while online vanished on the next offline relaunch — the exact poor-connectivity-at-the-venue case the cache exists for. - Connect's event branding is now reactive. Replaced the one-shot @State eventFirmware fetch (resolved only on .onAppear / firmwareEdition change) with a @query over EventFirmwareEntity plus a computed lookup. If the edition was already known before the async cache load finished, the old code returned nil and never re-fetched, so branding fell back to the enum name and the post-event nudge could silently never fire. @query re-renders as rows are populated/updated by the background refresh.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed?
Adds an Import Device Configuration action to Settings → Tools — the inverse of the Export Configuration feature (meshtastic#1940). It applies a saved binary
DeviceProfile(.cfg) file to the connected node.Meshtastic/Import/DeviceProfileImport.swift— pure, device-free core.parseDeviceProfiledefensively decodes untrusted bytes (size cap before decode).DeviceProfileImportPlan(profile:currentUser:)is thehas*-gated inverse ofexportDeviceProfile(): it emits an ordered[ImportItem]plan and is the single source of coverage truth so export/import can't silently drift. Items are grouped into 8 coarse toggle sections and carryisSensitive/causesRebootflags.Meshtastic/Import/DeviceProfileImporter.swift— aProfileApplyGatewayseam plus the apply engine (sequential,isConnectedpre-checks, abort-on-first-failure). The production gateway forwards each item to the existingsave*Config/saveUser/saveChannelSet/saveRtttlConfig/saveCannedMessageModuleMessages/setFixedPositioncalls, withfromUser == toUser ==the connected node (self-admin, no session passkey).Meshtastic/Views/Settings/ImportDeviceProfileView.swift— review sheet: per-section toggles, live sensitive-material + reboot banners, a confirmation gate, per-item progress, and an applied/failed/skipped result summary.Meshtastic/Views/Settings/Tools.swift— the Import section +.fileImporter([.meshtasticDeviceProfile])with a capped read, parse, and the review sheet.AccessoryManager+ToRadio.swift— newsetFixedPosition(_:fromUser:toUser:)overload to restore explicit coordinates from a profile (the existing overload only uses the phone's live GPS fix).Design decisions worth a look:
LoRaConfiginto bothconfig.loraand the channel URL'sChannelSet. The plan emits exactly one terminal item — the channel URL when it's a valid replace-mode set with a LoRa config, otherwise the standalone LoRa config (add-mode or LoRa-less URLs fall back so the region is still restored).node.user.toProto()and overrides only long/short name, soid/hwModel/publicKey/roleare never wiped bysetOwner.Why did it change?
The Android app and
meshtasticCLI can import a device's full configuration from a.cfg; iOS/macOS could export (meshtastic#1940) but not import. This closes the loop so a backup can be restored — or a config cloned onto a second node — across the Meshtastic ecosystem.How is this tested?
DeviceProfileImportTests(Swift Testing) — 22 tests, all passing: defensive parse + export↔import round-trip, coverage parity with export (all 8 device + 15 module configs;meshBeacon/remoteHardwareintentionally absent), apply ordering, LoRa/channel dedupe including the add-mode and LoRa-less fallbacks, fixed-position gating, owner-identity preservation at the plan level, sensitivity/reboot flags, and the engine's abort/skip/reboot semantics via a mock gateway.exportDeviceProfile()to keep import/export symmetric.Screenshots/Videos (when applicable)
Checklist
docs/user/ordocs/developer/, and updated accordingly. Added a Tools section todocs/user/settings.md(NFC, Export & Import Configuration) and regenerated the bundled HTML.Notes for reviewers
.cfgcarries the private key, admin keys, channel PSKs, and Wi-Fi/MQTT passwords, and import applies them (strictly more dangerous than export, which only reads). Handling: Security & Identity defaults OFF, a live banner itemizes exactly what a given selection overwrites, and a confirmation dialog gates apply. No secret is logged.DeviceProfileImportPlanis the inverse ofexportDeviceProfile(); a round-trip test locks the format, but a shared single source of truth between the two is the deeper follow-up (also noted on feat: export whole device configuration (#1924) meshtastic/Meshtastic-Apple#1940).🤖 Generated with Claude Code