diff --git a/NIGHTLY_CHANGELOG.md b/NIGHTLY_CHANGELOG.md index 7ae28db..2916255 100644 --- a/NIGHTLY_CHANGELOG.md +++ b/NIGHTLY_CHANGELOG.md @@ -3,4 +3,4 @@ - Fix: on printers with multiple daisy-chained ACE units and no toolhead buffer (e.g. Kobra S1 with 2 ACE Pro), only the first unit's 4 slots were ever shown on the dashboard or synced to OrcaSlicer — the bridge silently discarded every ACE unit after the first. All units now show up correctly (Issue #95, thanks @hoovercl for the detailed report and logs) - Feat: the GCode browser now supports multi-select and bulk delete — click any file's checkbox to enter select mode, use "Select All" to grab everything currently visible (respecting your active search/filter), then delete the selection with one confirmation instead of one-by-one (Issue #94, thanks @Blaim) - Fix: **printing via OrcaSlicer "Upload and print" failed (or fed the wrong spool) when a slot below the used filament was empty** — e.g. printing with Filament 4 while slot 3 was empty. The generated AMS slot mapping inserted a placeholder pointing at the physically empty tray, which the printer rejects. Placeholders now point at a loaded tray instead. Printing with all slots full already worked and is unchanged. -- Fix: manually assigning a filament profile to an ACE slot could crash the MQTT callback (`'list' object has no attribute 'get'`) when the printer rejected the assignment, e.g. for custom-RFID/third-party filament. The bridge now handles the printer's failure response cleanly instead of crashing (Issue #100, thanks @Blaim) +- Fix: manually assigning a filament profile to an ACE slot could crash the MQTT callback (`'list' object has no attribute 'get'`) when the printer rejected the assignment, e.g. for custom-RFID/third-party filament. The bridge now handles the printer's failure response cleanly instead of crashing, and the log now correlates a rejected assignment with the request that triggered it (slot/type/color) so a rejection reason can be diagnosed from bridge logs alone (Issue #100, thanks @Blaim) diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 81a5a1b..158ab6c 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -989,6 +989,7 @@ class KobraXBridge: except Exception: self._visible_vendors = [] self._last_state: dict = {} + self._last_ams_set_request: dict | None = None self._state = { "nozzle_temp": 0.0, "nozzle_target": 0.0, @@ -1880,7 +1881,10 @@ class KobraXBridge: def _on_multicolor_box(self, payload: dict): if payload.get("state") == "failed": - log.warning(f"multiColorBox/report failed: {payload.get('data')}") + req = getattr(self, "_last_ams_set_request", None) + log.warning( + f"multiColorBox setInfo rejected by printer: request={req} raw_response={payload.get('data')}" + ) self._state["last_ams_set_error"] = True return data = payload.get("data") or {} @@ -4046,6 +4050,10 @@ class KobraXBridge: box_id, local_slot = self._global_to_box_slot(index) loop = asyncio.get_event_loop() self._state["last_ams_set_error"] = False + # Remembered so a later state="failed" report (which carries no slot + # info of its own, see _on_multicolor_box) can be logged alongside the + # request that triggered it - otherwise the failure is unattributable. + self._last_ams_set_request = {"global": index, "box": box_id, "local_slot": local_slot, "type": mat, "color": color} # setInfo goes via the web/printer topic (like tempature/set). Verified via # Workbench-Vue mqtt_setInfo verifiziert — via slicer/printer/ wurden # slot changes are ignored by the printer and overwritten with the old diff --git a/tests/test_multicolor_box_failed_state.py b/tests/test_multicolor_box_failed_state.py index 16fd71b..3e3654e 100644 --- a/tests/test_multicolor_box_failed_state.py +++ b/tests/test_multicolor_box_failed_state.py @@ -67,3 +67,15 @@ def test_non_dict_data_without_failed_state_does_not_crash(): even if the printer doesn't set state="failed" for it.""" b = _bridge() b._on_multicolor_box({"state": "success", "data": ["multi_color_box", []]}) + + +def test_failed_report_is_logged_with_the_triggering_request(caplog): + """The failure payload alone carries no slot/type/color info - the log + must correlate it with the setInfo request that triggered it, otherwise + the failure reason can't be diagnosed from bridge logs alone.""" + import logging + b = _bridge() + b._last_ams_set_request = {"global": 6, "box": 0, "local_slot": 3, "type": "PLA", "color": [33, 39, 33]} + with caplog.at_level(logging.WARNING): + b._on_multicolor_box(FAILED_PAYLOAD) + assert any("global" in r.message and "6" in r.message for r in caplog.records)