feat: multiple firmware sources + custom firmware in RNode flasher (#485)#582
feat: multiple firmware sources + custom firmware in RNode flasher (#485)#582torlando-tech merged 18 commits intomainfrom
Conversation
Greptile SummaryThis PR adds support for multiple firmware sources (Official, microReticulum, Community Edition, Custom) in the RNode flasher, implements per-source cache isolation under Key observations:
Confidence Score: 4/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([User: Flash Firmware]) --> B[Device Selection]
B --> C{Mode?}
C -- tncConfigOnly --> D[TNC Configuration Step]
C -- skipDetection --> E[Firmware Selection]
C -- normal --> F[Device Detection]
F --> E
E --> G{Source?}
G -- Official/CE/microReticulum --> H[Download from GitHub repo]
G -- Custom URL --> I[Download from URL]
G -- Custom File --> J[Read URI via ContentResolver]
H --> K[Save to firmware/source-id/ cache]
I --> K
J --> K
K --> L[Flash Firmware]
L --> M{microReticulum?}
M -- Yes --> D
M -- No --> N[Complete Step]
D --> O{Standalone?}
O -- Yes --> P[tncConfigComplete = true → navigate back]
O -- No --> N
A2([User: Configure Transport]) --> B2[RNode Wizard - Transport Mode]
B2 --> Q[Review Config Step - no interface name/airtime fields]
Q --> R[applyTransportMode - TncModeController.enableTncMode]
R --> S[AlertDialog on error / saveSuccess on OK]
A3([User: Disable Transport]) --> T[Confirmation Dialog]
T --> U[RNodeFlasher inline - TncModeController.disableTncMode]
U --> V[Result Dialog]
|
app/src/main/java/com/lxmf/messenger/viewmodel/FlasherViewModel.kt
Outdated
Show resolved
Hide resolved
reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/FirmwarePackage.kt
Show resolved
Hide resolved
Additional Comments (1)
Prompt To Fix With AIThis is a comment left during a code review.
Path: reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/FirmwareDownloader.kt
Line: 184-220
Comment:
Missing URL validation before downloading arbitrary URLs - could accept malicious schemes like `file://` or `ftp://`. Validate that the URL uses HTTP/HTTPS scheme and points to a .zip file before initiating the download.
How can I resolve this? If you propose a fix, please make it concise. |
93656d2 to
f18c8ea
Compare
app/src/main/java/com/lxmf/messenger/viewmodel/FlasherViewModel.kt
Outdated
Show resolved
Hide resolved
app/src/main/java/com/lxmf/messenger/viewmodel/FlasherViewModel.kt
Outdated
Show resolved
Hide resolved
reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/FirmwareDownloader.kt
Outdated
Show resolved
Hide resolved
2e5e32f to
59f8a98
Compare
| val selectedSource = | ||
| if (currentState.selectedFirmwareSource in available) { | ||
| currentState.selectedFirmwareSource | ||
| } else { | ||
| FirmwareSource.Official | ||
| } | ||
| currentState.copy( | ||
| availableFirmwareSources = available, | ||
| selectedFirmwareSource = selectedSource, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Bug: The fallback logic in checkAvailableSources can set selectedFirmwareSource to a source not in availableFirmwareSources, leading to a broken and unresponsive UI state.
Severity: HIGH
Suggested Fix
In the fallback logic within checkAvailableSources, instead of hardcoding the selectedSource to FirmwareSource.Official, set it to the first item in the available list (e.g., using available.firstOrNull()). This ensures the selected source is always consistent with the available options shown in the UI.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: app/src/main/java/com/lxmf/messenger/viewmodel/FlasherTncHelper.kt#L335-L346
Potential issue: In `checkAvailableSources`, if the currently selected firmware source
is no longer available, the code defaults the selection to `FirmwareSource.Official`.
However, it does not verify that `FirmwareSource.Official` is actually in the list of
`available` sources. This can create a state inconsistency where
`selectedFirmwareSource` is a value not present in `availableFirmwareSources`. As the UI
only renders options from `availableFirmwareSources`, the user will see a selected
source that has no corresponding UI element to interact with, blocking them from
changing the source or proceeding with flashing.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/FirmwareDownloader.kt
Show resolved
Hide resolved
reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/FirmwareDownloader.kt
Show resolved
Hide resolved
reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/TncModeController.kt
Show resolved
Hide resolved
reticulum/src/main/java/com/lxmf/messenger/reticulum/flasher/FirmwarePackage.kt
Show resolved
Hide resolved
…asher Implements issue #485. The RNode flasher now supports four firmware sources: - Official (markqvist/RNode_Firmware) — unchanged default behaviour - microReticulum (attermann/microReticulum_Firmware) - Community Edition (liberatedsystems/RNode_Firmware_CE) - Custom — user-supplied local .zip file or direct download URL Key changes: - FirmwareSource sealed class maps each source to its GitHub owner/repo - FirmwareDownloader.getAvailableReleases/getLatestRelease accept a source param - FirmwareRepository uses per-source subdirectories (firmware/{source.id}/) for cache isolation; clearCache(source?) clears one or all sources - RNodeFlasher.downloadAndFlash forwards source to downloader + repository - FlasherViewModel adds selectedFirmwareSource, customFirmwareUri, customFirmwareUrl state; new selectFirmwareSource/setCustomFirmwareUri/setCustomFirmwareUrl helpers; startFlashing dispatches to flashCustomFirmware for the Custom path - FirmwareSelectionStep adds FirmwareSourceCard (four FilterChips) above the frequency-band picker; CustomFirmwareCard replaces the version list when Custom is selected, showing a URL text field and a local-file picker button - RNodeFlasherScreen wires a rememberLauncherForActivityResult(GetContent) file picker and passes the new callbacks to FirmwareSelectionStep Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rd wrap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… for board support Instead of hardcoding which boards each source supports, query each source's latest GitHub release to check if it has matching firmware assets. Falls back to showing all sources if GitHub is unreachable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Some repos (microReticulum) use a short tag like "1.85" but name the release "1.85.9". Using the release name gives the correct version. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TNC configuration step for microReticulum devices that sends KISS serial commands to configure radio parameters (frequency, bandwidth, spreading factor, coding rate, TX power) after flashing. Add "Configure Transport" option on USB device action screen for standalone transport configuration without flashing. Uses tncConfigOnly mode to skip detection/firmware selection and go directly to radio parameter configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match rnodeconf's initRadio() sequence by sending CMD_RADIO_STATE (0x06) with RADIO_STATE_ON (0x01) after setting radio params and before saving config. Also send CMD_RESET after save so the device reboots into transport mode. Without the radio-on command, the config was saved but the device never activated transport mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace raw text fields with region selection chips and modem preset cards (reusing the same ModemPreset enum and FrequencyRegions from the RNode wizard). Advanced settings section is available for manual overrides. Apply button requires a region to be selected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fig flow Route "Configure Transport" through the existing RNode wizard with a transportMode flag, reusing region/modem preset/frequency slot selection instead of a separate mini-wizard. In transport mode, the review step hides interface-specific fields (name, airtime limits, interface mode, framebuffer) and the final action sends KISS TNC commands instead of saving an interface config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a 4th option to the USB device action screen that clears saved TNC radio configuration and resets the device back to normal host-controlled mode. Shows confirmation dialog before proceeding and progress/result feedback dialogs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
21 tests that mock KotlinUSBBridge, capture all serial writes, decode KISS frames, and assert the exact command order, command bytes, and payload contents for enableTncMode and disableTncMode on RNodeDetector. Verifies the sequence matches rnodeconf's --tnc implementation: enable: FREQ → BW → TXP → SF → CR → RADIO_STATE(ON) → CONF_SAVE → RESET disable: CONF_DELETE Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Class detekt violations Extract TNC mode operations from RNodeFlasher into TncModeController and TNC/source/custom-firmware logic from FlasherViewModel into FlasherTncHelper. This brings both classes under detekt's 600 LOC threshold. Also fixes ImplicitDefaultLocale and ComplexCondition detekt violations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Avoids incrementing the NoRelaxedMocks suppression count tracked by CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The release name may be descriptive (e.g. "RNode Firmware 1.78") rather than a bare version. Prefer name when it starts with a digit, otherwise fall back to tagName. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…asesUrl applyTncConfiguration() now derives FrequencyBand from tncFrequencyMhz instead of reading selectedBand, which is never set when firmware selection is skipped (TNC-only Configure Transport flow). Fixes 433 MHz devices receiving the wrong band parameter. Also adds requireNotNull guards in githubReleasesUrl() so passing FirmwareSource.Custom fails fast instead of producing a malformed URL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous check (firstOrNull()?.isDigit()) would accept "1.78 - Bug fixes" as a version, which embeds spaces and text into the firmware filename, breaking parseFirmwareFile's regex and the cache lookup. Now requires the entire name to be digits and dots only before preferring it over tagName. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
transportConfigError was set in state on failure but never rendered. Add an AlertDialog in RNodeWizardScreen (matching the saveError pattern) and a clearTransportConfigError() method in the ViewModel. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
catch(Exception) was swallowing CancellationException, breaking structured concurrency. Use separate catch blocks so cancellation propagates and the USB bridge is still disconnected on cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on regex Add CancellationException catch blocks in flashCustomFirmware() and checkAvailableSources() to preserve structured concurrency (matching TncModeController pattern). Tighten version regex from [\d.]+ to \d+(\.\d+)+ so degenerate strings like ".", "1.", or "..." fall back to tagName instead of being accepted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c3062a9 to
12f1ba8
Compare
Summary
FirmwareSourcesealed class (Official,MicroReticulum,CommunityEdition,Custom) — closes After 0.9.0 release: Add microReticulum, Community Edition and custom RNode firmware flash capabilities #485FirmwareDownloaderis now parameterised by source; no hardcodedmarkqvistrepoFirmwareRepositoryuses per-source cache subdirectories (firmware/{official,ce,microreticulum,custom}/) so downloaded firmware from different repos never conflictsRNodeFlasher.downloadAndFlashforwards the source to both the downloader and the repositoryFlasherViewModeltracks the selected source in UI state; theCustompath reads a local file URI viaContentResolveror downloads from a user-supplied URL before flashingFirmwareSourceCard(four FilterChips) sits above the frequency-band picker in the firmware selection step; selecting Custom reveals a URL text field and a "Pick .zip file" buttonTest plan
liberatedsystems/RNode_Firmware_CE; cached firmware lands infirmware/ce/attermann/microReticulum_Firmware.zipURL → tap "Start Flashing" → download progress shows and firmware is flashed./gradlew :reticulum:testDebugUnitTest(all pass)🤖 Generated with Claude Code