* feat: add ScheduledDrying model for delayed drying runs (#2638)
* Release the printer when a scheduled dry ends (#2638)
_check_scheduled_dryings marks a printer as drying in _drying_in_progress,
which is shared with auto-drying. Auto-drying prunes that map in
_sync_drying_state(), but that call sits behind its enabled check, and this is
the first writer that runs whether auto-drying is on or not. With it off --
the default -- nothing dropped the entry short of a print being dispatched to
the same printer, so the next scheduled run parked on "already_drying"
forever and queue_drying_block held that printer's prints too. A nightly
off-peak dry with no printing in between is exactly the workflow this feature
is for: night one worked, every night after it silently did not. The check now
releases what it acquired, covering both a run that ends mid-pass and one
cancelled through the route between passes.
The retention prune ran on every pass. Issuing the DELETE is what opens a
write transaction, this method is called every 3s while the queue dispatches,
and rows only become prunable a week after they finish, so it is now gated to
hourly on a monotonic stamp -- with the first pass after a restart still
reaping whatever the dead process left behind.
Both drying paths now pick the blocking dry_sf_reason through one rule.
The immediate endpoint quoted whichever code the firmware listed first while
the scheduler prioritised power over retract, so one blocked AMS read two ways
depending on which button you pressed. drying_preflight.primary_reason_code
holds the order and both call it, including the flame button's tooltip, which
had no wording for filament at the outlet at all and sent those users to the
generic "can't start drying right now".
scheduled_drying joins the model list in init_db. The table was already
created -- importing the package registers it -- but it was the only model
relying on that indirection.
Tests: the release (completion and route-cancel), the prune throttle, the
shared reason rule, the tooltip priority, and four driving the real check_queue,
which nothing covered before -- a pass with no rows still dispatching prints, a
due row dispatching, and a failed row not stalling the queue behind it. Each
one fails against the code it guards.
---------
Co-authored-by: MartinNYHC <martin@bambuddy.cool>
Co-authored-by: maziggy <mz@v8w.de>
check_queue() read the plate-clear setting with _get_bool_setting(default=True),
but SettingsSchema.require_plate_clear defaults False and the whole frontend
treats a missing value as off. Since _get_bool_setting returns its default when
no DB row exists, installs that never saved the setting enforced the plate-clear
gate the UI showed as disabled — FINISH-state printers never dispatched and no UI
control existed to clear awaiting_plate_clear. Read the setting with default=False
so the enforced behavior matches the schema and the toggle. Both defaults shipped
together in #752; this aligns them.
With Auto Off enabled and another job queued, the smart plug cut power when a
print finished and immediately re-powered the printer because the scheduler
saw pending items. The printer booted fresh into IDLE and the next job
auto-dispatched, bypassing the "Clear Plate & Start Next" confirmation.
Root cause: the plate-clear gate lived only in PrinterManager._plate_cleared
(in-memory set) and _is_printer_idle treated IDLE as unconditionally idle. On
power cycle the in-memory flag was lost and the IDLE-on-boot state skipped
the gate entirely.
Fix:
- Replace the in-memory flag with an awaiting_plate_clear column on the
printers table, rehydrated into the PrinterManager at startup.
- Set the flag in on_print_complete for completed/failed prints (not user
cancellations); clear it on ack and on scheduler dispatch.
- _is_printer_idle now short-circuits to not-idle whenever require_plate_clear
is on and the flag is set, regardless of the currently reported state —
so the gate holds through power cycles, Bambuddy restarts, and the printer
booting back into IDLE.
- /printers/{id}/clear-plate no longer requires the printer to report
FINISH/FAILED; it accepts the ack whenever the flag is raised.
- Frontend widgets (PrinterQueueWidget, Layout, BulkPrinterToolbar) gate on
the flag rather than reported state.
Tests: added regression tests for IDLE+awaiting=True (the #961 case) and
full DB round-trip tests for the persistence layer.
Stagger option now available when printing directly to multiple printers,
not just in queue mode. Prints are automatically queued with staggered
start times using group size/interval from Settings. New "Require
plate-clear confirmation" setting lets farm users disable per-printer
plate confirmations so queued prints start automatically on finished
printers.
Also fixes settings API type parsing for require_plate_clear (boolean),
stagger_group_size and stagger_interval_minutes (integer) — without this,
saved values returned as strings would cause the settings toggle to
always show enabled and trigger a permanent save loop.
Suppress SQL/aiosqlite debug noise (~90% log volume reduction), add
caller-traced PRINT COMMAND logging to start_print(), log scheduler
queue checks, tighten stale archive ilike match to exact match, and
warn on multiple queue items in "printing" status. Includes 18 new
unit tests.
The print scheduler previously treated FINISH/FAILED as idle states
and would auto-start the next queued print before the user cleared
the build plate. Now requires explicit user confirmation via a
"Clear Plate & Start Next" button on the printer card. Uses an
in-memory plate-cleared flag — no MQTT command needed since the
scheduler's start_print overrides the printer state.