This might fix (#58) getting uploads from the linux version of the
BambuStudio network plugin. Issue observer is connection resets with
larger uploads (somewhere >80k).
Large 3MF uploads intermittently failed with [Errno 104] Connection
reset by peer while the 16-byte verify_job always succeeded. The
_handle_data_connection callback returned immediately, letting the
asyncio task complete while cmd_STOR was still reading from the data
connection. The passive port listener also stayed open during transfers.
- Keep _handle_data_connection alive via _transfer_done event so the
asyncio task holds strong references throughout the transfer
- Close passive port listener after accepting the data connection
- Reject duplicate data connections with a warning log
- Add drain timeout (5s) to MQTT status pushes to prevent blocking
when the slicer is busy with FTP upload
- Improve error logging with bytes received and exception type
The Add Printer dialog previously required users to manually enter their
network subnet for scanning (defaulting to 192.168.1.0/24). Now the
backend detects available network interfaces and returns their subnets
via the /discovery/info endpoint. The frontend auto-selects the first
detected subnet and shows a dropdown when multiple subnets are available,
falling back to a text input if none are detected.
AMS Lite units (A1 series) have no weight sensor and always report 0%
fill level. When a spool is linked to Spoolman with weight data, use
Spoolman's remaining weight as a fallback. External spools also show
fill level from Spoolman data instead of always showing unknown.
Backend: Enrich GET /spoolman/spools/linked response with
remaining_weight and filament_weight alongside spool ID.
Frontend: Add getSpoolmanFillLevel() helper. Update regular AMS, HT
AMS, and external spool fill computations to use Spoolman fallback
when AMS reports 0%. Show "(Spoolman)" indicator in hover card when
fill data comes from Spoolman.
H2D Pro firmware interprets use_ams as a nozzle index when sent as
integer (1 = deputy nozzle) instead of boolean. Bambu Studio sends
use_ams: true (boolean) while using integers for calibration fields.
Bambuddy was converting all boolean fields to integers for H2D series,
routing filament to the deputy nozzle instead of main, causing
extrusion motor overload at ~75% print completion.
Keep use_ams as boolean for all printers, matching Bambu Studio format.
Implements retry mechanism to handle intermittent network errors when
fetching spools cache for AMS sync operations.
Changes:
- Add retry logic to get_spools() with 3 attempts and 500ms delay
- Configure httpx client with connection pool limits to prevent stale
connection reuse (max_keepalive_connections=5, keepalive_expiry=30s)
- Recreate client on connection errors (ReadError, RemoteProtocolError)
- Abort sync operations if cache fetch fails after all retries
- Update on_ams_change, sync_single_printer, and sync_all_printers to
handle cache fetch failures gracefully
This addresses ReadError(ClosedResourceError()) failures that occurred
intermittently when Spoolman closed idle connections or connection
pooling reused stale connections.
Testing:
- Added 4 new unit tests for retry behavior
- All 1018 tests passing
- Add cached_spools parameter to find_spool_by_tag, find_spools_by_location_prefix, sync_ams_tray, and clear_location_for_removed_spools
- Fetch spools once before loops in on_ams_change, sync_single_printer, and sync_all_printers endpoints
- Cache newly created spools during sync to avoid duplicate API calls
- Add 5 unit tests for caching functionality (all passing)
- Reduce redundant API calls when syncing multiple AMS trays
- Improve sync performance for users with large spool databases
- Maintain backward compatibility with optional cached_spools parameters
- Add HA_URL and HA_TOKEN environment variables for automatic HA
integration configuration in HA add-on deployments
- Environment variables always override database settings with
non-negotiable precedence; database values preserved for fallback
- Auto-enable integration when both env vars are set; partial config
(one env var) uses database enable state without auto-enabling
- Add centralized get_homeassistant_settings() function following
Spoolman pattern; replace direct database queries across codebase
- Add ha_url_from_env, ha_token_from_env, ha_env_managed fields to
AppSettings schema to inform frontend about configuration source
- UI shows read-only fields with lock icons and "(Environment Managed)"
labels when env-controlled; toggle shows auto-enable badge
- Add comprehensive test coverage: 9 integration + 8 unit tests
Closes#283
Users with local DNS can now add printers using hostnames like
printer.local or my-printer.home.lan. Updated backend schema
validation, database column width, frontend form patterns/placeholders,
and i18n labels across all locales. Added integration tests for
hostname and FQDN creation plus invalid hostname rejection.
Enable Bambu Studio on a remote network to print through BamBuddy
acting as a TLS-terminating proxy for both MQTT and FTP connections.
- Add TLSProxy base class and FTPTLSProxy with PASV response rewriting,
EPSV→PASV translation, PROT P/C tracking, and one-shot data proxies
- Add SlicerProxyManager to coordinate per-slicer MQTT + FTP proxy pairs
- Support additional SAN IPs in certificate generation for proxy mode
- Broadcast SSDP on LAN B so slicers discover the proxy as a printer
- Narrow FTP passive port range to 50000-50100 with retry logic
- Expose proxy ports (8883, 9990, 50000-50100) in Dockerfile
- Document passive port range in docker-compose.yml
Enable Bambu Studio on a remote network to print through BamBuddy
acting as a TLS-terminating proxy for both MQTT and FTP connections.
- Add TLSProxy base class and FTPTLSProxy with PASV response rewriting,
EPSV→PASV translation, PROT P/C tracking, and one-shot data proxies
- Add SlicerProxyManager to coordinate per-slicer MQTT + FTP proxy pairs
- Support additional SAN IPs in certificate generation for proxy mode
- Broadcast SSDP on LAN B so slicers discover the proxy as a printer
- Narrow FTP passive port range to 50000-50100 with retry logic
- Expose proxy ports (8883, 9990, 50000-50100) in Dockerfile
- Document passive port range in docker-compose.yml
homeassistant_service.get_energy() was called without first configuring
the service with the HA URL and token, so it returned None for all
Home Assistant smart plugs. Added configure() call before the plug
loop, matching the pattern used in main.py and smart_plugs.py.
FTP bugs have been the #1 recurring issue across releases (0.1.8+).
This adds a real implicit FTPS mock server and 67 test cases covering
every known failure mode — connection, upload, download, delete, storage
info, model-specific SSL behavior, async wrappers, and failure injection.
New files:
- mock_ftp_server.py: implicit FTPS server on pyftpdlib with failure injection
- conftest.py: FTP test fixtures (certs, server, client factory)
- test_bambu_ftp.py: 67 tests across 10 test classes
Also adds pyOpenSSL to requirements-dev.txt (needed by pyftpdlib
TLS_FTPHandler in the Docker test image).
download_to_file() returned True when retrbinary transferred 0 bytes
without raising an exception. This caused the /cover endpoint to hit
the empty file check and raise HTTP 500 instead of retrying or
returning 404.
Now treats 0-byte downloads as failures, allowing the cover endpoint's
retry logic to work and falling back to 404 if all attempts fail.
The CodeQL cleanup in "Housekeeping" (2b11efd) bulk-narrowed except
clauses across 50+ files, breaking FTP uploads on ALL printer models.
ftplib.error_perm (550 errors) is not a subclass of ftplib.error_reply,
so diagnose_storage() CWD failures escaped the handler and prevented
STOR from ever executing — causing 100% upload failure and HTTP 500s
on /api/v1/archives/{id}/reprint and /api/v1/library/files/{id}/print.
FTP fixes:
- Remove diagnose_storage() from upload hot path
- Change all except (OSError, ftplib.error_reply) to
except (OSError, ftplib.Error) across bambu_ftp.py
Exception handling reverts (9 files):
- Revert narrowed except clauses back to except Exception in route
handlers and service code where broad catches are intentional
defensive programming (archive parsing, HTTP clients, 3MF/ZIP
processing, Home Assistant, firmware checks)
- Keep narrow exceptions only where safe (single-op blocks like
int(), file.unlink(), socket.close())
- Remove unused XMLParseError imports from archive.py, threemf_tools.py
Version system:
- Add 4-segment version support (e.g. 0.1.8.1) for patch releases
- Bump version to 0.1.8.1
Closes#287
The CodeQL cleanup in "Housekeeping" (2b11efd) bulk-narrowed except
clauses across 50+ files, breaking FTP uploads on ALL printer models.
ftplib.error_perm (550 errors) is not a subclass of ftplib.error_reply,
so diagnose_storage() CWD failures escaped the handler and prevented
STOR from ever executing — causing 100% upload failure and HTTP 500s
on /api/v1/archives/{id}/reprint and /api/v1/library/files/{id}/print.
FTP fixes:
- Remove diagnose_storage() from upload hot path
- Change all except (OSError, ftplib.error_reply) to
except (OSError, ftplib.Error) across bambu_ftp.py
Exception handling reverts (9 files):
- Revert narrowed except clauses back to except Exception in route
handlers and service code where broad catches are intentional
defensive programming (archive parsing, HTTP clients, 3MF/ZIP
processing, Home Assistant, firmware checks)
- Keep narrow exceptions only where safe (single-op blocks like
int(), file.unlink(), socket.close())
- Remove unused XMLParseError imports from archive.py, threemf_tools.py
Closes#287
Several FTP operations (file browser, timelapse scan, storage info,
cover download, skip objects, etc.) were missing the printer_model
parameter. Without it, A1/A1 Mini and PS1 printers can't use the prot_p/prot_c
auto-detection and fallback logic, causing FTP failures on these models
when the mode cache isn't already populated.
Simplify always-true authEnabled ternary and localSettings truthiness
checks in SettingsPage.tsx. Remove commented-out auth re-setup guard
and its dead _existing_setting/_user_count queries from auth.py.
Add clarifying comments to firmware_check.py api_key logs (model
identifier, not a secret).
Remove vestigial _debug_logging_enabled and _debug_logging_enabled_at
globals from support.py (written but never read; DB is queried directly).
Simplify hue classification in PrintersPage.tsx and colors.ts by removing
always-true h < 345 checks and dead 'Unknown' fallbacks. Narrow
getWifiStrength param type to remove always-false null guard.
- Remove 28 unused imports across 22 test files
- Prefix 4 unused local variables with _ in app code
(archives, bambu_mqtt, main) and remove 1 dead store
- Consolidate import/import-from in test_plate_detection.py
- Fix unreachable statement in test_archive_service.py
- Simplify redundant comparison in timelapse_processor.py
Resolves ~50 CodeQL py/unused-import, py/unused-local-variable,
py/import-and-import-from, py/unreachable-statement, and
py/redundant-comparison findings.
These modules were already imported at the top of each file.
Removes re-imports of re, json, zipfile, and logging from
inside functions in archive.py, library.py, main.py,
printers.py, support.py, and test_library_api.py.
Resolves all 30 CodeQL py/repeated-import findings.
MD5 in bambu_mqtt.py is used for AMS tray change detection fingerprinting,
and SHA1 in github_backup.py matches Git's blob hash format. Neither is
used for security purposes, so mark them explicitly to satisfy Bandit B303
and CodeQL py/weak-cryptographic-algorithm findings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace number inputs with select dropdowns for retry attempts,
retry delay, and connection timeout to avoid auto-save race conditions
- Move connection timeout inside the FTP retry toggle section
- Add ftp_timeout to backend settings schema and integer parsing list
so the value actually persists (was silently dropped before)
Closes#275
Queue items were being marked as "expired" if older than 24 hours,
which breaks legitimate use cases like weekend print queues or
printers that are offline for extended periods.
Replace xml.etree.ElementTree with defusedxml in test files to satisfy
Bandit B314 scanner. While test XML is trusted, using defusedxml
consistently across the codebase prevents CI failures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix defusedxml import style in print_queue.py to be recognized by Bandit
(use `import defusedxml.ElementTree as ET` not `from defusedxml import`)
- Update Trivy scanner version from 0.65.0 to 0.69.1
Security scan (Bandit) identified vulnerable XML parsing in 3MF file
processing. The standard xml.etree.ElementTree is vulnerable to XXE
(XML External Entity) attacks.
Changes:
- Add defusedxml>=0.7.0 to requirements.txt
- Replace all xml.etree.ElementTree imports with defusedxml.ElementTree
in production code (6 files)
Affected files:
- backend/app/services/archive.py
- backend/app/services/print_scheduler.py
- backend/app/api/routes/print_queue.py
- backend/app/api/routes/library.py
- backend/app/api/routes/printers.py
- backend/app/api/routes/archives.py
Test files intentionally left unchanged (test XML is trusted).
The A1 printer's FTP server hangs when Python's storbinary() calls
voidresp() to wait for the server's completion response. This caused
upload timeouts on A1 and A1 Mini printers.
Fix contributed by an A1 user - replaces storbinary() with manual
chunked transfer using transfercmd() + sendall():
- Uses 1MB chunks (CHUNK_SIZE constant) for better throughput
- Sets explicit 120s socket timeout on data connection
- Manually closes connection after transfer, avoiding voidresp() hang
Applied to all printer models since the manual approach is compatible
with X1C/P1S/P1P as well (transfercmd is what storbinary uses internally).