When targeting a specific printer, the scheduler's power-on-wait loop
created new MQTT clients on each attempt. Each new client re-tried the
request topic subscription, which some brokers (e.g. A1) reject by
disconnecting. This caused a 170s thrash loop leaving the connection
fragile, so the eventual print command silently failed to reach the
printer.
Cache request topic support per serial number at the class level so
new client instances inherit the knowledge and skip the subscription.
When a printer shuts down it sends a final MQTT message with
tray_exist_bits=0 and power_on_flag=false. The tray_exist_bits
clearing code processed this all-zero value, wiping every AMS
slot's filament data. On reconnect, the auto-unlink check saw
empty tray data (no color, no type) and deleted all spool
assignments as "fingerprint mismatch".
Fix: skip tray_exist_bits slot clearing when power_on_flag is
false. Defaults to true when absent for backwards compatibility.
Adds 3 regression tests covering shutdown preservation, genuine
removal still working, and missing power_on_flag fallback.
Add a "Rotate spool during drying" checkbox to the manual drying popover
for AMS 2 Pro and AMS-HT units. The firmware-level rotate_tray field was
already sent (hardcoded to false) — this makes it user-configurable.
The checkbox defaults to unchecked and resets each time the popover opens.
Firmware silently disables rotation if filament is currently loaded.
When all matching printers were busy and a job was queued with ASAP
timing, the scheduler immediately fired a "Job Waiting for Filament"
notification even though the job was just waiting for a printer to
finish — no user action required.
Added _is_busy_only() check to skip the waiting notification when the
only reason is "Busy". Notifications still fire for actionable reasons
(missing filament, offline, wrong color). Also renamed the default
notification template title to "Queue Job Waiting" and updated
descriptions across all 7 locales.
A1/A1 Mini printers fail to connect through VP proxy mode while
X1C/P2S/H2C work fine with identical transparent TCP proxy code.
Root cause unknown — the proxy is model-agnostic, so the failure
suggests BambuStudio uses a different connection flow for A1 models
that hits ports we don't proxy.
Add diagnostic probe listeners on ports 21, 80, and 443 on each
proxy VP's dedicated bind IP. If the slicer connects to any of
these un-proxied ports, a WARNING is logged so debug logs and
tcpdump can reveal what's missing.
The closed-source bambu_networking DLL validates TLS connection parameters
and rejects connections where the certificate doesn't match the printer's
real BBL CA certificate. The TLS-terminating proxy presented Bambuddy's
own certificate, causing X1C/X1 prints to silently fail after verify_job.
Switch to transparent TCP proxying for FTP, FileTransfer, Camera, and FTP
data — only MQTT remains TLS-terminated (required for IP rewriting). The
slicer now gets end-to-end TLS directly with the printer's real certificate.
Changes:
- SlicerProxyManager uses TCPProxy for FTP (990), FileTransfer (6000),
Camera (322), and pre-listens on FTP data ports (50000-50100)
- Only MQTT (8883) uses TLSProxy for IP rewriting
- Remove debug logging from MQTT and FTP proxy code
- Fix install.sh missing AmbientCapabilities=CAP_NET_BIND_SERVICE
- Update module docstring, migration docs, README proxy description
- Add tests verifying transparent proxy architecture
When the slicer and printer are on different VLANs, Bambu Studio could
not send prints through the proxy because the printer's real IP leaked
through MQTT payloads, the bind protocol forwarded the real printer's
identity, file transfer and camera ports were not proxied, and FTP
data connections raced the TLS handshake on zero-byte uploads.
- Rewrite IP addresses in MQTT PUBLISH payloads (string + integer)
with proper packet framing and cross-chunk buffering
- Respond to bind/detect with VP identity via BindServer
- Add TLS proxies for port 6000 (file transfer) and 322 (RTSP camera)
- Buffer slicer FTP data during printer connection setup
- Advertise configured VP name in SSDP proxy
- Add cross-subnet SSDP wildcard listener for VPN setups
- Register UserEmailPreference model in models/__init__.py
- Add 11 unit tests for MQTT rewrite, IP conversion, SSDP name
When the slicer and printer are on different VLANs, Bambu Studio could
not send prints through the proxy because the printer's real IP leaked
through MQTT payloads, the bind protocol forwarded the real printer's
identity, the port 6000 file transfer tunnel was not proxied, and FTP
data connections raced the TLS handshake on zero-byte uploads.
- Rewrite IP addresses in MQTT PUBLISH payloads (string + integer)
with proper packet framing and cross-chunk buffering
- Respond to bind/detect with VP identity via BindServer
- Add TLS proxy for port 6000 (file transfer tunnel)
- Buffer slicer FTP data during printer connection setup
- Advertise configured VP name in SSDP proxy
- Add cross-subnet SSDP wildcard listener for VPN setups
- Register UserEmailPreference model in models/__init__.py
- Add 11 unit tests for MQTT rewrite, IP conversion, SSDP name
After sending a print command via MQTT, monitor whether the printer's
gcode_state changes within 15 seconds. If not, log a warning visible in
support packages. Addresses silent command drops observed on P1S firmware
01.09.01.00 where the printer ignores project_file commands while
continuing to send status updates.
Ntfy notifications with camera snapshots failed when the printer name
or filename contained non-ASCII characters. httpx enforces ASCII
encoding on string header values, but the Title and Message headers
can contain printer names with accented letters or CJK characters.
Encode these header values as UTF-8 bytes, which ntfy handles correctly.
Test notifications were unaffected because they use a hardcoded ASCII
title and no image attachment.
When running multiple virtual printers with different access codes on
separate bind IPs, FTP connections were always routed to the wrong VP.
Root cause: the iptables REDIRECT rule (990→9990) rewrites the
destination IP to the incoming interface's primary address. With Linux's
weak host model (arp_filter=0), packets for secondary IPs arrive on the
primary interface, and REDIRECT sends them all to the first VP's FTP
server. MQTT was unaffected because port 8883 had no redirect.
Fix: FTP server now binds directly to port 990 (standard implicit FTPS),
eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE
(already set in the systemd service file and Docker image).
Also removed a global asyncio set_exception_handler() in the MQTT server
that was overwritten by each VP instance, causing spurious "Unhandled
exception in client_connected_cb" errors on startup.
Changes:
- FTP_PORT: 9990 → 990 (ftp_server.py)
- Removed set_exception_handler() from MQTT server
- Updated Dockerfile, docker-compose.yml port mappings
- Deprecated --redirect-990 in install script
- Updated wiki: removed iptables instructions for all platforms
- Added migration guide (docs/migration-vp-ftp-port.md)
- Added unit tests for port constant and no-global-state invariant
FTP PASS commands were logged with the plaintext password visible in
log files. Since support packages include logs and are shared publicly
on GitHub issues, this exposed user access codes. Now redacted as
PASS ********.
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.
X1C and X1 virtual printers used legacy SSDP model codes
(3DPrinter-X1-Carbon, 3DPrinter-X1) that BambuStudio doesn't
recognize, causing "incompatible printer preset" errors when
sending prints. Changed to the correct codes (BL-P001, BL-P002)
that real printers report via SSDP.
Also fixed proxy mode auto-inherit storing printer display names
(e.g. "X1C") instead of SSDP codes, by adding a resolution layer
that maps display names to model codes.
DB migration auto-converts existing VPs on startup.
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
Webhook providers did not include image data (e.g. camera snapshots
from first layer complete notifications) even though other providers
like Telegram, Pushover, and Discord already attached them. The webhook
payload now includes a base64-encoded "image" field when a snapshot is
available (generic format only, excluded from Slack format).
The Debian ffmpeg package uses GnuTLS, whose hardened defaults reject
TLS renegotiation and legacy ciphers that some Bambu printer firmwares
(notably P2S) rely on — causing RTSP sessions to drop after a few
seconds.
Add a local TLS termination proxy (Python ssl/OpenSSL) that handles
the TLS connection to the printer and exposes a plain RTSP port to
ffmpeg. The proxy rewrites RTSP request-line URLs (rtsp://proxy →
rtsps://printer) while preserving Authorization headers so Digest
auth hashes remain valid.
Also:
- Reduce RTSP reconnect delay from 1.0s to 0.2s
- Add ffmpeg fast-start flags (-probesize 32, -analyzeduration 0,
-fflags nobuffer, -flags low_delay)
- Fix external camera double rate-limiting causing choppy streams
- Apply TLS proxy to external camera rtsps:// URLs and snapshot capture
- Update orphan ffmpeg cleanup to match rtsp:// (proxied) URLs
- Add unit tests for RTSP URL rewriting and proxy lifecycle
Ambient drying: automatically dry filament on idle printers when
humidity exceeds threshold, regardless of queue state. Separate toggle
from queue auto-drying — both can run simultaneously. Uses the same
presets, humidity threshold, and power constraint detection.
Fix: block mode (wait for drying) previously skipped the humidity
auto-stop check for already-drying printers, causing drying to
continue indefinitely. Now only prevents starting new drying.
Queue auto-drying: scheduler automatically starts drying on idle printers
with scheduled queue prints when AMS humidity exceeds the configured
threshold. Uses conservative parameters (lowest temp, longest duration)
for mixed filaments. Drying stops when humidity drops below threshold
(30-minute minimum prevents oscillation), when scheduled items are
removed, or when the feature is disabled. Optional "block queue" mode
delays the next print until drying completes.
Configurable presets: temperature and duration per filament type,
editable in Settings → Print Queue, used by both manual drying popover
and queue auto-drying. Separate presets for AMS 2 Pro (n3f) and AMS-HT
(n3s) reflecting different heating capabilities.
PSU detection: drying button disabled with tooltip when dry_sf_reason
indicates insufficient power. Parses drying status bits and dry_sf_reason
from AMS info hex string via MQTT.
Backend: print_scheduler.py (+316 lines), bambu_mqtt.py, printer_manager,
schemas, settings route. Frontend: PrintersPage drying presets prop,
SettingsPage drying config UI, i18n (7 locales). Tests: 27 new tests in
test_scheduler_auto_drying.py covering conservative params, presets,
state sync, stop logic, minimum drying time, and auto-stop regressions.
Fix P2S camera stream dropping and snapshot capture race (#661)
P2S firmware's TLS renegotiation is rejected by Debian's hardened GnuTLS
defaults, causing ffmpeg RTSP sessions to drop after ~3 seconds. Add
GnuTLS config allowing unsafe renegotiation and legacy ciphers. Also add
ffmpeg fast-start flags, reduce reconnect delay from 1.0s to 0.2s,
remove double rate-limiting on external camera streams, and fix orphan
cleanup killing snapshot capture ffmpeg processes (exit code -9).
Or as a single combined commit:
Fix P2S camera streaming, snapshot race, and energy stats (#661, #695)
Camera: P2S firmware's TLS renegotiation rejected by Debian's hardened
GnuTLS defaults, dropping RTSP sessions after ~3s. Add GnuTLS compat
config, ffmpeg fast-start flags, reduce reconnect delay to 0.2s, remove
external camera double rate-limiting, and register snapshot ffmpeg PIDs
with the orphan tracker to prevent SIGKILL during capture.
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.
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.
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.
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.
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.
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.
Three interrelated bugs in AMS info field parsing caused AMS units
to appear on the wrong nozzle on H2D dual-nozzle printers:
1. Info field parsed as decimal instead of hex (BambuStudio uses
std::stoull with base 16)
2. Extruder ID extracted as 1-bit instead of 4-bit field (bits 8-11)
3. Partial MQTT updates replaced the full extruder map instead of
merging into it
home_flag bit 18 was incorrectly interpreted as "wired/ethernet
connection", causing the ethernet badge to always show — even on
printers without an ethernet port (e.g. A1, P1S). This hid the
WiFi signal indicator entirely.
Removed the wired_network field and ethernet badge UI. The WiFi
signal badge now shows correctly whenever the printer reports
signal strength.
* Added "Force Color Match" feature to filament override
* Update settings.py
* Update settings.py
* Label updates for warnings and checkbox case
* Initial plan
* Add noMatchingMaterial i18n translations to all 7 locale files
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Use {{material}} interpolation in noMatchingMaterial; populate from filament_overrides with getColorName
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Updates for PR#625 comments
* Added "Force Color Match" feature to filament override
* Update settings.py
* Update settings.py
* Label updates for warnings and checkbox case
* Initial plan
* Add noMatchingMaterial i18n translations to all 7 locale files
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Use {{material}} interpolation in noMatchingMaterial; populate from filament_overrides with getColorName
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
* Updates to PrintersPage and QueuePage
* Update QueuePage.tsx
* Rebase to 0.2.2b3
* Fix compile errors with FilamentOverride
* Updates for PR#625 comments
https://github.com/maziggy/bambuddy/pull/625#pullrequestreview-3908738261
* Fix force color match default, loadedFilamentTypes bug, remove dead translations
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
Co-authored-by: MartinNYHC <mz@v8w.de>
Virtual printers in Queue mode now have an "Auto-dispatch" setting.
When enabled (default), prints start automatically — preserving current
behavior. When disabled, prints are added with manual_start so they
wait for manual dispatch from the queue UI.
The FTP upload skipped reading the server's "Transfer complete"
confirmation, sending the print command before the file was flushed
to the SD card. Now waits for the 226 response with a 60s timeout.
Add {eta} template variable showing wall-clock completion time
(e.g. "15:53" or "3:53 PM") to print_start, print_progress, and
queue_job_started notifications. Respects the user's time_format
setting (12h/24h). The existing {estimated_time} variable continues
to show duration ("1h 23m").
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.
enum.StrEnum was added in Python 3.11, but the documented minimum is
3.10. Add a compatibility shim in backend/app/core/compat.py that falls
back to (str, Enum) on older versions. Updated all 5 import sites and
lowered pyproject.toml target-version to py310.
Bambu printers only support plain RSA key exchange ciphers
(AES256-GCM-SHA384, AES128-GCM-SHA256), but Python's OpenSSL 3.x
defaults exclude them in favor of forward-secrecy (ECDHE/DHE) only.
Add the RSA ciphers to the client SSL context so the TLS proxy can
connect to the printer's bind port (3002).
When the print queue scheduler finds pending items but doesn't start
any, there was no logging to explain why. Added INFO-level summary
after each queue check showing skip reasons (manual_start,
scheduled_future) and busy printer details (state, connected,
plate_cleared). Also added DEBUG-level logging in _is_printer_idle()
for the specific condition that failed.
* AMS Labels addition to PrintersPage
* Added database reinitialization for schema migrations on database restore
* Bug fixes and AMS Label persistence updates
* Update database.py to resolve PR conflicts
* PR Comment Resolution
* Resolve conflicts in database.py for PR#570
* Updates to address PR#570 additional comments
* Optimize visibility handling for popup component
* Improve serial key handling in printers.py
Refactor serial key assignment to handle empty AMS serial gracefully.
* Implement error handling in serial number mapping
Add error handling for serial number mapping.
* Add migration to drop old ams_labels table
---------
Co-authored-by: MartinNYHC <mz@v8w.de>