mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-08 23:21:58 +02:00
d82f4e032fefdb9269fe906ce50c6311cc3cd784
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aed01f875a |
fix(vp): resolve hostname/FQDN targets in MQTT bridge IP encoding (#1429 follow-up)
Printers added to Bambuddy by hostname/FQDN (e.g. p1s.fritz.box) hit 'invalid IPv4' in _ip_to_uint32_le, so the net.info[*].ip rewrite never armed and BambuStudio Send went straight to the real printer instead of the Bambuddy archive whenever the printer was powered on. Add _resolve_target_to_ipv4(target): IPv4 pass-through, else socket.getaddrinfo(target, family=AF_INET). AF_INET filter is load-bearing because net.info[*].ip is uint32 LE and IPv6 can't round-trip. OSError returns None so a transient DNS failure recovers on the next 30s refresh tick via the existing not-armed throttle. Apply the resolver to both the encode call and the host-interface picker (which also assumes dotted-quad). Armed log line now carries configured->resolved when they differ, so bad-DNS regressions stay legible in 'docker logs'. The unresolvable not-armed reason now names the configured value rather than parroting 'invalid IPv4', distinguishing 'DNS gave a v6 result' from 'user typed garbage'. Root-caused by @Mape6; @TrickShotMLG02 confirmed the FQDN workaround on the same release. Pre-0.2.4 these setups worked by accident because there was no net.info[].ip rewrite at all. |
||
|
|
db1c664fce |
feat(vp): surface why MQTT bridge IP encoding didn't arm (#1429 defensive)
_refresh_ip_encoding had 4 silent early-returns. When the rewrite silently no-op'd on a user's setup, the only signal was the absence of the "armed" INFO line, and diagnosing which path was firing meant grepping the source. Each path now emits one INFO line naming the specific reason. A _not_armed_reason dedup field throttles to one line per state change, so an idle unarmed bridge doesn't spam every 30s refresh tick. Cleared on successful arm so regressions re-emit. Not a fix for #1429 itself — the bridge logic is unchanged; this just turns the silent failure into visible signal so the next "fix didn't work for me" report can be triaged in one round-trip. |
||
|
|
cdc27eb517 |
fix(virtual-printer): #1429 follow-up — auto-resolve VP IP when bind_address is 0.0.0.0
The original #1429 fix's _refresh_ip_encoding early-returned when mqtt_server.bind_address was "0.0.0.0" or empty (the default for VPs created without a dedicated bind IP). On a flat-LAN install that's the typical case, so the encoding never armed, _rewrite_net_info_ips was a no-op on every push, and the slicer kept following the real printer IP to its SD card. @Mape6 reported this on the 2026-06-02 daily that supposedly fixed the bug. New helper _resolve_host_interface_for_target() consults the existing network_utils.find_interface_for_ip() to pick the host interface in the printer's subnet. _refresh_ip_encoding falls back to it when bind_address is unspecified; an explicit bind IP still wins. INFO log line distinguishes the two paths ("armed: ... (bind_address)" vs "(auto-resolved)") so future bundles directly answer which IP the rewrite picked. Tests: 4 new under TestBindAddressAutoResolve — rewrite arms via auto-resolved IP at bind_address=0.0.0.0; stays disabled if no interface matches (no crash); explicit bind_ip still takes precedence; helper returns None defensively when find_interface_for_ip does. |
||
|
|
5d6d928b3f |
fix(virtual-printer): #1429 net.info[*].ip cache leak + mode wire-value rename
#1429 (reported by @TrickShotMLG02, confirmed by @Mape6 on a flat single-LAN that rules out subnet / mDNS-reflector theories): with the physical printer off the slicer's "Send" landed in Bambuddy's archive; once the printer powered on every subsequent "Send" went straight to the printer's SD card and bypassed Bambuddy. Bundle analysis: mape6-before showed clean FTP receive + archive lines, mape6-after had zero FTP attempts to Bambuddy once the printer was online. Cause: mqtt_bridge.py::_resolve_client encoded _target_ip_uint32_le / _vp_ip_uint32_le ONLY on client-identity change and early-returned on every refresh tick when the same client object was still bound. If target_client.ip_address was empty at first bind (DB row stale, or client constructed before SSDP refresh filled it in), the encoding stayed None, the net.info[*].ip rewrite block was skipped, the cache filled with the real printer IP, sticky-key preservation kept the poisoned net value alive across every subsequent incremental push, and the slicer followed the leaked IP. Only Bambuddy-restart-with-printer-off cleared it — the workaround both reporters independently arrived at. Same shape on multi-NIC printers (X1C, H2D Pro): the rewrite only matched entries whose ip equalled _target_ip_uint32_le, so a secondary interface IP Bambuddy never saw would leak through unchanged. Bridge fix: - _resolve_client calls a new _refresh_ip_encoding() on every refresh tick, even when client identity is unchanged; self-heals once ip_address becomes valid. - _refresh_ip_encoding() sweeps the existing _latest_print_state when encoding becomes valid for the first time. Without the sweep, sticky-key preservation keeps the pre-arm poisoned cache alive forever — incremental pushes that don't include net carry the bad value forward. - _rewrite_net_info_ips() rewrites EVERY non-zero net.info[].ip entry that doesn't already equal the VP IP, not just entries matching _target_ip_uint32_le. Multi-NIC printers stop leaking secondary interfaces. Zero-IP placeholders are left alone so "active interface" detection still works. - INFO logging on encoding arm/update and on cache sweep so future bundles directly answer "did the rewrite fire?". Mode wire-value rename (#1429 follow-up, separate confusion source): - Both reporters' support bundles showed mode: immediate while the UI said "Archive"; @TrickShotMLG02 quoted: "I have no idea why it says immediate in the support-info.json file. In the webui the printer is set to archive". UI button "Archive" had always saved immediate, and "Queue" had always saved print_queue. Canonical wire values are now archive / review / queue / proxy, matching the button labels 1:1. - New normalize_vp_mode() + VP_MODE_* constants in models/virtual_printer.py; manager.py normalises on construction so a legacy row read pre-migration still dispatches correctly. - core/database.py::run_migrations rewrites existing virtual_printers and settings rows; idempotent (re-runs are no-ops); identical SQL under SQLite and Postgres. - API routes accept both legacy and canonical on input, normalise before storage. GET /settings/virtual-printer normalises on read so the frontend's mode-button highlight works for stale legacy values. - Three frontend VP components (VirtualPrinterSettings, VirtualPrinterCard, VirtualPrinterAddDialog) switched click handlers and type aliases to canonical; each got its own normalizeMode() helper so a stale-cached settings payload still highlights the right button. Two pre-existing `printer.mode === 'queue' ? 'review'` legacy mappings in VirtualPrinterCard were the source of a test failure caught mid-implementation where the new canonical 'queue' was being mis-aliased back to 'review' and hiding the auto-dispatch + force-color-match toggles. mode handler is NOT the dispatch bug: manager.py::_archive_file (the handler for archive mode) doesn't dispatch to the physical printer. The "files end up on the printer's SD card" symptom was the IP-leak from the bridge cache. The mode rename is purely clarity / support- bundle accuracy. |
||
|
|
597762685c |
fix(virtual-printer): #1558 Send pre-flight + slicer-surface audit bundle
#1558: cached-as-base push_status only forced gcode_state=IDLE while letting the real printer's live-progress fields (mc_percent, stg_cur, layer_num, ...) leak through. Bambu Studio's Send pre-flight read them as busy and refused. The cached branch now overrides the activity-field set the same way it already overrode storage indicators (#1228) and protocol fields. Same bundle ships a multi-round VP audit that found adjacent bugs in the same family: - #1558: cached branch zeroes mc_print_stage / mc_percent / mc_remaining_time / stg / stg_cur / layer_num / total_layer_num / print_error - MQTT auth: per-IP rate-limit (5/60s lockout), hmac.compare_digest, access_code redacted in DEBUG log - FTP cmd_STOR streams chunks to disk + 4 GiB cap (was buffering whole upload) - Sticky-keys allowlist extended with upgrade_state / xcam / hw_switch_state / nozzle_diameter / nozzle_type / online / ams_status - _pending_files cleanup in finally for archive / queue / dispatch handlers - _add_to_print_queue position uses MAX+1 (was hardcoded 1) - DELETE VP removes orphan PendingUpload rows + upload_dir from disk - Per-VP cert regenerates on shared-CA rotation (real signature verification, not DN match) - DHCP target-IP refresh + queue_force_color_match toggle now restart proxy VPs - Per-slicer bridge-response routing (multi-slicer cross-leak fix via sequence_id map) - Child-service readiness barrier (FTP / MQTT / Bind / SSDP) — no false is_running before sockets bind - H2D Pro O1E / O2D model codes added (experimental, needs field confirmation) - FTP passive port range widened 50000-51000; docker-compose + wiki updated - VP refresh_loop crash now unbinds raw_message_handler; tailscale catches asyncio.TimeoutError; SlicerProxyManager lifecycle hardening |
||
|
|
1bb0d4856d |
fix(vp): deep-merge ams on bridge cache so P1S/A1 partial pushes don't nuke AMS (#1387)
Reporter vmhomelab ran a Print Queue VP against a P1S, opened BambuStudio, and saw only the External Spool. Toggling Auto-Dispatch (which restarts the VP) made AMS briefly appear, then it reverted to defaults. Proxy Mode worked fine. The earlier #1371 sticky-keys fix only handled one of two firmware incremental-push shapes: it preserved cached `ams` when the incoming push OMITTED the key entirely. P1S firmware (01.09.01.00) instead sends incrementals with the `ams` key present but the inner `ams.ams` array stripped — `{ams_status: 1, humidity: 2}` rather than `{ams: [...], ams_status: 1}`. To the existing "key present? leave it" check that read as "no need to preserve," so the bridge cache got overwritten with the stripped blob, the slicer's next 1 Hz read saw `ams` with no unit list, and BambuStudio fell back to its "no AMS" default render. Toggling Auto-Dispatch restarted the VP and got a fresh pushall through; the next P1S incremental stripped it again. H2D rarely trips this because its incrementals typically don't carry `ams` at all, so #1371 alone was enough — which is why H2D users (including the project owner) didn't see the bug while P1S/A1 users do. Fix: deep-merge the `ams` key inside the bridge cache. Mirrors the structure Bambuddy itself already does in `bambu_mqtt.py::_handle_ams_data` — scalar fields take the new value, but the `ams.ams` array is merged unit-by-unit by `id`, each unit's `tray` array is merged tray-by-tray by `id`, and units / trays the incremental doesn't mention survive intact from the cached full state. A tray-targeted incremental during a print (`{ams: [{id: 0, tray: [{id: 0, state: 11}]}]}`) now updates that one tray's state without dropping the other trays' tray_type / tray_color. Helper added as `_merge_ams_dict` next to `_ip_to_uint32_le`, called from the existing sticky-keys block when both prev and new carry the `ams` key as dicts. Other sticky keys (vt_tray, net, ipcam, lights_report, ams_extruder_map, mapping) keep the prior absent-only preservation; only `ams` has the multi-shape partial problem worth the merge complexity. |
||
|
|
072b8c8ce1 |
fix(vp): preserve AMS/vt_tray/net across incremental push_status updates (#1371)
The bridge cache replaced _latest_print_state wholesale on every push_status arrival. Bambu firmware sends full pushall responses (with AMS/vt_tray/net.info/lights_report) on reconnect / pushall requests, but ~1 Hz incremental updates with only the fields that changed. The first incremental push after a pushall therefore wiped AMS info from the bridge cache, and slicers reading the cache (via the VP's 1 Hz status push) saw a stripped-down state with no AMS visible until the next pushall — typically only on a manual printer power-cycle. Preserve a small set of slicer-visible sticky keys from the previous cache when the incoming push doesn't carry them: ams, vt_tray, ams_extruder_map, mapping, net, ipcam, lights_report. Mirrors the same pattern Bambuddy uses for its own internal state.raw_data. |
||
|
|
7dea33d0d8 |
feat(vp): mirror live target printer state to slicer in non-proxy modes
In non-proxy VP modes (Immediate / Review / Print Queue), the slicer now sees real AMS / FTS / nozzle / k-profile state from the target printer and streams the live camera — full slicer-as-remote functionality without giving up Bambuddy's queue / archive / dispatch features. Architecture (cached-as-base, single source of truth). The bridge caches the latest real push_status and info.get_version response from Bambuddy's existing per-printer MQTT subscription — no second session on the printer, firmware in-flight budget unaffected (#1164). _send_status_report serves a near-byte-identical copy of the cached push with only the upload-state- machine fields overridden. Command responses (extrusion_cali_get, AMS write acks, xcam) fan out raw — they carry sequence_ids the slicer is waiting on. Slicer-issued commands forward to the printer except project_file / gcode_file, which still terminate locally because the file lives on Bambuddy. Camera is a raw TCPProxy on bind_ip:322 → printer:322, same approach proxy mode uses. Field-shape gotchas pinned in the bridge module's docstring and the new test file: - Real Bambu pushes use json.dumps(indent=4) wire format. Compact JSON fails BambuStudio's Send pre-flight silently. - net.info[*].ip is the FTP destination IP (little-endian uint32). Without rewriting to the VP bind IP, the slicer FTPs straight to the real printer. - upgrade_state.sn rewritten to VP serial; AMS-hardware sn fields (n3f/0.sn etc.) left alone. - ipcam.rtsp_url passes through unchanged; BambuStudio overrides the URL host with the device IP it bound on, so :322 lands on the VP's TCPProxy. - extrusion_cali_get must forward; answering it locally hides the user's stored per-filament k-profiles. Setup nuance for camera: the VP's access code must match the target printer's because the slicer authenticates RTSPS with whatever access code is in its profile. MQTT and FTP work either way. Tested e2e with BambuStudio and OrcaSlicer against H2D (dual-nozzle, AMS 2 Pro + AMS HT) and X1C across all three non-proxy modes — sync, send, k-profile lookup, AMS configuration from slicer, and live camera all work. Proxy mode is untouched: SlicerProxyManager owns its own proxies and never instantiates SimpleMQTTServer or MQTTBridge. 25 new tests in backend/tests/unit/test_vp_mqtt_bridge.py cover lifecycle, caching, identity / IP rewriting, wire format, slicer→printer routing, and the LE-uint32 IP encoder against the real H2D capture value. |