The migration that removes the UNIQUE constraint on smart_plugs.printer_id
used an exact substring match ("printer_id INTEGER UNIQUE") to detect the
constraint. Databases created with older SQLAlchemy versions may express
the constraint differently (quoted column names, table-level UNIQUE clause,
or separate UNIQUE indexes), causing the migration to silently skip.
Users hit "IntegrityError: UNIQUE constraint failed: smart_plugs.printer_id"
when assigning a second HA switch to a printer.
Replace the exact string match with regex pattern matching that handles
inline constraints, table-level UNIQUE(printer_id), quoted column names,
and standalone UNIQUE indexes.
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.
Non-script HA entities (switch/light/input_boolean) on the printer card
always sent turn_on when clicked, which is a no-op when the switch is
already on. Now sends toggle for non-script entities so the badge click
actually changes the switch state. Script entities still use turn_on.
Also fix SmartPlugManager._get_plug_for_printer() using
scalar_one_or_none() which crashes with MultipleResultsFound when
multiple plugs are assigned to the same printer (e.g., Tasmota + HA
switch). Now fetches all plugs and returns the main non-script plug.
Add tests for both fixes: _get_plug_for_printer multi-plug handling
(5 tests) and canonicalFilamentType/filamentTypesCompatible/autoMatch
PA-CF equivalence (8 tests).
Bambu Lab firmware treats PA-CF, PA12-CF, and PAHT-CF as interchangeable,
but the print scheduler and filament override UI used exact string matching.
A 3MF requiring PA-CF wouldn't match PA12-CF in the AMS, leaving the
override dropdown disabled and preventing scheduler assignment.
Add a filament type equivalence system (both backend and frontend) so
these PA variants are treated as compatible in scheduler assignment,
AMS slot matching, force color match validation, and the override dropdown.
Also fix the "Force color match" checkbox click target being too wide
(entire row instead of just the checkbox/label) by changing flex to
inline-flex on the label element.
The embedded camera viewer only handled mouse events (mousedown,
mousemove, mouseup) for window dragging and resizing. On iOS/iPadOS,
touch input doesn't trigger mouse events, so the camera window
couldn't be repositioned — touch just scrolled the page underneath.
Add touch event handlers (touchstart, touchmove, touchend, touchcancel)
alongside mouse handlers for both the header drag handle and the
bottom-right resize handle. Uses preventDefault on touchmove to
prevent page scrolling during drag.
When all higher-priority slot-to-tray mapping methods failed (MQTT
mapping, print command mapping, queue mapping, color matching), the
internal usage tracker fell back to slot_id - 1 which can never reach
external spool IDs (254/255) or AMS-HT IDs (128+). Added position-based
resolution using sorted available tray IDs, matching the Spoolman fix
from PR #686.
When inventory spool assignments had stale weight_used values, the fill
level fallback chain (Spoolman → Inventory → AMS remain) used nullish
coalescing (??), which doesn't fall through on 0. A stale inventory
fill of 0% permanently shadowed the correct real-time AMS remain value.
Viewer users were unaffected because their group lacked the
inventory:view_assignments permission, so the query never fired.
Now when inventory says 0% but AMS hardware reports positive remain,
the inventory value is bypassed in favor of the live AMS data.
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.
When creating a virtual printer in proxy mode, the model was always set
to X1C because the frontend hides the model dropdown and the backend
used a hardcoded default. Now auto-inherits the model from the target
printer in proxy mode — on create, on target printer change, and on
mode switch to proxy.
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.
Cloud credentials were stored globally — one Bambu Cloud account per
Bambuddy instance. When auth was enabled, any user logging into Cloud
overwrote everyone else's credentials. Credentials are now stored
per-user: each user gets their own independent Cloud login.
Also fixed cloud data endpoints (settings, fields, preset CRUD)
requiring settings:read/settings:update permissions instead of
cloud:auth — users who had "Cloud Auth" enabled but "Settings"
disabled couldn't load profiles after logging in.
The P2S firmware drops RTSP sessions after a few seconds with an I/O
error. The backend treated this as fatal, ending the MJPEG stream and
forcing the frontend through a full reconnection cycle. Added transparent
auto-reconnection: when ffmpeg's RTSP connection dies, it respawns
immediately and continues streaming MJPEG frames to the browser with
only a brief freeze (~1s). Up to 30 reconnections before giving up.
The Docker image (python:3.13-slim) didn't include iproute2, so the
`ip` command wasn't available. The code fell back to ioctl-based
enumeration which can only return one IP per interface — aliases like
eth0:1 were completely invisible. Added iproute2 to the Dockerfile.
The compatible_printers filter on local presets parsed the stored JSON
array (e.g. '["BBL X1C 0.4 nozzle"]') as a semicolon-delimited string,
so matching always failed and every local preset was silently skipped.
Removed the filter entirely — user-imported profiles should be usable
on any printer.
Three bugs: (1) home_flag bit 18 is set on all printers regardless
of connection type, so every ethernet-capable model showed the badge
even on WiFi. Replaced with wifi_signal heuristic — ethernet printers
with WiFi disabled report a hardcoded -90 dBm sentinel. (2) The lazy
import used `from app.utils.printer_models` which crashes with
ModuleNotFoundError in paho-mqtt's background thread (correct path:
backend.app.utils.printer_models). This killed the thread, causing
all printers to go stale after 60s and disconnect/reconnect in a
loop. (3) WiFi-only models excluded via model-based gating.
home_flag bit 18 is set on all printers regardless of hardware,
causing the ethernet badge to appear on WiFi-only models (A1, P1P,
etc.). The previous fix removed the feature entirely, so ethernet
printers lost their badge too.
Now only trusts bit 18 on models with an ethernet port (X1C, X1E,
P1S, P2S, H2D, H2D Pro, H2C, H2S). WiFi-only models always show
the WiFi signal badge.
The GET /api/v1/support/debug-logging endpoint returned 500 when the
database contained a timezone-aware enabled_at timestamp written by
0.2.2b3. The duration calculation mixed offset-aware and offset-naive
datetimes, raising TypeError. Strip tzinfo when reading the stored value.
Sends persistent notifications to the HA dashboard using the existing
HA connection from Settings. Zero config — just select "Home Assistant"
as provider type. Users can forward notifications to mobile via HA
automations.
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.
AMS slots now show "B3" instead of "AMS-B Slot 3" and external spool
holders show "Ext" instead of "External", keeping the table readable
at narrow widths.
The debug logging banner displayed a negative elapsed time (e.g. "-60m -59s")
equal to the server's UTC offset. datetime.now() stored local time without a
timezone indicator, but the frontend's parseUTCDate() interpreted it as UTC.
Use datetime.now(tz=timezone.utc) consistently for storing, parsing, and
comparing the enabled_at timestamp.
* Enhance link spool functionality with additional printer and AMS details
* Refactor linkSpool function to accept detailed context object for improved spooling integration
* Enhance LinkSpoolModal to include amsName in props and linkSpool mutation for improved functionality
* Add amsName prop to PrinterCard for enhanced spool linking functionality
* Updates LinkSpoolModal test to work with recent spool location update changes
* Adds clear_location to unlink testing
* Refactor LinkSpoolRequest to remove ams_name and update location generation logic for improved clarity
* Remove amsName from LinkSpoolModal and PrinterCard for cleaner API integration
* Remove amsName from LinkSpoolModal test props and update linkSpool mock response for improved clarity
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.
The GET /api/v1/support/debug-logging endpoint returned 500 when the
database contained a timezone-aware enabled_at timestamp written by
0.2.2b3. The duration calculation mixed offset-aware and offset-naive
datetimes, raising TypeError. Strip tzinfo when reading the stored value.
* Expanded link logic to accept a generic spool tag (spool_tag, tray_uuid, or tag_uid) and validate 16/32-char hex values, including rejection of all-zero tags. Added a new unlink endpoint that clears Spoolman extra.tag for a given spool ID.
* Updated the link validation expectation to match the new 16-or-32-hex rule. Added an integration test for the new unlink endpoint to verify extra.tag is cleared and success is returned.
* Updated the link API call to send spool_tag instead of tray_uuid. Added a new unlinkSpool API helper that calls the new backend unlink route.
* Switched modal data source from inventory spools to Spoolman unlinked spools so the list matches the backend response you shared. Updated the link action to call Spoolman link directly and use trayUuid or tagUid as the slot tag.
* Refactored Spoolman card behavior so actions render correctly across linked/unlinked cases, and added support for an Unlink from Spoolman button under Open in Spoolman. Final UI rule now hides Unlink for Bambu Lab filament and shows it only for non-Bambu linked spools.
* Added fallback slot-tag generation so untagged slots can still be linked/unlinked reliably and linked spool lookups work consistently across AMS, AMS-HT, and external trays. Wired a new unlink mutation and passed per-slot unlink callbacks into the hover card, with query invalidation for linked/unlinked spool caches after unlink.
* Added/updated the noTrayUuid message to reflect that either tray UUID or tag UID is required when no slot tag is available. This keeps error text aligned with the new tag fallback and modal behavior.
* Added noTrayUuid message in the other languages. The translation was done using Google Translate.
* Updated LinkSpoolModal tests to use Spoolman API instead of inventory API, with new UnlinkedSpool data structure and Select Spool UI text.
* Adds hashSerialToHex32, uses new function in getFallbackSpoolTag and updates all uses. This ensures we're generating a fallback spool tag using the printers serial number rather than the database ID for consistency.
* Removes 'import json' from unlink_spool and moves to the top of the file.
* Removes hasUnlinkedSpools and related references since it's no longer used
* Removes noTrayUuid from translations and references in components.
* Adds confirmation dialog before fully unlinking spool from Spoolman
* Adds unlinkConfrimTitle and unlinkConfirmMessage. All languages other than English were done using Google Translate
* Fixes unlink toast to use dedicated unlinkSuccess and unlinkFailed keys.
* Changes tag priority on spoolTag, fixes type error by adding non-null assertion to spoolTag
* Switches around tag_uid and tray_uuid on const trayTag
---------
Co-authored-by: MartinNYHC <mz@v8w.de>
AMS slots now show "B3" instead of "AMS-B Slot 3" and external spool
holders show "Ext" instead of "External", keeping the table readable
at narrow widths.
The debug logging banner displayed a negative elapsed time (e.g. "-60m -59s")
equal to the server's UTC offset. datetime.now() stored local time without a
timezone indicator, but the frontend's parseUTCDate() interpreted it as UTC.
Use datetime.now(tz=timezone.utc) consistently for storing, parsing, and
comparing the enabled_at timestamp.
Prevent unauthorized closed-source redistribution of Bambuddy.
AGPL-3.0 requires anyone who modifies and distributes or hosts
Bambuddy to share their source code.
All prior contributions were made under MIT, which is
forward-compatible with AGPL-3.0 — no contributor rights are
affected. Community usage, forking, and contributions continue
as before; the only change is that derivative works must remain
open source.
Files changed:
- LICENSE: replaced MIT with AGPL-3.0 (from gnu.org)
- README.md: updated license reference
- CHANGELOG.md: documented license change under [0.2.3b1]
Prevent unauthorized closed-source redistribution of Bambuddy.
AGPL-3.0 requires anyone who modifies and distributes or hosts
Bambuddy to share their source code.
All prior contributions were made under MIT, which is
forward-compatible with AGPL-3.0 — no contributor rights are
affected. Community usage, forking, and contributions continue
as before; the only change is that derivative works must remain
open source.
Files changed:
- LICENSE: replaced MIT with AGPL-3.0 (from gnu.org)
- README.md: updated license reference
- CHANGELOG.md: documented license change under [0.2.3b1]