Dual-nozzle H2D/H2D Pro: filament matching now respects nozzle assignments
from the 3MF file. Each AMS unit feeds a specific nozzle (L/R), and the
scheduler/frontend constrain matching to only trays on the correct nozzle.
Falls back to unfiltered matching when no trays exist on the target nozzle.
L/R badges shown in the filament mapping UI. Translated in en/de/ja/it.
Fix AMS slot config overwritten on startup: on_ams_change unconditionally
unlinked BL spool assignments on every MQTT pushall, then re-assigned them
sending ams_filament_setting without setting_id — clearing the printer's
filament preset. Now compares spool RFID identifiers before unlinking.
Fix BL spool detection false positives: removed tray_info_idx from detection
logic in both backend is_bambu_lab_spool() and frontend isBambuLabSpool().
Third-party spools using Bambu generic presets had GF-prefixed tray_info_idx
values, causing misidentification. Now uses only tray_uuid and tag_uid.
SQLite WAL mode with 5s busy timeout reduces "database is locked" errors.
Prevent accidentally skipping the wrong object during a print by showing
a warning confirmation dialog with the object name and an irreversible
action warning. Uses the existing ConfirmModal component with warning
variant. Translations added for all 4 locales (en, de, ja, it).
Existing code did not allow the MQTT relay to be shut down gracefully.
Moved _client.loop_stop() after _client.disconnect() call to allow
Paho to be notified to disconnect and shut down without reconnecting.
Added _client._disconnection_event to help prevent a race condition where
a disconnection is requested but the loop is immediately stopped.
Modify _client.disconnect() to take a timeout parameter (default: 0).
Wait in that routine for the disconnection event with the given timeout.
Apply same logic to MQTTSmartPlugService and add to shutdown sequence.
Shutdown the MQTT relay last in lifespan() with a hard-coded 2 sec timeout.
The disconnect timeout is also available to printer MQTT clients.
It is left to the default of 0 at this time. If later enabled,
it should be a configuration parameters, especially for large farms.
Fixes: #327
Signed-off-by: Jeff Kletsky <git-commits@allycomm.com>
Attach printer camera snapshots to Discord webhook notifications using
multipart form-data with embed image references. Images are included
for print start, complete, progress milestones, and error events.
When no image is available, notifications send as text-only as before.
Move _notified_hms_errors tracking update before the async notification
send to prevent concurrent status callbacks from both seeing the same
errors as new. This mirrors the existing milestone tracking pattern
which correctly updates before the await.
When the same file was queued multiple times from the archive, all jobs
after the first were automatically skipped with "already printed X hours
ago". The scheduler's 4-hour safety check compared the shared archive's
completed_at timestamp, incorrectly treating legitimate repeat prints as
phantom reprints from crash recovery. This also affected single queue
items created from recently completed archives.
Remove the check entirely — the crash scenario it guarded against is
already prevented by committing item.status="printing" before sending
the print command.
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.
External links behind reverse proxies (Traefik, nginx) block iframe
embedding via X-Frame-Options/CSP headers. Add a per-link boolean
toggle so users can choose between iframe (default) and new-tab
behavior. Keyboard shortcuts also respect the setting.
Underscores in job names or error codes caused parse_mode to be
disabled entirely, rendering *bold* markers as literal asterisks.
Now escapes underscores in the body with \_ so Markdown stays enabled.
The cover image URL was always /printers/{id}/cover regardless of the
active print, so the browser served its cached image. Now appends the
print name as a cache-busting query parameter and resets load state
when the print changes.
Content-Disposition parser now handles RFC 5987 percent-encoded filenames
(filename*=utf-8''...) used by FastAPI for non-ASCII characters. Applied
to all 7 download endpoints.
Closes#334
Rename the "Users" settings tab to "Authentication" and move the
standalone "Global Email" tab into it as an "Email Authentication"
sub-tab. This groups email/SMTP configuration with user management
where it logically belongs.
- Remove top-level email tab from settings tab bar
- Add sub-tab navigation (Users / Email Authentication) inside the
Authentication tab
- Handle legacy ?tab=email URLs by mapping to users tab + email sub-tab
- Update i18n keys in en/de/ja locales
Nozzle rack slots use the filament color as background, but light
colors (white, beige) made the white "0.4" text nearly invisible.
Added a luminance check to switch to dark text on light backgrounds
and removed the dark text shadow when not needed.
The snapshot-diff timelapse detection captured its baseline of existing
MP4 files at print completion time inside a background task. Fast-
encoding printers could finish writing the timelapse before the baseline
was taken, so the new file appeared in the baseline and was never
detected as "new" — resulting in no timelapse attached.
Move baseline capture to print start time, when the timelapse file
cannot possibly exist yet. The baseline is stored in a module-level dict
keyed by printer_id and popped at completion time. Falls back to
completion-time baseline if the app was restarted mid-print.
The snapshot endpoint always used the internal printer camera even when
an external camera was configured. Now checks for external camera first,
matching the stream endpoint pattern. Also added retry logic (3 attempts,
2s delay) to MJPEG and RTSP stream generators so they reconnect on
timeout instead of silently ending the stream.
There were several instances where print() debugging was used instead of
or in addition to formal logging. These messages were tagged `[EXT-CAM]`.
Where there was already a logging call, the print statement was removed.
Where there was not, logger.debug() was used as a replacement.
Signed-off-by: Jeff Kletsky <git-commits@allycomm.com>