Repository navigation
feat: Web based firmware updates - #1966
Conversation
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Signed-off-by: Morton Jonuschat <mjonuschat@gmail.com>
Bundle size report (gzip)
120 chunks compared, 25 changed. Sizes are gzip, matching what nginx serves. |
Very cool project I had not heard about yet! ps. what does the name aldis stand for? |
It’s named after the Aldis lamp, the signal lamp ships use to flash messages to each other. I thought it was fitting in the whole maritime theme |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com> # Conflicts: # src/globals.ts # src/store/socket/actions.ts
…dates Signed-off-by: Pedro Lamas <pedrolamas@gmail.com> # Conflicts: # AGENTS.md # src/store/extensions/actions.ts
Drop MoonrakerAgent's dispatch and disconnectDispatch, along with extensions/initAgents: agents now react to their own connected and disconnected events (live or synthesized) in eventDispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
Drop isAgentCustomEvent: once connected and disconnected are handled, the payload already narrows to AgentCustomEvent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
The agent list stays current through notify_agent_event and the relist on every connection, so extensions/refresh is gone and the firmware refresh button is disabled while aldis is not connected. Drop registeredAgents and MoonrakerAgent, restoring findAgent's inline type with the optional klippyDispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
…dates Signed-off-by: Pedro Lamas <pedrolamas@gmail.com> # Conflicts: # src/components/widgets/console/ConsoleItem.vue
|
Hi @mjonuschat, thank you for opening this Pull Request. I have been doing some refactoring around this area, and I do intend to merge it to Fluidd "soon"! Today I actually tested it with my printer, and I'm not going to lie: I was holding my breath while I was doing this (SKR-2 connected via serial and flashed with Katapult)... but it worked perfectly!!
This is VERY impressive!!! |
- Store the agent's Aldis.Run as-is, with pending, logRunId and dismissedRunId alongside; result, message and log are getters - Reconcile status snapshots only against the same run - Refresh ignores calls while one is in flight; retries and the post-run refresh fetch status directly - Show the agent's message for relayed agent errors - Label outdated MCUs with the agent's message - Add isAvailable getter and move agent event predicates to util Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
A run marked lost on agent disconnect now takes any snapshot of the same run, so a run the agent kept going after reconnecting shows as running again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
- Return ConsoleLogEntry from formatResponse, dropping the unread FirmwareUpdateResponse type - Share one isUpdatable check between the store and FirmwareStatus - Drop api version checks made redundant by unsupportedStatus - Freeze the stored status and run, typed as Readonly, so Vue does not deep-observe them - Read statusError from state instead of a wrapper getter Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
- Mark a lost run explicitly with lostRunId instead of inferring it from a missing result; a progress event resumes the run - Refresh on Klippy state changes even while a refresh is in flight - Validate the status blocker and the run id returned by update - Label unknown transports by type and hide empty status tooltips Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
- Replace lostRunId with a runLost flag that setRunLost sets together with the lost run, so no commit order needs keeping - Build the run straight from the event in applyEvent Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
pedrolamas
left a comment
There was a problem hiding this comment.
🤖 Cursor code review — 3 findings across 2 files.
These inline comments were posted from Cursor.
A socket drop reset the firmware module while onAgentDisconnected awaited, clearing the run before it could be marked lost. The module is no longer reset on drop, as the synthesized disconnected event already clears its live state, and the run check now runs first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
There was a problem hiding this comment.
Note
Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.
🟡 Changes recommended
The change still has unresolved review findings—most notably the dialog close path bypasses the open setter so the parent never receives a close event, alongside a too-permissive status type guard and inconsistent state immutability in setRunStarted.
3 open issues
What changed in this PR
This PR adds web-based firmware updates to Fluidd by integrating with the aldis Moonraker agent, introducing a new Firmware Updates Settings UI + Vuex module, and extending the agent plumbing so additional agents can be supported via a registry entry. It also refactors the update log dialog to be reusable across software and firmware update flows.
Changes:
- Add a new
firmware/Vuex module (state/actions/getters/mutations + tested helpers) driven byserver.extensions.requestfor aldis status, runs, and live update events. - Add Firmware Updates UI in Settings (status chips/actions, notifications toggle, navigation entry) and document the third-party integration.
- Generalize agent event handling (connected/disconnected + Klippy-state fanout) and make
UpdatingDialogprops-driven for reuse.
This pull request requires changes.
Unresolved review comments:
- (Moderate)
UpdatingDialog.vue: closing via directinvokedDialogmutation bypasses theopensetter/close event; close viathis.open = falseor emitcloseinstead. - (Moderate)
firmware/helpers.ts: tightenisStatusResponseguard sohost.klippy_state/host.klippy_messageare validated as strings to prevent malformed payloads leaking into UI. - (Moderate)
firmware/mutations.ts:setRunStartedassigns a mutable run (despiteReadonlyexpectation) and does not clearrunLostfor a new run, risking incorrect reconciliation. - (Nit)
firmware/mutations.ts: freeze the run payload insetRunStartedto keep state consistency withsetRun/setStatus.
| File | Summary |
|---|---|
src/views/Settings.vue |
Renders the Firmware Updates Settings section when available. |
src/util/is-agent-event.ts |
Adds type guards for agent connected/disconnected events. |
src/typings/aldis.d.ts |
Defines typings for the aldis agent API payloads. |
src/store/types.ts |
Registers the firmware Vuex module type in root store typings. |
src/store/socket/actions.ts |
Fans out Klippy disconnect events to agent Klippy-state handlers. |
src/store/server/actions.ts |
Detects Klippy state changes and notifies agent handlers. |
src/store/index.ts |
Registers the firmware Vuex module in the store. |
src/store/firmware/types.ts |
Defines firmware module state shape and related types. |
src/store/firmware/state.ts |
Implements firmware module initial state. |
src/store/firmware/mutations.ts |
Implements firmware module state transitions (run/status tracking). |
src/store/firmware/index.ts |
Wires up the firmware Vuex module (state/getters/actions/mutations). |
src/store/firmware/helpers.ts |
Pure helpers for run reconciliation, payload guards, and error extraction. |
src/store/firmware/getters.ts |
Derived state for availability/support/busy status and UI-facing data. |
src/store/firmware/actions.ts |
Refresh/update flows, agent event handling, notifications, reconnect behavior. |
src/store/firmware/__tests__/mutations.spec.ts |
Unit tests for firmware mutations and state transitions. |
src/store/firmware/__tests__/helpers.spec.ts |
Unit tests for helper logic and payload guards. |
src/store/extensions/actions.ts |
Centralizes agent event guards and adds Klippy-state fanout to agents. |
src/store/console/types.ts |
Adds ConsoleLogEntry typing for generic log rendering. |
src/store/config/types.ts |
Adds enableFirmwareNotifications UI setting. |
src/store/config/state.ts |
Defaults firmware notifications setting to disabled. |
src/locales/en.yaml |
Adds i18n strings for firmware update UI and notifications. |
src/globals.ts |
Registers the aldis agent and adds firmware-related wait keys/hooks. |
src/components/widgets/console/ConsoleItem.vue |
Broadens console item rendering to support generic log entries. |
src/components/widgets/console/ConsoleBrowser.vue |
Broadens console browser to accept generic log entry lists. |
src/components/settings/FirmwareStatus.vue |
Adds per-MCU status chip/action UI component. |
src/components/settings/FirmwareSettings.vue |
Adds Firmware Updates Settings card UI and interactions. |
src/components/layout/AppSettingsNav.vue |
Adds Firmware Updates entry to the Settings navigation/sidebar. |
src/components/common/UpdatingDialog.vue |
Refactors UpdatingDialog to be props-driven and reusable across flows. |
src/App.vue |
Wires two UpdatingDialog instances (software + firmware) and close handling. |
src/api/socketActions.ts |
Adds aldisStatus / aldisUpdate wrappers via server.extensions.request. |
docs/docs/features/third-party-integrations.md |
Documents the aldis integration and where the UI appears. |
AGENTS.md |
Updates agent plumbing documentation to include Klippy-state dispatch behavior. |
Review effort: Balanced
- UpdatingDialog closes through its open setter, so the parent's close handler runs when an update ends with nothing to show - isStatusResponse checks the host fields - setRunStarted freezes the run, like setRun Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
There was a problem hiding this comment.
Note
Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.
🟢 Approval recommended
The change set appears broadly complete and well-scoped—adding the new firmware update UI, Vuex module wiring, and supporting tests/documentation with no explicitly recorded unresolved blockers in the provided context.
No open issues 🎉
3 resolved since last review
Review effort: Balanced



Summary
Adds a Firmware Updates card to Settings, driven by the aldis Moonraker agent. aldis finds every MCU Klipper is configured with, compares each one's firmware to its target (the running host, or for FOCI, Beacon and Cartographer MCUs, their release or image), and builds and flashes the outdated ones. The agent exposes a small API over
server.extensions.request.What Fluidd gains:
server.extensions.listand keeps them current with Moonraker'sconnectedanddisconnectedevents. A registry inGlobalsroutesnotify_agent_eventby agent name and fans out Klippy state changes. Adding another agent takes one registry row.UpdatingDialog, shared by Software Updates and Firmware Updates. Software Updates changes in one way. A run that finishes with zero response lines now closes the dialog instead of leaving an empty one open. Nothing else changes.firmwarestore module and the card. The module keeps the agent's run as the agent reports it, adds liveupdate_responseevents to it, and reconcilesstatussnapshots of the same run, so a reloaded page or a reconnecting agent picks up a run in progress. Its run and payload-guard logic lives in pure, tested helpers. Buttons follow the agent'sactions, and a rejected update shows the agent's own error message. MCUs the agent cannot flash, and states Fluidd doesn't know, show a chip with no action. An unsupportedapi_versiondisables updates and shows an alert explaining why.The agent ships in aldis v0.3.0.
aldis setup --agentinstalls the service, and the API isapi_version1. The card renders only when the agent is connected oraldisis listed in Moonraker's available services, so users without aldis see no change.Test plan
pnpm run lint,type-check,test,circular-check,buildrestart_pending, printing and paused, and the notification switch.Demo Video
aldis-fluidd-demo.mp4