Преглед изворни кода

feat(mqtt): one-shot device identification probe for unknown models

  Logs device.dev_model_name / dev_product_name / dev_id / project_name
  at INFO level once per client session, falling back to device.keys()
  if none of the known fields are present.

  The MQTT push_status carries the model code in device.dev_model_name
  on every message, but nothing in bambu_mqtt.py reads or logs that
  field — so adding a new printer model meant chasing the code through
  either Bambu cloud or a manual mosquitto_sub. A2L (#1684) was the
  case that surfaced this: get_version also failed because the firmware
  disconnected right after request topic subscription, so the support
  bundle had no way to disclose the model.

  INFO level so the line lands in support bundles without enabling
  debug. One-shot via _device_id_logged, mirroring the existing
  _nozzle_fields_logged flag at line 2095, so push_status spam is
  avoided.

  Future-proofs against Bambu renaming the field (the fallback dumps
  device.keys() so a rename like model_name without the dev_ prefix
  is still observable). 3 unit tests in TestDeviceIdentificationProbe
  pin all three branches.
maziggy пре 2 месеци
родитељ
комит
e01d3a7979
3 измењених фајлова са 83 додато и 0 уклоњено
  1. 2 0
      CHANGELOG.md
  2. 20 0
      backend/app/services/bambu_mqtt.py
  3. 61 0
      backend/tests/unit/services/test_bambu_mqtt.py

Разлика између датотеке није приказан због своје велике величине
+ 2 - 0
CHANGELOG.md


+ 20 - 0
backend/app/services/bambu_mqtt.py

@@ -2100,6 +2100,26 @@ class BambuMQTTClient:
         # bit 8 = 1 → LEFT extruder (active_extruder=1)
         if "device" in data and isinstance(data.get("device"), dict):
             device = data["device"]
+            # One-shot identification probe: surface whatever the firmware uses to
+            # name itself so an unknown model in a support bundle becomes self-
+            # diagnosing. INFO level so it shows up without debug logging. Falls
+            # back to dumping device.keys() if none of the known fields are present
+            # (so a future Bambu rename like `model_name` is still observable).
+            if not getattr(self, "_device_id_logged", False):
+                id_fields = {
+                    k: device.get(k)
+                    for k in ("dev_model_name", "dev_product_name", "dev_id", "project_name")
+                    if k in device
+                }
+                if id_fields:
+                    logger.info("[%s] Device identification: %s", self.serial_number, id_fields)
+                else:
+                    logger.info(
+                        "[%s] Device identification: no known id fields; device.keys=%s",
+                        self.serial_number,
+                        sorted(device.keys()),
+                    )
+                self._device_id_logged = True
             if "extruder" in device and "state" in device["extruder"]:
                 state_val = device["extruder"]["state"]
                 # Extract bit 8 for extruder position

+ 61 - 0
backend/tests/unit/services/test_bambu_mqtt.py

@@ -2752,6 +2752,67 @@ class TestTrayNowDualNozzleH2DActiveExtruder(_H2DFixtureMixin):
         assert h2d_client.state.tray_now == 128
 
 
+# ---------------------------------------------------------------------------
+# 8. Device identification probe (#1684 enabler)
+# ---------------------------------------------------------------------------
+
+
+class TestDeviceIdentificationProbe:
+    """One-shot INFO log of any device.* identification fields the firmware
+    sends. Lets a new-model support bundle self-disclose the internal model
+    code (e.g. dev_model_name='N2L') without a separate debug build.
+    """
+
+    @pytest.fixture
+    def mqtt_client(self):
+        from backend.app.services.bambu_mqtt import BambuMQTTClient
+
+        return BambuMQTTClient(
+            ip_address="192.168.1.100",
+            serial_number="TEST_PROBE",
+            access_code="12345678",
+        )
+
+    def _device_payload(self, device):
+        return {"print": {"device": device}}
+
+    def test_logs_known_id_fields_once(self, mqtt_client, caplog):
+        import logging
+
+        caplog.set_level(logging.INFO, logger="backend.app.services.bambu_mqtt")
+        mqtt_client._process_message(
+            self._device_payload({"dev_model_name": "N2S", "dev_product_name": "Bambu Lab A1"})
+        )
+        matches = [r for r in caplog.records if "Device identification" in r.getMessage()]
+        assert len(matches) == 1
+        msg = matches[0].getMessage()
+        assert "dev_model_name" in msg and "N2S" in msg
+        assert "dev_product_name" in msg
+
+    def test_one_shot_does_not_repeat(self, mqtt_client, caplog):
+        import logging
+
+        caplog.set_level(logging.INFO, logger="backend.app.services.bambu_mqtt")
+        payload = self._device_payload({"dev_model_name": "N2S"})
+        mqtt_client._process_message(payload)
+        mqtt_client._process_message(payload)
+        mqtt_client._process_message(payload)
+        matches = [r for r in caplog.records if "Device identification" in r.getMessage()]
+        assert len(matches) == 1
+
+    def test_fallback_dumps_keys_when_no_known_fields(self, mqtt_client, caplog):
+        """Future Bambu rename (e.g. model_name without dev_ prefix) still surfaces."""
+        import logging
+
+        caplog.set_level(logging.INFO, logger="backend.app.services.bambu_mqtt")
+        mqtt_client._process_message(self._device_payload({"model_name": "MysteryModel", "extruder": {"state": 0}}))
+        matches = [r for r in caplog.records if "Device identification" in r.getMessage()]
+        assert len(matches) == 1
+        msg = matches[0].getMessage()
+        assert "no known id fields" in msg
+        assert "model_name" in msg and "extruder" in msg
+
+
 # ---------------------------------------------------------------------------
 # 8. H2D Full multi-message sequences
 # ---------------------------------------------------------------------------

Неке датотеке нису приказане због велике количине промена