diff --git a/backend/app/services/bambu_mqtt.py b/backend/app/services/bambu_mqtt.py index 1f75f79e5..8d540b3cb 100644 --- a/backend/app/services/bambu_mqtt.py +++ b/backend/app/services/bambu_mqtt.py @@ -1494,6 +1494,8 @@ class BambuMQTTClient: # True once the printer has sent `cfg`; from then on AMS Filament Backup # is read from cfg only, never from home_flag (#3259). self._backup_cfg_seen: bool = False + # Last home_flag written by the #3259 probe log below. + self._backup_home_flag_logged: int | None = None # Track last requested tray ID for H2D dual-nozzle printers # H2D only reports slot number (0-3) in tray_now, not global tray ID @@ -2339,6 +2341,25 @@ class BambuMQTTClient: new_backup = parse_ams_filament_backup_from_cfg(print_data["cfg"]) elif not self._backup_cfg_seen and len(print_data) > 30: new_backup = parse_ams_filament_backup_from_home_flag(print_data.get("home_flag")) + # Probe for #3259: does bit 10 stay right in the small update + # frames these printers send between full reports? Logged on + # every change, so a heartbeat that clears bit 10 shows up as + # a flip. Remove once a P1S capture answers it. + home_flag = print_data.get("home_flag") + if ( + not self._backup_cfg_seen + and isinstance(home_flag, int) + and not isinstance(home_flag, bool) + and home_flag != self._backup_home_flag_logged + ): + self._backup_home_flag_logged = home_flag + logger.debug( + "[%s] home_flag probe: 0x%08X bit10=%d keys=%d", + self.serial_number, + home_flag & 0xFFFFFFFF, + (home_flag >> 10) & 1, + len(print_data), + ) if new_backup is not None and new_backup != self.state.ams_filament_backup: hold_start = self._xcam_hold_start.get("print_option_auto_switch_filament") if hold_start is not None and (time.time() - hold_start) <= self._xcam_hold_time: diff --git a/backend/tests/unit/services/test_bambu_mqtt.py b/backend/tests/unit/services/test_bambu_mqtt.py index 0e6175545..98a42c06e 100644 --- a/backend/tests/unit/services/test_bambu_mqtt.py +++ b/backend/tests/unit/services/test_bambu_mqtt.py @@ -7269,6 +7269,27 @@ class TestCommandAckIsNotTelemetry: assert mqtt_client.state.ams_filament_backup is True + def test_home_flag_probe_logs_each_change_with_frame_size(self, mqtt_client, caplog): + """#3259 probe: small frames are logged too, but only when home_flag changes.""" + caplog.set_level(logging.DEBUG, logger="backend.app.services.bambu_mqtt") + + mqtt_client._process_message(self._full_status(home_flag=7554719)) # bit10=1 + mqtt_client._process_message({"print": {"command": "push_status", "home_flag": 7554719}}) + mqtt_client._process_message({"print": {"command": "push_status", "home_flag": -1024}}) + + probes = [r.getMessage() for r in caplog.records if "home_flag probe" in r.getMessage()] + assert len(probes) == 2 + assert "0x0073469F bit10=1 keys=32" in probes[0] + assert "0xFFFFFC00 bit10=1 keys=2" in probes[1] + + def test_home_flag_probe_silent_once_printer_sent_cfg(self, mqtt_client, caplog): + caplog.set_level(logging.DEBUG, logger="backend.app.services.bambu_mqtt") + + mqtt_client._process_message(self._full_status(cfg="C0340FC219")) + mqtt_client._process_message({"print": {"command": "push_status", "home_flag": 0}}) + + assert not [r for r in caplog.records if "home_flag probe" in r.getMessage()] + def test_project_file_ack_does_not_clear_timelapse_state(self, mqtt_client): """The ack echoes the per-job timelapse request, not the recorder.""" mqtt_client.state.timelapse = True