Explorar el Código

revert(printers): remove SD card badge entirely

  Four attempts at making the printer-card SD badge stable on H2D all failed:
  the final straw was powering on an A1 causing every connected H2D to flip to
  red simultaneously. Bambu firmware SD signaling is not reliably derivable
  from MQTT — the legacy `sdcard` field is sporadic and inconsistently typed,
  and home_flag bits 8-9 are cleared on heartbeat pushes regardless of card
  state with no clean way to distinguish heartbeats from full status reports.

  Remove the badge from the Printers page card and the Printer Info modal,
  drop `sdcard` from the frontend PrinterStatus type, and strip all home_flag
  derivation and heartbeat-handling code from the MQTT parser.

  `state.sdcard` is retained on the backend and populated only from a plain
  truthy read of the `sdcard` field, because firmware_update.py uses it as a
  precondition before starting firmware installs.
maziggy hace 1 mes
padre
commit
899c2c6480

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 2
CHANGELOG.md


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

@@ -2263,27 +2263,19 @@ class BambuMQTTClient:
             if home_flag < 0:
             if home_flag < 0:
                 home_flag = home_flag & 0xFFFFFFFF
                 home_flag = home_flag & 0xFFFFFFFF
 
 
-        # A "full" push_status report carries many state fields; heartbeat pushes
-        # are sparse (often just home_flag + a handful of counters). We use this
-        # to decide whether home_flag's SD bits can be trusted as ground truth.
-        _full_push_markers = ("gcode_state", "mc_percent", "nozzle_temper", "print_type", "stg_cur", "ams")
-        _is_full_push = sum(1 for k in _full_push_markers if k in data) >= 2
-
-        # Parse SD card status. H2D (and likely others) emit heartbeat-style pushes
-        # that carry `home_flag` with bits 8-9 cleared even when a card is inserted,
-        # so `home_flag` alone is not reliable. Prefer the legacy top-level `sdcard`
-        # field when present (handling bool/int/string variants), and only consult
-        # `home_flag` bits 8-9 when the push looks like a full status report and
-        # `sdcard` is absent.
-        def _truthy_sdcard(v: object) -> bool:
-            if isinstance(v, str):
-                return "HAS_SDCARD" in v.upper() or v.lower() in ("true", "normal", "1")
-            return bool(v)
-
+        # SD card presence: the only remaining consumer is the firmware-update
+        # precondition check (firmware_update.py). Use the top-level `sdcard`
+        # field when present with a permissive truthy check covering the
+        # bool/int/"HAS_SDCARD_NORMAL" variants real firmware emits. We do NOT
+        # derive this from home_flag — heartbeat pushes clear bits 8-9 even
+        # when a card is inserted, which caused the badge to flap before the
+        # badge was removed entirely.
         if "sdcard" in data:
         if "sdcard" in data:
-            self.state.sdcard = _truthy_sdcard(data["sdcard"])
-        elif home_flag is not None and _is_full_push:
-            self.state.sdcard = ((home_flag >> 8) & 0x3) != 0
+            raw_sdcard = data["sdcard"]
+            if isinstance(raw_sdcard, str):
+                self.state.sdcard = "HAS_SDCARD" in raw_sdcard.upper() or raw_sdcard.lower() in ("true", "normal", "1")
+            else:
+                self.state.sdcard = bool(raw_sdcard)
 
 
         if home_flag is not None:
         if home_flag is not None:
             store_to_sdcard = bool((home_flag >> 11) & 1)
             store_to_sdcard = bool((home_flag >> 11) & 1)

+ 7 - 39
backend/tests/unit/services/test_bambu_mqtt.py

@@ -3577,13 +3577,10 @@ class TestDoorOpenParsing:
 
 
 
 
 class TestSdCardParsing:
 class TestSdCardParsing:
-    """SD-card state is derived from the top-level `sdcard` field when present
-    (firmware may send bool/int/string). `home_flag` bits 8-9 are only consulted
-    on full push_status reports when `sdcard` is absent, because H2D heartbeat
-    pushes carry `home_flag` with those bits cleared regardless of card state."""
-
-    # Markers used by bambu_mqtt to recognize a full push_status payload.
-    FULL_PUSH = {"gcode_state": "IDLE", "mc_percent": 0}
+    """SD-card state is only set from the top-level `sdcard` field (bool/int/
+    string variants). home_flag is NOT consulted — heartbeat pushes clear those
+    bits even when a card is inserted, and the prior badge feature was removed
+    entirely because no reliable heartbeat-vs-full-push heuristic existed."""
 
 
     def _make_client(self, model: str = "H2D"):
     def _make_client(self, model: str = "H2D"):
         from backend.app.services.bambu_mqtt import BambuMQTTClient
         from backend.app.services.bambu_mqtt import BambuMQTTClient
@@ -3595,42 +3592,13 @@ class TestSdCardParsing:
             model=model,
             model=model,
         )
         )
 
 
-    def test_home_flag_bit8_sets_sdcard_true_on_full_push(self):
-        client = self._make_client()
-        client._update_state({"home_flag": 0x00000100, **self.FULL_PUSH})
-        assert client.state.sdcard is True
-
-    def test_home_flag_bit9_sets_sdcard_true_on_full_push(self):
-        client = self._make_client()
-        client._update_state({"home_flag": 0x00000200, **self.FULL_PUSH})
-        assert client.state.sdcard is True
-
-    def test_heartbeat_home_flag_does_not_flip_badge(self):
-        # The actual bug: repeated heartbeat pushes (home_flag only, bits clear)
-        # must NOT downgrade an inserted card. Only full pushes or an explicit
-        # `sdcard` field can change state.
+    def test_home_flag_alone_does_not_touch_sdcard(self):
         client = self._make_client()
         client = self._make_client()
         client.state.sdcard = True
         client.state.sdcard = True
-        for _ in range(10):
-            client._update_state({"home_flag": 0x00000000})
+        for home_flag in (0x00000000, 0x00000100, 0x00000200):
+            client._update_state({"home_flag": home_flag})
         assert client.state.sdcard is True
         assert client.state.sdcard is True
 
 
-    def test_sdcard_field_wins_over_home_flag(self):
-        # The legacy top-level `sdcard` field is reliable; home_flag bits 8-9
-        # are cleared on heartbeats even when a card is inserted.
-        client = self._make_client()
-        client._update_state({"home_flag": 0x00000000, "sdcard": "HAS_SDCARD_NORMAL"})
-        assert client.state.sdcard is True
-        client._update_state({"home_flag": 0x00000100, "sdcard": False})
-        assert client.state.sdcard is False
-
-    def test_full_push_downgrade_when_no_sdcard_field(self):
-        # On a genuine full push with bits clear and no `sdcard` field, we do downgrade.
-        client = self._make_client()
-        client.state.sdcard = True
-        client._update_state({"home_flag": 0x00000000, **self.FULL_PUSH})
-        assert client.state.sdcard is False
-
     def test_sdcard_string_fallback_when_no_home_flag(self):
     def test_sdcard_string_fallback_when_no_home_flag(self):
         client = self._make_client()
         client = self._make_client()
         client._update_state({"sdcard": "HAS_SDCARD_NORMAL"})
         client._update_state({"sdcard": "HAS_SDCARD_NORMAL"})

+ 0 - 1
frontend/src/api/client.ts

@@ -244,7 +244,6 @@ export interface PrinterStatus {
   ams: AMSUnit[];
   ams: AMSUnit[];
   ams_exists: boolean;
   ams_exists: boolean;
   vt_tray: AMSTray[];  // Virtual tray / external spool(s)
   vt_tray: AMSTray[];  // Virtual tray / external spool(s)
-  sdcard: boolean;  // SD card inserted
   store_to_sdcard: boolean;  // Store sent files on SD card
   store_to_sdcard: boolean;  // Store sent files on SD card
   timelapse: boolean;  // Timelapse recording active
   timelapse: boolean;  // Timelapse recording active
   ipcam: boolean;  // Live view enabled
   ipcam: boolean;  // Live view enabled

+ 0 - 16
frontend/src/components/PrinterInfoModal.tsx

@@ -162,22 +162,6 @@ export function PrinterInfoModal({ printer, status, totalPrintHours, onClose }:
     value: printer.nozzle_count,
     value: printer.nozzle_count,
   });
   });
 
 
-  // SD Card
-  if (status?.sdcard != null) {
-    rows.push({
-      label: t('printers.sdCard'),
-      value: (
-        <span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
-          status.sdcard
-            ? 'bg-bambu-green/20 text-bambu-green'
-            : 'bg-bambu-dark-tertiary text-bambu-gray'
-        }`}>
-          {status.sdcard ? t('printers.inserted') : t('printers.notInserted')}
-        </span>
-      ),
-    });
-  }
-
   // Auto-Archive
   // Auto-Archive
   rows.push({
   rows.push({
     label: t('printers.autoArchive'),
     label: t('printers.autoArchive'),

+ 0 - 14
frontend/src/pages/PrintersPage.tsx

@@ -2480,20 +2480,6 @@ function PrinterCard({
                 </span>
                 </span>
               ) : null}
               ) : null}
 
 
-              {/* SD Card Badge */}
-              {status?.connected && (
-                <span
-                  className={`flex items-center px-2 py-1 rounded-full text-xs ${
-                    status.sdcard
-                      ? 'bg-status-ok/20 text-status-ok'
-                      : 'bg-red-500/20 text-red-400'
-                  }`}
-                  title={`${t('printers.sdCard')}: ${status.sdcard ? t('printers.inserted') : t('printers.notInserted')}`}
-                >
-                  <HardDrive className="w-3 h-3" />
-                </span>
-              )}
-
               {/* Enclosure Door Badge (X1/P1S/P2S/H2*) */}
               {/* Enclosure Door Badge (X1/P1S/P2S/H2*) */}
               {status?.connected && ['X1C', 'X1', 'X1E', 'P1S', 'P1P', 'P2S', 'H2D', 'H2D Pro', 'H2C', 'H2S'].includes(printer.model ?? '') && (
               {status?.connected && ['X1C', 'X1', 'X1E', 'P1S', 'P1P', 'P2S', 'H2D', 'H2D Pro', 'H2C', 'H2S'].includes(printer.model ?? '') && (
                 <span
                 <span

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
static/assets/index-Bcx_I7VB.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-Dvdlpt_K.js"></script>
+    <script type="module" crossorigin src="/assets/index-Bcx_I7VB.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-3s5orqQ4.css">
     <link rel="stylesheet" crossorigin href="/assets/index-3s5orqQ4.css">
   </head>
   </head>
   <body>
   <body>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio