Commit Graph
212 Commits
Author SHA1 Message Date
maziggy 813d9dde38 Fix Spoolman location not cleared on auto-sync when spool removed from AMS (#921)
The on_ams_change auto-sync callback set locations for new spools but
  never called clear_location_for_removed_spools(), leaving stale locations
  that caused double-booked slots. Also pass synced_spool_ids in the
  single-printer sync route to match the sync-all endpoint behavior.
2026-04-09 08:16:54 +02:00
maziggy 42b1653f52 Fix spool weight tracking when auto-archive disabled for queue/reprint prints (#839)
When auto_archive was disabled but the print was dispatched by BamBuddy
  (queue/reprint), on_print_start discarded the expected print entry and
  returned early. The archive was never promoted to _active_prints, so at
  completion archive_id and ams_mapping were both None — all tracking paths
  failed silently. Now detects expected prints before the auto-archive
  early-return and falls through to the normal promotion path. Also injects
  the stored ams_mapping into the usage tracker session for printers where
  MQTT request topic subscription fails (P1S, A1).
2026-04-08 08:17:42 +02:00
maziggy 8c00b1b75f Fix multi-plug automation only working for first plug (#903)
When multiple smart plugs were assigned to the same printer, only the
  first plug's automation triggered. All automation paths (print start
  auto-on, print complete auto-off, queue auto-off, scheduler power-on)
  now iterate every plug linked to the printer. Also fix queue auto-off
  hardcoded to Tasmota instead of using the correct service for the plug
  type.
2026-04-07 09:44:48 +02:00
maziggy 2d9a56b3d0 Fix ghost jobs from SQLite lock on print completion (#897)
Queue status update (printing → completed) failed silently when SQLite
  was locked by another writer, leaving ghost jobs permanently stuck in
  printing status. Add run_with_retry() for SQLite lock retries and split
  runtime tracker into per-printer commits to reduce lock hold time.
2026-04-07 09:20:58 +02:00
maziggy aa9b60a265 Fix spool weight tracking for fallback archives, VT trays, and notifications (#839)
Usage tracking failed silently when FTP download failed (fallback archive
  with no 3MF), when printing from external spool holder (VT tray not
  iterated by AMS fallback), and notifications showed "Unknown" for time
  and filament. Now resolves 3MF from library/previous archives, tracks
  VT tray remain% deltas, enriches notifications with usage tracker
  results, and captures print time from MQTT for fallback archives.
2026-04-05 09:59:05 +02:00
maziggy aeb61e58d9 Fix spool manager deducting double filament after print completion (#880)
Skip AMS remain% weight sync in on_ams_change while a print session is
  active. The MQTT FINISH message triggers both the AMS weight sync (SET
  from remain%) and the usage tracker (ADD from 3MF data) in the same
  event loop cycle, causing double deduction. The active-session check is
  snapshotted before any await to prevent a race with on_print_complete
  popping the session during interleaved execution.
2026-04-04 13:38:16 +02:00
maziggy 4d09b3f669 Add HTTP security headers middleware
Set X-Content-Type-Options, X-Frame-Options, and Referrer-Policy on all
  responses. CSP omitted (React inline styles would require unsafe-inline,
  negating protection). HSTS omitted (LAN app commonly accessed over HTTP).
2026-04-04 13:08:37 +02:00
maziggy 610431d6b7 Add optional PostgreSQL database support
Bambuddy can now use an external PostgreSQL database via the
  DATABASE_URL environment variable. SQLite remains the default.
  Dialect-aware helpers handle upserts, PRAGMAs, FTS (FTS5 vs
  tsvector+GIN), backup/restore, and health checks. All migration
  blocks use savepoints to prevent Postgres transaction poisoning.
  Backups are always portable SQLite format regardless of backend.
  Cross-database restore imports SQLite backups into PostgreSQL
  with automatic boolean/datetime conversion, NOT NULL default
  filling, and FK constraint handling.
2026-04-03 11:33:29 +02:00
maziggy eec1e43d6d Fix AMS history cleanup crash from naive/aware datetime mismatch
The periodic cleanup of old AMS sensor history entries (every ~24h)
  failed with "can't compare offset-naive and offset-aware datetimes".
  The cutoff used datetime.now(timezone.utc) but recorded_at stores
  naive datetimes via SQLite's func.now(). Use utcnow() instead.
2026-04-02 11:14:06 +02:00
maziggy 34ccc7b6e7 Fix bed cooled notification never firing on some firmware (#872)
Replace polling-based bed cooldown monitor with event-driven approach.
  Some firmware stops sending bed_temper after print completion, causing
  the 30-min polling loop to time out on stale cached values. Now reacts
  instantly to bed_temper MQTT data via a new on_bed_temp_update callback.
2026-04-02 10:37:48 +02:00
maziggy 914adde5aa Add REST/Webhook smart plug type (#472) 2026-04-01 10:06:01 +02:00
maziggy 24a5217d46 Fix filament usage not recorded when auto-archive is disabled
on_print_complete returned early when no archive_id was found (auto-
  archive off), skipping both internal inventory tracking (AMS remain%
  deltas) and Spoolman usage reporting. Moved the filament usage tracking
  block to run before the archive_id early-return so consumption is
  always recorded regardless of the auto-archive setting.
2026-03-28 12:30:45 +01:00
maziggy c9efa4b8bb Fix stale AMS slot data and empty slot button visibility (#784)
Some printers (e.g. H2D) only send {id, state} in incremental MQTT
  tray updates — no tray_type, tray_color, or other fields. When
  filament is unloaded (state changes from 11 to 10), the old tray
  data persisted indefinitely because the merge logic only updates
  fields present in the incoming message.

  Backend:
  - Detect tray state != 11 without tray_type in incremental updates
    and clear stale tray data (bambu_mqtt.py)
  - Expose tray `state` field via REST API and WebSocket broadcasts
    (printer.py schema, printers.py route, printer_manager.py)
  - Include AMS tray content in WebSocket dedup key so load/unload
    transitions trigger broadcasts (main.py)

  Frontend:
  - Add `state` to AMSTray interface (client.ts)
  - Show configure/assign buttons for state=10 (spool present, not
    loaded) but hide for state=9 (truly empty) on AMS/HT slots
  - Hide fill level bar on empty slots

  Tests:
  - 5 new tests covering state-based clearing, preservation, reload,
    and idempotency
2026-03-24 16:03:34 +01:00
Keybored 3f7d0e2d55 Add notification for no spool assigned for active trays, improve usage tracker logic in edge cases (#789)
Add notification for no spool assigned for active trays, improve usage tracker logic in edge cases (#789)
2026-03-24 11:31:32 +01:00
maziggy dd75c8f0c9 feat: auto-link untagged inventory spools on AMS insert (#538)
When a Bambu Lab spool is detected in the AMS but no tag match exists,
  check for an untagged inventory spool with matching material/subtype/color
  before creating a new entry. Links the RFID tag to the existing spool
  (data_origin="rfid_linked") to prevent duplicate inventory entries.
2026-03-24 09:23:25 +01:00
maziggy 7f1ced60fd Fix stale frontend after deploy due to service worker caching
The SW used stale-while-revalidate for JS/CSS, serving old cached
  bundles even after a new build. Changed to network-first (Vite already
  content-hashes filenames), bumped cache version v24→v25, and added
  Cache-Control: no-cache to the sw.js endpoint so browsers always fetch
  the latest service worker.
2026-03-23 15:12:18 +01:00
maziggy dbe86968b0 [Fix] Print complete notification not firing (#736)
Print complete notifications were chained behind the finish photo
  capture task with no timeout. If photo capture hung, the notification
  would never send. Added a 45-second timeout so notifications always
  fire regardless of photo outcome.

  Also added diagnostic logging to MQTT state detection and upgraded
  notification error logging to include stack traces for easier
  debugging.
2026-03-17 17:00:32 +01:00
maziggy fe3c1983af Add camera image rotation option (#672)
Per-printer camera rotation (0°/90°/180°/270°) for cameras mounted
  in portrait or upside-down. CSS rotation for live views, Pillow
  rotation for notification snapshots. Setting visible in external
  camera config when enabled.
2026-03-17 13:59:01 +01:00
maziggy a6d307d739 Post work PR #693
Add tests, docs, and ruff fixes for per-user email notifications

  - Fix missing timezone import in email_service.py (F821)
  - Fix unused lambda arg in main.py asyncio done_callback (ARG005)
  - Fix E302 blank line spacing for mark_printer_stopped_by_user
  - Fix F821/UP037 forward reference in user_email_pref model
  - Fix SettingsPage test for duplicate "Notifications" text
  - Add backend unit tests for permissions, schemas, and templates
  - Add backend integration tests for user-notifications API
  - Add frontend tests for NotificationsPage
  - Add user_email_pref model import to test conftest
  - Update CHANGELOG and README
2026-03-17 12:24:36 +01:00
Thomas Rambach 9562d66b6b Feature: Advanced Authentication User Email Notifications (#693)
Feature: Advanced Authentication User Email Notifications (#693)
2026-03-17 12:01:18 +01:00
maziggy e314305623 Fix HMS notifications firing for unknown/phantom error codes
Printers send many undocumented HMS codes (e.g. 0501_0002, 0501_0004,
  0300_0001) that don't correspond to real errors — calibration status,
  firmware state, etc. The frontend already filters these out (only shows
  codes in ERROR_DESCRIPTIONS), but the backend notified for all codes
  with severity >= 2, causing spurious email/push notifications on every
  backend restart when a printer has active phantom codes.

  Flip the logic: only send notifications for HMS codes with a known
  description in the backend's HMS_ERROR_DESCRIPTIONS dictionary. Also
  fix the log message to report the actual number of notifications sent,
  not the pre-filter count.
2026-03-13 13:23:01 +01:00
Dakota G fb29eb4cd7 [Fix]: Properly track filament usage data in Spoolman (#686)
Properly track filament usage data in Spoolman#686
2026-03-13 11:59:44 +01:00
maziggy 0b6facc91c Add first layer complete notification with camera snapshot (#679)
Notify users when the first layer finishes printing so they can check
  adhesion remotely. Triggers once per print when layer 2 begins
  (layer_num >= 2, capped at <= 5 to handle reconnects). Includes a
  camera snapshot attachment. Adds the on_first_layer_complete toggle
  to all notification providers, with backend/frontend/i18n support
  across all 7 locales.
2026-03-13 10:53:21 +01:00
maziggy f21e502b8f Add remote AMS drying controls (#292)
Start, monitor, and stop drying sessions for AMS 2 Pro (n3f) and
  AMS-HT (n3s) directly from the Printers page. Flame icon in AMS card
  header opens a popover with filament-based temperature/duration presets
  from BambuStudio. Live countdown status bar shows time remaining.

  Backend: cache module_type from MQTT get_version, expose dry_time and
  module_type in both WebSocket and REST paths, add supports_drying()
  with firmware gating, add server-side guard on start_drying endpoint.

  Frontend: drying button, popover with filament select + temp/duration
  sliders, status bar, start/stop mutations. i18n for all 7 locales.

  Supported: X1/X1C (fw 01.09+), P1P/P1S (fw 01.08+), H2D (fw 01.02.30+),
  H2D Pro, X1E. Not supported: P2S, A1, A1 Mini, H2S, H2C.
2026-03-12 16:46:10 +01:00
maziggy f69b0413f0 Fix bed cooled notification never firing (#497)
Two bugs prevented bed cooldown notifications from working:

  1. Stale temperature data: After print completion, the printer sends
     partial MQTT updates without bed_temper, leaving the cached value
     frozen at the end-of-print temperature. The monitor polled for 30
     minutes seeing the stale value above threshold, then timed out.
     Fix: send periodic pushall commands to force fresh temperature data.

  2. Missing fields in provider create endpoint: on_bed_cooled and all 7
     queue event toggles were omitted from the NotificationProvider
     constructor, silently discarding user selections on create (update
     worked fine via dynamic setattr).

  Also added debug logging to the bed cooldown polling loop.
2026-03-11 09:57:55 +01:00
maziggy becc4669b1 Fix greenlet_spawn error in spool auto-assign
Two lazy-load bugs caused greenlet_spawn errors in the RFID auto-assign
  flow:

  1. create_spool_from_tray set spool.k_profiles=[] AFTER db.flush(),
     but assigning to a relationship on a persistent object triggers a
     lazy load (SQLAlchemy loads the current collection before replacing).
     Moved initialization to BEFORE db.add().

  2. spool.assignments was never initialized or eagerly loaded, so
     db.add(SpoolAssignment) triggered a back_populates lazy load
     outside the async greenlet. Added assignments=[] in create and
     selectinload(Spool.assignments) in get_spool_by_tag.

  Also added exc_info=True to error handlers for full tracebacks, and
  19 new tests including greenlet regression tests that reproduce the
  exact failure.
2026-03-05 12:40:53 +01:00
maziggy 058f74a7da Add in-app bug reporting with relay, debug log collection, and privacy controls
Floating bug report button submits issues via bambuddy.cool relay (no GitHub
  token needed locally). Collects 30s debug logs with printer push_all, sanitizes
  all sensitive data, uploads logs as files to GitHub. Screenshot upload/paste/drag
  with JPEG compression. Translated into all 7 languages. Includes 21 tests.
2026-03-04 13:33:19 +01:00
maziggy 8d9894793a Fix queue 500 error when cancelled print exists (#558)
The MQTT completion handler stored "aborted" as the queue item status
  when a print was cancelled mid-print, but the response schema only
  allows "cancelled". Pydantic validation failed on the invalid status
  when listing all queue items, returning 500. Filtering by specific
  status excluded the bad row so those still worked.

  Normalise "aborted" → "cancelled" before storing. A startup fixup
  also converts any existing "aborted" rows in the database.
2026-03-01 10:03:33 +01:00
maziggy 1eeeb37b42 Updated commit message (now includes the test fix):
Fix camera button permissions & ffmpeg process leak (#550)

  Camera button on printer card was clickable without camera:view
  permission. ffmpeg processes (~240MB each) accumulated after closing
  camera streams because: (1) stop endpoint called terminate() without
  wait()/kill(), (2) HTTP disconnect detection only ran between frames
  so was blocked when the generator was stuck on stdout read, and
  (3) no mechanism caught processes orphaned by generator abandonment
  or app restarts.

  - Add camera:view permission check + tooltip to camera button
  - Fix stop endpoint: terminate() → wait(2s) → kill() → wait()
  - Add background disconnect monitor (polls every 2s, kills ffmpeg
    directly on disconnect)
  - Add periodic /proc scan (every 60s) that SIGKILLs any ffmpeg
    with rtsps://bblp: not in an active stream
  - Add noCamera i18n key to all 6 locales
  - Fix camera API test mocks for async wait() and pid attribute
2026-02-28 12:58:31 +01:00
maziggy fe5bb1fd90 Fix naive-vs-aware datetime crash from 0.2.1 timezone migration
The timezone fix (ed36eaf) replaced datetime.utcnow() with
datetime.now(timezone.utc) across ~80 call sites, but SQLAlchemy's
SQLite DateTime columns strip tzinfo on read, returning naive datetimes.
Any Python-side comparison (subtraction, <, >) between an aware "now"
and a naive DB value raises TypeError.

Add `if value.tzinfo is None: value = value.replace(tzinfo=timezone.utc)`
guards at all 8 affected comparison sites:
- maintenance.py: last_performed_at subtraction (500 on /maintenance/overview)
- auth.py: API key expires_at check (2 locations)
- print_scheduler.py: scheduled_time comparison
- smart_plug_manager.py: auto_off_pending_since elapsed calc
- smart_plugs.py: power_alert_last_triggered cooldown
- main.py: last_runtime_update elapsed calc
- archives.py: timelapse completed_at fallback
2026-02-27 17:39:32 +01:00
maziggy 2c8fbf3e33 Fix queue empty after restart by checkpointing WAL on shutdown (#523)
The SQLite WAL was never checkpointed during container shutdown, leaving
uncommitted data (including schema migrations) in the -wal file. On
restart, partial WAL recovery could cause inconsistent query results,
making the queue appear empty until a filter was applied. Add
PRAGMA wal_checkpoint(TRUNCATE) and engine.dispose() to the lifespan
shutdown handler.
2026-02-27 07:41:03 +01:00
maziggy 37d9b4c841 Fix manual spool weight overwritten by AMS auto-sync (#525)
Add weight_locked flag to spools that auto-sets when weight_used is
explicitly updated via the API. Both the MQTT AMS remain% auto-sync and
the manual force-sync endpoint skip locked spools. Usage tracker delta
tracking is unaffected. Users can re-enable AMS sync by setting
weight_locked to false.
2026-02-26 08:42:19 +01:00
maziggy c2b7b6a99a Fix inconsistent print cost calculation on reprints (#505)
Three independent code paths wrote to archive.cost with conflicting
strategies, causing the same model to produce different prices on each
reprint (e.g. £0.77, £1.54, £2.03).

- Remove add_reprint_cost (Path 3) — redundant cost accumulation that
  double-counted on top of usage tracker
- Fix usage tracker (Path 2) — compute cost from current session's
  results only, not a SUM of all historical SpoolUsageHistory rows
- Remove _reprint_archives tracking set from main.py
- Update test mocks to match simplified query pattern

archive.cost now always reflects the cost of a single print.
2026-02-24 08:58:35 +01:00
maziggy ed36eafbec Fix timestamps off by timezone offset in non-UTC containers (#504)
All backend timestamps used datetime.now() (server local time) or the
deprecated datetime.utcnow(). The frontend's parseUTCDate() assumes
timestamps without timezone indicators are UTC and appends 'Z', so
stored timestamps were off by the timezone offset when the container's
timezone wasn't UTC.

Backend: replaced datetime.now() and datetime.utcnow() with
datetime.now(timezone.utc) across 16 files (~80 call sites) for all
database fields and DB comparisons. Cosmetic timestamps (filenames,
user-facing local time formatting) intentionally left as local time.

Frontend: replaced 13 new Date(backendTimestamp) calls with
parseUTCDate() across 8 files to correctly interpret UTC timestamps.
2026-02-24 08:18:02 +01:00
maziggy faff453dec Fix bed cooled notification never triggering (#497)
The bed cooldown monitor was defined at the end of on_print_complete,
after an early return that exits when no archive is found. Prints
started from BambuStudio or the printer's touchscreen have no archive,
so the function returned before the bed cooldown task was ever created.

Moved the bed cooldown block (function def + task creation) to before
the archive_id early-return so it fires for all completed prints.
Also hardened the temperature dict check from truthiness to isinstance.
2026-02-23 13:57:58 +01:00
maziggy 32b50e02d8 Fix external spool assignments lost on restart (#493)
External spool assignments (ams_id=255) were silently deleted on every
AMS change because _find_tray_in_ams_data only searches AMS units, not
vt_tray. Now looks up external assignments in the printer's vt_tray data
instead, and preserves assignments when vt_tray hasn't arrived yet.
2026-02-23 09:07:42 +01:00
maziggy aeca15b585 Add SpoolBuddy integration as optional filament management add-on
SpoolBuddy turns a Raspberry Pi 4B with a PN5180 NFC reader and
NAU7802 scale into a filament management station that integrates
with Bambuddy via REST API and WebSocket.

Backend: SpoolBuddyDevice model, 10 REST endpoints (/spoolbuddy/*),
6 WebSocket broadcast types, background offline-detection watchdog.

RPi daemon: asyncio service with concurrent NFC polling (300ms,
MIFARE Classic + Bambu HKDF key derivation), scale reading (10 SPS,
5-sample moving average, stability detection), and 10s heartbeat
with exponential backoff reconnect.

Frontend: kiosk-optimized 1024x600 UI at /spoolbuddy with Dashboard
(live weight + NFC spool detection), AMS Overview, Inventory,
Printers, and Settings pages. useSpoolBuddyState reducer hook
driven by WebSocket CustomEvents.

i18n: all 6 locales (en, de, fr, it, ja, pt-BR).
2026-02-22 15:27:54 +01:00
maziggy 7232ddb5a6 Fix finish photo not captured when archive has no source 3MF (#484)
When the 3MF source file wasn't downloaded from the printer (e.g. FTP
failure on P2S), archive.file_path was null. The finish photo capture
silently skipped because it derived the save directory from file_path.

Now falls back to archive/{id}/ directory and logs a warning.
2026-02-21 17:19:47 +01:00
maziggy 055243683c Fix archive endpoints crash with "Is a directory" for BambuStudio prints (#475)
When FTP download of a 3MF fails (e.g. BambuStudio-initiated prints),
the fallback archive has file_path="". Path.exists() on
settings.base_dir / "" resolves to the base directory itself and
returns True, so ZipFile() then fails with [Errno 21] Is a directory.

Replace .exists() with .is_file() across all 15 archive route checks
and 1 in main.py. Add file_path truthiness guard for finish photo
capture to prevent saving photos under the base directory.
2026-02-21 11:35:18 +01:00
maziggy e6236301b4 Fix phantom prints from lingering SD card files (#477)
Three bugs allowed uploaded gcode files to survive on the printer's SD
card, causing firmware restarts to auto-start old prints hours later.

1. The post-print cleanup retry loop always broke after attempt 1
   regardless of delete success — delete_file_async returns False on
   failure instead of raising, so the except-based retry never fired.

2. When start_print() failed after uploading, the file was never cleaned
   up since on_print_complete never fires for prints that never started.

3. Cleanup failures were logged at DEBUG level, invisible in production.
2026-02-21 09:51:42 +01:00
maziggy d7a15c79bb Filter non-actionable HMS errors from notifications (#470)
Infrastructure/auth HMS error codes like 0500_0007 (MQTT command
verification failed) triggered false-alarm printer error notifications
even though they don't indicate actual print problems.

- Add suppress list for non-actionable error codes: 0500_0007 (MQTT
  auth failure), 0500_4001 (cloud connection failure), 0500_400E
  (print cancelled by user)
- Skip suppressed codes in the notification loop so they are still
  tracked in HMS state and shown in the UI, but don't fire notifications
2026-02-21 08:36:16 +01:00
maziggy 1404242e0c Fix cost tracking issues 2026-02-20 16:31:16 +01:00
MartinNYHC 965000c3ca Merge branch '0.2.1b2' into feature/cost_tracker 2026-02-20 16:26:01 +01:00
Wesley Reuel Marques SilvaandMartinNYHC ceb2de7164 Adding the energy cost from 2 to 3 decimal precision (#416)
* Adding the energy cost from 2 to 3 decimal precision

* Complying with pr

* Complying with PR

* Complying with PR

* Fix energy cost display precision in ProjectDetailPage

* Change energy cost formatting to two decimal places

* Change decimal precision for energy cost display

---------

Co-authored-by: MartinNYHC <mz@v8w.de>
2026-02-20 16:23:00 +01:00
Keybored 85645ce184 Merge branch '0.2.1b2' into feature/cost_tracker 2026-02-20 15:44:34 +01:00
maziggy b22f091dd0 Fix SD card cleanup silently failing after print completion (#374)
The post-print SD card cleanup used printer_manager.get_printer(),
which returns a PrinterInfo object with only name and serial_number.
The code then accessed .ip_address, .access_code, and .model, which
don't exist on PrinterInfo — raising AttributeError on every print
completion. The outer except Exception handler silently swallowed it
at debug level.

Result: uploaded gcode files were never deleted from the printer's
SD card root. On some printers (e.g. P1S), files left in root
auto-start on power cycle, causing phantom prints.

Replaced with a DB query for the Printer model, matching the pattern
used everywhere else in on_print_complete().
2026-02-20 14:30:26 +01:00
maziggy 3f201594f1 Fix spool usage lost when spool runs empty mid-print (#459)
When a spool ran empty during a print and the AMS auto-switched to a
backup, on_ams_change eagerly deleted the SpoolAssignment (fingerprint
mismatch). on_print_complete then queried the now-deleted assignment
and silently dropped usage.

Snapshot all spool assignments into PrintSession at print start. At
completion, prefer the snapshot over live DB queries in both the 3MF
primary path and the AMS remain% delta fallback. Fall back to live
query for backward compatibility with in-flight sessions started
before upgrade.
2026-02-20 14:08:35 +01:00
maziggy e4e6e9f8c4 Decouples file uploads and print start commands so that they run in the background asynchronously. When a print is started, the print modal no longer waits for the print to upload and start. Instead, a new toast-based UI appears with the status of prints being dispatched. Prints being dispatched can be cancelled during upload. If cancelled, the partially-uploaded gcode is deleted from the printer automatically before the FTP connection is closed.
This allows users sending particularly large prints to slow printers such as the P1-series to start a print and then move onto another task in Bambuddy immediately (such as starting more prints on more printers). It also gives the user visibility into what's happening instead of a loading indicator appearing for an indefinite period of time.

The new toast-based UI uses websockets to update in real time. It will also appear for other users / instances of Bambuddy, not just the user who started the prints, allowing more transparency and handling cases where the user closes the page and then comes back wanting to know the status of the dispatching.

fix: review fixes for background dispatch PR #408

- Restore missing imports in main.py (inventory, print_log, virtual_printers, mqtt_smart_plug_service)
- Guard voidresp() for A1 printers to prevent hang after upload
- Don't fail upload on voidresp() error since data transfer already completed
- Add ams_mapping to register_expected_print calls for Spoolman usage tracking
- Fix cancel_job TOCTOU race by using single lock acquisition
- Fix batch counter reset TOCTOU by re-checking condition inside second lock
- Add backgroundDispatch translations to fr.ts and pt-BR.ts
- Remove dead upload_progress_callback definitions
- Skip redundant "Print queued" toast in reprint mode (dispatch toast handles it)
- 5 backend tests: cancel_job single-lock TOCTOU, batch reset re-check,
  job lifecycle
- 2 FTP regression tests: voidresp error handling (upload-loop fix),
  A1 model voidresp skip
- 1 frontend test: reprint toast suppression
- CHANGELOG: background dispatch feature + test coverage entries
- README: add background dispatch to Scheduling & Automation
- Website: add feature item to Automation section
- Wiki: add Background Print Dispatch section to print-queue.md
2026-02-20 09:26:50 +01:00
Ryan Ewen 88fa58fff8 Add background print dispatching + tests 2026-02-20 08:52:49 +01:00
Ryan Ewen 4a625c1ef2 Add background print dispatching + tests 2026-02-20 08:51:13 +01:00