Skip to content

feat: check and install firmware updates through Prusa Connect - #4

Merged
trevin-lee merged 3 commits into
trevin-lee:mainfrom
ynezz:pr/connect-firmware
Oct 10, 2026
Merged

trevin-lee merged 3 commits into
trevin-lee:mainfrom
ynezz:pr/connect-firmware

Conversation

@ynezz

@ynezz ynezz commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

hi, my clankers found this nice project during research, I liked it so decided to make you aware about the changes I've made for my use cases, so everyone can profit from that, but feel free to close it or implement it differently, happy to help with testing, cheers, Petr


Without a USB stick and a browser, prusactl could not tell which firmware the printer runs, and could not update it.

  • prusactl firmware (MCP: get_firmware_status) shows the running firmware and the latest firmware that Prusa Connect offers for the model of the printer.

  • prusactl firmware update [--version X.Y.Z] (MCP: update_firmware) does what the update button of the Connect web app does:

    1. Connect copies its hosted .bbf file to the USB drive of the printer through a download-queue entry.
    2. prusactl waits for the copy.
    3. It installs the firmware with the FLASH command. This restarts the printer.

    The command does nothing when the printer already runs the latest version and no --version is given.

  • The printer accepts FLASH only when it is idle, ready, finished or stopped. During a print, the file stays on the drive and the result says so.

  • A preparatory refactor makes defaultStorage prefer a USB drive and skip corrupted storages. It also gives the refused-state error a type. A table test covers both.

The API flow comes from the Connect web app. The firmware files of Prusa belong to team_id 0. path is the 8.3 short name on the drive, for example /usb/COREON~3.BBF.

Testing

  • Unit tests with a mocked Connect: status, the up-to-date case that does nothing, copy then poll then FLASH, FLASH refused during a print (the file stays on the drive), and a timeout.
  • Live on a CORE One L:
    • status (6.8.1 running, 7.0.0 offered)
    • copy of 6.5.7 to USB during a print, with FLASH correctly refused while PRINTING and the file left on the drive
    • end-to-end update from 6.8.1 to 7.0.0 with one prusactl firmware update after a print had finished. The copy to the USB drive took less than 1 minute. The printer installed the file, restarted and came back idle on 7.0.0+16903 about 4 minutes after FLASH. Connect then reported the firmware as up to date. Nobody had to touch the printer during the update.

@trevin-lee

Copy link
Copy Markdown
Owner

The flow is right and the mocked tests are good. Three bugs I'll fix:

  • tools_firmware.go:250 sets installed: true regardless of res.State. A sync command the printer REJECTED comes back as installed, with a note saying it restarts. runCommand returns no error for a rejected sync command (I had the same bug in cmd send and fixed it in 0.2.1). installed will be false unless the event is FINISHED.
  • upToDate() at :56 is string equality. A printer on 7.0.0-RC1 with Connect offering 6.8.1 reports update_available: true, and firmware update with no flags flashes 6.8.1. I'll decide on Connect's support.state and refuse a downgrade unless --version is explicit.
  • The state check is before FLASH only, so the download is enqueued in any state. Mid-print it writes the .bbf to the USB, polls for 2 minutes, and exits 0. Moving the check before the queue POST, the way upload_file then=print does.

Smaller: require transferring == nil before FLASH, and "pass version" in the error becomes --version for the terminal reader. I'll also add one line to the tool description: install only when the user asked for it.

After the fixes, an end-to-end flash on your L would be the real test. Mine is on 6.8.1 with 7.0.0 offered, so I may run it here too.

@trevin-lee
trevin-lee force-pushed the pr/connect-firmware branch 2 times, most recently from 7ffabd5 to 49f1fc4 Compare October 10, 2026 08:18
ynezz and others added 2 commits October 10, 2026 01:22
…fused-state error

The next commit needs two small changes. They are separate, so that the
feature commit is easy to read.

`defaultStorage` returned the first writable storage of the printer. It
now skips a corrupted storage and prefers a USB drive over other
storages, as the Connect web app does. A USB drive is the only place
where a printer keeps a file that it must install.

The refusal in `runCommand` for the current printer state is now a
`stateError`. Before, it was an anonymous `fmt.Errorf`. A caller can now
tell "the printer is in the wrong state for this" from any other
failure. The message is unchanged.

`TestDefaultStorage` covers the storage choice. `TestRunCommandStateError`
covers the error type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Petr Štetiar <ynezz@true.cz>
Before, you could not ask the printer which firmware it runs, or update
it, without a USB stick and a browser.

`prusactl firmware` (`get_firmware_status`) shows the running firmware
and the latest firmware that Prusa Connect offers for the model of the
printer. It also shows whether they differ.

`prusactl firmware update` (`update_firmware`) does these steps:

1. Connect copies its hosted `.bbf` file to the USB drive of the printer
   through a `download-queue` entry.
2. prusactl waits for the copy.
3. It installs the file with the `FLASH` command. This restarts the
   printer.

The command does nothing when the printer already runs the latest
version and no `--version` is given. The printer accepts `FLASH` only
when it is idle, ready, finished or stopped. During a print, the file
stays on the drive and the result says so. You can run `update` again
after the print. The result says "installing" only after prusactl has
sent `FLASH`.

A tool that waits can now report progress. The CLI prints these lines on
stderr as they arrive. `runCommand` takes an `onSend` callback. It runs
once a command has passed every check.

All of this goes through Prusa Connect only, so it needs
`prusactl login`.

Tested on a CORE One L: `prusactl firmware update` updated the
printer from 6.8.1 to 7.0.0 in one command. The copy to the USB drive
took less than 1 minute. The printer installed the file, restarted and
came back idle on 7.0.0+16903 about 4 minutes after `FLASH`. Connect
then reported the firmware as up to date. Nobody had to touch the
printer during the update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Petr Štetiar <ynezz@true.cz>
…h installed

Three things in update_firmware said more than they knew.

"installed" was true whatever the printer answered: a FLASH the printer
REJECTED came back as installed, with a note saying it restarts. The
event that ended the wait decides it now: FINISHED is installed, REJECTED
is the printer refusing, and anything else (CREATED, EMIT, no event) is
reported as not confirmed rather than as either.

"Up to date" was string equality, so a printer running something newer
than Connect's latest (a release candidate) was flashed back to the
older build with no --version given. Connect's own verdict on the
firmware decides now, the one its update button goes by: "outdated"
installs, "supported" does nothing, and any other state, or none, is a
refusal to decide, in both tools; --version still installs a specific
one where Connect offers updates at all.

The state check sat before FLASH only, so a call during a print wrote
the .bbf to the drive the print was reading from, polled for two
minutes and exited 0. It refuses before copying anything, the way
upload_file then=print does, and a record with no state at all is
reported as Prusa changing its API rather than as a printer to refuse;
a print that starts during the copy still leaves the file staged, and
the result says so.

Also: a path reported while a transfer is still running is not a file
to flash, and "pass version" reaches the terminal as --version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@trevin-lee
trevin-lee merged commit 8201b3f into trevin-lee:main Oct 10, 2026
4 checks passed
@trevin-lee

Copy link
Copy Markdown
Owner

Merged in v0.3.0. Two things changed from what I said, after a second review of my own fixes:

  • "Up to date" is decided only on support.state: outdated installs, supported does nothing, and anything else (or no state) refuses to decide and asks for --version. On my printer Connect reports outdated for 6.8.1 to 7.0.0, which matches. I haven't seen the string Connect sends for a printer that is current. Yours is on 7.0.0 now, so prusactl firmware --json would tell me whether it's supported or something else.
  • installed is true only when the sync FLASH ends with FINISHED. REJECTED says the printer refused; CREATED or EMIT says it wasn't confirmed, rather than claiming either way.

One more thing only a live run can answer: the copy loop now accepts path only once transferring is gone, on the assumption that Connect never reports both at once. If your next update stalls with "made no progress on the download" while the file is clearly on the drive, that assumption is wrong and I'll relax it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants