Commit Graph
1006 Commits
Author SHA1 Message Date
maziggy 2bceb1f6b4 Show the backup spool on a printing queue job's card once AMS Filament Backup takes over
When a spool ran out mid-print and the printer carried on from the backup
spool, the queue card still named the original slot and marked it Empty.
The printer status now carries the print's tray change log, and the card
shows the tray that took over: one outside the job's mapping, right after
the mapped tray it replaced, followed through a backup that ran out in
turn. Queued jobs still warn about an empty slot.
2026-10-08 15:31:20 +02:00
maziggy 10ed899130 test: count only the racing test's own archive folders (#2957)
Other xdist workers archive into the same printer directory, sometimes
within the same second, and the race test counted their folders too.
2026-10-08 15:16:25 +02:00
FormFollowsFunction a8965b0e9f Use Spoolman 0.27's native tags in Spoolman mode (issue #3168) (#3252) 2026-10-08 14:34:56 +02:00
maziggy 170335e8dc Log home_flag changes on printers without cfg to check AMS Filament Backup in update frames (#3259) 2026-10-08 11:08:18 +02:00
maziggy 549c87b926 Use a made-up token in the query-token redaction tests 2026-10-07 13:48:28 +02:00
maziggy 0b35bbd444 Mask query-string tokens in uvicorn's log lines and support bundles 2026-10-07 13:33:49 +02:00
maziggy 879e403315 Price a running print from its spools at start (#3261) 2026-10-07 09:49:51 +02:00
maziggy e7dd899963 Read AMS Filament Backup from home_flag on printers without cfg (#3259) 2026-10-07 09:06:40 +02:00
maziggy 3acc52d938 Slice with a printer preset saved under a name of its own (#3250) 2026-10-06 13:26:20 +02:00
maziggy 84f320b859 Remember when each spool was last dried (#2863) 2026-10-06 13:05:01 +02:00
Chris Bennight d2ea32ef6f Add Bambuddy Internal URL for Obico snapshots (#3255) 2026-10-06 12:19:38 +02:00
maziggy cf98daa0bb Security hardening (maziggy/bambuddy-security #10) 2026-10-06 11:48:37 +02:00
maziggy 2022824b29 Stop two tests leaving a database query running after they end 2026-10-06 11:04:38 +02:00
maziggy 5b62df754a Find the file of a print started from the printer's screen on the first try (#3009) 2026-10-06 10:26:24 +02:00
maziggy 71a98800e1 Close the queue run of a file whose name ends in a space (#3241) 2026-10-06 09:46:18 +02:00
maziggy 609d869a1c Wait for the MQTT client teardown thread before asserting on it 2026-10-06 09:26:44 +02:00
maziggy 24c5c12623 Raise the plate for the finish photo from the print's own archive (#3240) 2026-10-06 09:09:38 +02:00
maziggy e7f4ce0429 Give an Any-model job to a printer that has enough filament (#3137) 2026-10-06 08:27:38 +02:00
maziggy 3e646febd6 Don't log a printer callback cancelled at shutdown as an error (#3243)
Stopping the event loop cancels the printer callbacks still pending.
_schedule_async's done-callback then called result(), which raises
concurrent.futures.CancelledError. Unlike asyncio's, that one is an
Exception, so it was logged as "Exception in scheduled callback" with
a traceback. Two support packages show it as the last line before a
restart.

Return early on a cancelled future, as core/tasks.py already does. A
callback that raised is still logged as an error.
2026-10-06 07:55:44 +02:00
maziggy 3ddd324f87 Keep the loaded tray while an H2 slot is ambiguous (#3242)
On a dual-nozzle printer with several AMS on one extruder, tray_now is a
local slot. On every filament change it arrives about a second before
the extruder's snow, and the fallback filled the gap with the bare slot,
i.e. AMS 0. Support packages show 2,416 of these across H2C, H2D and
H2D Pro, 2,291 of them landing in the tray change log.

Resolve the slot from the printer's mapping field first, narrowed to the
active extruder's units when two trays share it. When that doesn't
decide, keep the current tray until snow names the new one.
2026-10-06 07:39:52 +02:00
maziggy 89a9a81195 Compress and cache the web interface's asset files (#3175)
/assets was served uncompressed and without Cache-Control, so phones
downloaded the full 11 MB bundle and revalidated it on every load. Text
assets are now gzip-compressed once per file and kept in memory, and
files with a content hash in their name are marked immutable. The
pdf.js runtime data has no hash and is revalidated instead.

-----

Load pages and languages when they are needed (#3175)

Every page and all 15 languages were one 11 MB script. Pages are now
separate files loaded on first open, and only English is bundled; the
language in use loads before the first render. A tab left on an older
build reloads once when a file is gone, after the server answers
again, so a kiosk is never left on the browser's connection error page.
The service worker serves hashed files from its cache and drops the
previous build's files.
2026-10-05 14:59:11 +02:00
maziggy f471fe7b27 Add Gotify as a notification provider (#2743)
Posts to the server's /message endpoint with the app token in the
X-Gotify-Key header. Each event's priority level is sent to Gotify as
0, 2, 5, 8 or 10, set in the same per-event priority section ntfy uses.
Events without a level go out at 5. The snapshot goes out as a
bigImageUrl, and the outcome confirmation opens its dialog on tap.

-----

List notification providers alphabetically

The Provider Type list in the Add Notification Provider dialog is now
sorted by the name shown on screen, in the current UI language, so the
order holds in every locale.
2026-10-05 12:24:02 +02:00
maziggy 57dd8651d8 Match a location job's trays on the printer it gets (#3239)
A job kept the AMS mapping of the printer it was made for. Moved to a
location and picked by another printer, a tray of the same material in
another colour passed the fit check, so the job printed in that colour.
The scheduler now drops a stored mapping when it assigns a job by model
or location and matches the trays on the chosen printer, by type and
colour. Moving a job from a printer to a model or location clears its
mapping and the "Print Anyway" given for that printer.

A printer without an AMS whose external spool has no filament set gave
the matcher nothing to match, so such a job went out without a mapping
and the firmware rejected it with 0700_8012. Every filament now goes to
the external spool there.
2026-10-05 11:16:41 +02:00
maziggy 24b8485f87 Remap a job sent from an external spool to a printer with an AMS (#3239)
A queue item keeps the AMS mapping it was made with. A job made for a
printer without an AMS ([254], external spool) that was moved to a
location could be picked by a printer with an AMS, and still went out
with use_ams false; the printer stopped with "External filament is
missing". The #2799 check skipped it because an external tray the
printer doesn't report as loaded counted as no evidence.

A printer that reports its external spool empty, while its AMS holds
the filament type the slot needs in a similar colour, now counts as a
conflict, so the mapping is recomputed for that printer. AMS-less
printers, including ones whose external spool has no filament set, keep
the stored mapping, and with only another colour in the AMS the printer
still asks for the spool.
2026-10-05 09:43:55 +02:00
maziggy cdb78f1321 Never delete the SD-card file the printer is printing (#3009)
The post-print cleanup picks what to delete from the finished archive.
When a print finished while Bambuddy was stopped and the file was then
reprinted from the printer's screen, startup reconciliation closed the
old archive and the cleanup deleted the file the printer was printing.

The cleanup now skips any candidate that matches the printer's current
gcode_file or job name while the printer is busy. Finished, failed and
idle states hold nothing back, so the ghost-print cleanup is unchanged;
a ghost replay's file is deleted by its own end-of-print cleanup.
The cleanup moves out of on_print_complete into its own function.
2026-10-05 08:27:23 +02:00
maziggy 08a1b68235 Keep the external spool in a virtual printer's saved AMS mapping (#3237)
The slicer writes an external spool as -1 in ams_mapping, the same as an
unused filament, and names it only in ams_mapping2. Save AMS mapping read
the flat list alone, so dispatch sent the filament as unmapped and an H2C
stopped with 0700-8012 before the first layer. Resolve it through
ams_mapping2 to the global tray (254/255, by the target's nozzle count),
as the usage capture does since #3166.
2026-10-05 07:46:24 +02:00
maziggy b596b55456 Take the electricity price from Home Assistant and cost energy hour by hour (#1251)
Each hourly energy snapshot records the price, and the energy used until the
next one is costed at it. Prints and the Statistics energy cost follow the
price over time instead of applying one price after the fact. The price can
come from a Home Assistant sensor, read hourly and at print start and end.
Energy multipliers now accept any value above zero, for plugs that report
watt-seconds.
2026-10-04 15:09:33 +02:00
Rojo ef029ad9eb Sum the lifetime counter for REST plugs in all-time energy stats (#3232) (#3233) 2026-10-04 14:30:35 +02:00
maziggy d336bb2ad2 Merge printer-scoped access (#1727) and the queue review gate (#1620) into dev
Groups can be limited to printers and whole locations, managed on the new
Printer access page, and jobs can wait for staff review before they print.

Merging onto the current dev:
- The Printer Locations page keeps to the same access rules: a rename keeps
  its groups' access, deleting a granted location or moving printers into or
  out of one needs an admin and drops the stale grant, and a user limited to
  some printers sees and changes only their own locations.
- The overlay logo checks its token without a printer, which the stricter
  overlay check now needs and the logo route doesn't have.
2026-10-04 13:29:57 +02:00
maziggy 3480d39434 Browse a Manyfold library and import its files (issue #1471)
The MakerWorld page becomes Model Sources, with a MakerWorld tab and a
Manyfold tab. Bambuddy signs in to a self-hosted Manyfold with an OAuth
application (public read scopes), lists and searches its models with their
previews, and imports 3MF, STL and STEP files into the library, from where
they are sliced and printed like any other file.

New permissions manyfold:view and manyfold:import are granted once to the
groups that hold the matching MakerWorld permissions. The client secret is
never returned and is left out of GitHub backups. The Manyfold URL and every
redirect pass the LAN-service URL check. Old /makerworld links open the
MakerWorld tab.

Several files import at once: tick files in a model, or Select all, then
Import selected or Import all; or tick models in the grid and import all
their printable files. Files import one after another, failures don't stop
the rest, and a summary reports the outcome.
2026-10-04 13:09:25 +02:00
maziggy 6954cff3ee Turn on the chamber light while the camera is in use
A printer kept dark between uses showed a black live view and sent black
notification photos (#1655). Settings → Camera can turn the light on for
all printers or for selected ones, for the live view, notification
photos, the finish photo and the snapshot API that Home Assistant uses,
and turns it off 15 seconds after the last use. It only turns off a
light it turned on, and switching the light by hand hands it back.
Snapshots wait an adjustable delay after the light comes on. Timelapse,
the Obico check and the in-print frame bank don't switch it, since they
would make it flash.

The camera card no longer lists every printer: external cameras are
added from a searchable printer picker and only those printers are
listed, so large farms get a short page.
2026-10-04 11:26:07 +02:00
maziggy 384903328e Charge every filament that shares a tray, not just the first (#3230)
The 3MF pass skipped a tray once it had charged it, so a second slicer
filament mapped to the same tray was never deducted, and a single-filament
print that switched trays and came back lost its last segment. Deduplicate
by slicer slot instead. Trays covered before the pass, and trays only
guessed by position that were already charged, are still skipped; charged
trays are still reported to the remain% fallback.
2026-10-04 09:11:21 +02:00
maziggy 902f09a7fe Notify a flickering HMS fault once, not on every return (#3226)
Track a last-seen time per fault instead of one set per printer, with a
10-minute window. Faults seen before the print resumes, starts or
finishes are forgotten as soon as they are gone, so a fixed runout that
recurs is still reported.
2026-10-03 15:31:51 +02:00
M2ABRAMSTANK 3ce9b917cb fix(db): cap SQLite pool fd bill + raise container nofile — prevents fd-exhaustion DB corruption (#2884) 2026-10-03 13:44:50 +02:00
Martin Grolmus 0a3e341584 Map each printer against its own AMS, and stop dispatching a job whose filament is not loaded (#2799) (#2803) 2026-10-03 13:28:26 +02:00
maziggyandKeybored02 12e543b1b4 Fix SpoolBuddy reading the filament type as the Bambu spool ID (#984)
The daemon took tray_uuid from tag blocks 4-5, which hold the filament
type, so every spool of one type sent the same value and the kiosk never
saved it. Read block 9, which both tags of a spool share and the AMS
reports as tray_uuid. Save it on kiosk quick-add and link in local and
Spoolman mode, and give spools added before the fix their tray_uuid when
their exact tag UID is scanned again. Drop any tray_uuid that is not a v4
UUID, so daemons that are not updated yet keep matching by tag UID.

Block 9 read based on #1200 by @Keybored02.

Co-Authored-By: Keybored02 <Keybored02@users.noreply.github.com>
2026-10-03 12:19:04 +02:00
maziggy 098fa5273f Security hardening 2026-10-02 17:29:19 +02:00
maziggy bc649befc3 fix(tests): check label text in the content stream, not the whole PDF 2026-10-02 16:39:19 +02:00
maziggy 56e067753a fix(db): PostgreSQL pool default fits a stock server 2026-10-02 15:39:07 +02:00
maziggy bff7f68106 Put back a slot's K-profile when the printer loses it (#3219) 2026-10-02 10:04:48 +02:00
maziggy e0149ae4ac Restart a camera stream that keeps repeating one frame (#3218, #3189)
After an RTSP session dropped, ffmpeg could keep writing the same JPEG
indefinitely with no connection to the printer left (two hours at ~29 fps
on a P2S), and every check that counts frames saw a healthy stream. The
stream now restarts ffmpeg after 20 s without a changed frame, on the
existing reconnect path, while viewers stay attached. If the first frame
after the restart is the same picture again, the camera really shows a
still scene (a dark, idle chamber): it is then only re-checked every
5 minutes until the picture changes.
2026-10-02 08:50:19 +02:00
maziggy 02e39b18b9 Configure Orca Cloud filaments with their own filament ID (#3216)
OrcaSlicer's Sync filaments finds a slot's preset by the slot's filament
ID alone. The Configure dialog looked up an Orca profile's ID in the
browser and quietly sent the generic for the material when that came back
empty, so Orca custom filaments reached the slicer as Generic and the log
showed nothing. configure now resolves the ID on the server from
orca_profile_id, follows inherits to the parent profile or the Bambu
filament it was copied from, logs the outcome and reports a fallback,
which the dialog shows as a warning.

Slot presets also store the filament ID they were written with, so the
slot card and the Configure dialog stop showing a preset once the slot is
changed from OrcaSlicer's Device tab or the printer.

Re-configuring a slot for another filament no longer carries over the old
filament's active K-profile, which switched the slot back to the old
filament.
2026-10-02 08:32:57 +02:00
maziggy 45921b7a56 Limit groups to selected printers (issue #1727)
A group can now be limited to a set of printers. Its members see and
control only those printers. Every other printer answers 404, as if it
didn't exist.

- Groups gain restrict_printers and a group_printers table (migration
  for SQLite and Postgres). A user's printers are the union of their
  limited groups. Groups without the flag don't limit anything, a user
  in no limited group keeps every printer, and admins see all of them.
- core/printer_scope.py holds the scope. RequestPrinterScope and
  RequirePrinterPermissionIfAuthEnabled apply it to routes: printer
  routes, camera, queue and batches, archives, projects, stats, print
  log, pipeline runs, inventory and Spoolman assignments, maintenance,
  smart plugs, scheduled drying, firmware and Obico status.
- API keys, camera stream, Cam Wall, overlay and WebSocket tokens carry
  the printers of whoever created them. WebSocket broadcasts are
  filtered per connection, and the filtering fails closed.
- Scheduler: "Any <model>" jobs stay on their owner's printers. A job
  pinned to a printer its owner lost waits with a reason. Callers with
  no user identity and limited printers must queue to a specific printer.
- Group editor: new Printer access section, translated into all 15
  locales. Saving a system group no longer resends unchanged
  permissions, which the backend refused.
2026-10-01 14:47:40 +02:00
maziggy 995b4ada59 Count progress milestones from the first layer (#3211) 2026-10-01 12:48:02 +02:00
maziggy d4f66d99a4 Keep queued jobs when a printer refuses the upload (#3210) 2026-10-01 12:18:23 +02:00
maziggy 3fae2b402b Announcements: history, collapsed list, instant refresh, footer icon
- Expired messages stay readable under a collapsed "Earlier" section, as
  long as the feed keeps them (12 months, at most 50). They never count as
  unread or raise a banner; withdrawn ones are gone everywhere.
- Each message is one row (level, date, title) that opens in place.
  Unread ones carry a dot and a New chip, and opening one is what marks
  it read; the banner's Read more opens the panel on its message.
- A fetch that brings a newer feed broadcasts an empty
  announcements_changed event, so open pages show the new dot and banner
  without a reload.
- The sidebar entry is a megaphone icon with an unread badge, in the
  footer row left of System. Footer icons are 32px with no gap so seven
  fit an expanded sidebar; with authentication on, logout used to wrap
  onto a line of its own.
2026-10-01 11:37:06 +02:00
maziggy 5eb37cd41c Show announcements from the Bambuddy maintainers
Fetch a signed feed.json from the public bambuddy-notifications repo on
GitHub at startup and every 6 hours. Nothing about the install is sent;
targeting (version, beta channel, install type) is decided locally.

- Ed25519 against a key built into the app; an older serial is refused so a
  withdrawn message can't come back. The feed replaces the stored list, and
  a failed or rejected fetch keeps the last good one.
- Sidebar entry above System with an unread count, a slide-over list, and a
  banner for unread important/critical messages. Read state per user.
- Admins by default; Settings > General > Updates can show them to all
  users or switch them off, which also stops the fetch.
- Plain text only; links to github.com and bambuddy.cool only.
2026-10-01 10:45:06 +02:00
maziggy 1cc4bae5d2 Report a refused RFID refresh, and fall back to M620 R (#3206)
ams_get_rfid was published and reported as success without reading the
printer's answer. X1Plus on base 01.08.02.00 answers FAIL / ERROR STATE,
so the user saw "Refreshing" and the K profile was re-applied to a slot
that was never read.

- Wait for the ams_get_rfid answer (matched by sequence_id).
- On a refusal from an idle X1/P1/A1, send the legacy M620 R<ams*4+slot>
  gcode Bambu Studio uses for those models, AMS units 0-3 only. Never
  during a job, never on newer-protocol models; printers that accept
  ams_get_rfid never see it.
- Refused: the route returns 400 with the printer's reason and no PA
  re-apply is scheduled. No answer still counts as accepted.
- Log the answers at INFO so refusals reach support bundles.
2026-10-01 09:40:00 +02:00
maziggy 919aa69f83 Make the {finish_photo_url} link open with authentication on
With authentication on, {finish_photo_url} pointed at the archive photo
route, which needs a media token. A link tapped in Telegram, CallMeBot
or a Home Assistant notification has none, so it only ever answered 401.

_finish_photo_for_notification() now builds the link and the attachment
bytes. With authentication off the link is the archive URL, unchanged.
With it on, the photo is also saved through the notification photo
store from #3199, and the link points there. That is an unguessable
name that opens this one photo, for 3 days. If the auth check fails,
it is treated as on. With auth on and the photo missing, no link is
set rather than one that 401s. Photos over 2.5 MB are still linked but
not attached, as before.
2026-10-01 09:16:49 +02:00
Benji 7f64e2ba8b Add feature: Attach camera snapshots to notifications (issue #3089) (#3199) 2026-10-01 08:43:44 +02:00