diff --git a/src/vehicle.py b/src/vehicle.py index f20630e8..669e1a54 100644 --- a/src/vehicle.py +++ b/src/vehicle.py @@ -130,6 +130,7 @@ def __init__( self.charge_polling_min_percent = charge_polling_min_percent self.refresh_mode = RefreshMode.OFF self.previous_refresh_mode = RefreshMode.OFF + self.__polling_phase: PollingPhase | None = None self.__remote_ac_temp: int | None = None self.__remote_ac_running: bool = False self.__remote_heated_seats_front_left_level: int = 0 @@ -710,6 +711,9 @@ def get_topic(self, sub_topic: str) -> str: return f"{self.mqtt_vin_prefix}/{sub_topic}" def __publish_polling_phase(self, phase: PollingPhase) -> None: + if self.__polling_phase == phase: + return + self.__polling_phase = phase self.publisher.publish_str( self.get_topic(mqtt_topics.REFRESH_POLLING_PHASE), phase.value ) diff --git a/tests/mocks/__init__.py b/tests/mocks/__init__.py index 0818d803..9790d3a3 100644 --- a/tests/mocks/__init__.py +++ b/tests/mocks/__init__.py @@ -15,8 +15,10 @@ class MessageCapturingConsolePublisher(ConsolePublisher): def __init__(self, configuration: Configuration) -> None: super().__init__(configuration) self.map: dict[str, Any] = {} + self.publish_count: dict[str, int] = {} @override def internal_publish(self, key: str, value: Any) -> None: self.map[key] = value + self.publish_count[key] = self.publish_count.get(key, 0) + 1 LOG.debug(f"{key}: {value}") diff --git a/tests/test_vehicle_state.py b/tests/test_vehicle_state.py index b1078aaf..e0a47cdd 100644 --- a/tests/test_vehicle_state.py +++ b/tests/test_vehicle_state.py @@ -486,6 +486,45 @@ def test_charging_stop_triggers_after_shutdown_grace(self) -> None: PollingPhase.AFTER_SHUTDOWN.value, ) + def test_polling_phase_not_republished_when_unchanged(self) -> None: + """Calling should_refresh() twice with the same state should only publish the phase once.""" + self.vehicle_state.configure_missing() + self.vehicle_state.set_refresh_mode(RefreshMode.OFF, "test") + self.publisher.map.clear() + self.publisher.publish_count.clear() + + self.vehicle_state.should_refresh() + self.vehicle_state.should_refresh() + + phase_topic = self.get_topic(mqtt_topics.REFRESH_POLLING_PHASE) + assert self.publisher.publish_count.get(phase_topic, 0) == 1 + + def test_polling_phase_republished_after_transition_back(self) -> None: + """A->B->A transition must publish all three phases (not suppress the return to A).""" + self.vehicle_state.configure_missing() + # Start with OFF (phase A) + self.vehicle_state.set_refresh_mode(RefreshMode.OFF, "test") + self.publisher.map.clear() + self.publisher.publish_count.clear() + + # Phase A: OFF + self.vehicle_state.should_refresh() + phase_topic = self.get_topic(mqtt_topics.REFRESH_POLLING_PHASE) + assert self.publisher.map[phase_topic] == PollingPhase.OFF.value + assert self.publisher.publish_count[phase_topic] == 1 + + # Phase B: FORCE (one-shot, reverts to PERIODIC) + self.vehicle_state.set_refresh_mode(RefreshMode.FORCE, "test") + self.vehicle_state.should_refresh() + assert self.publisher.map[phase_topic] == PollingPhase.FORCE.value + assert self.publisher.publish_count[phase_topic] == 2 + + # Phase A again: back to OFF + self.vehicle_state.set_refresh_mode(RefreshMode.OFF, "test") + self.vehicle_state.should_refresh() + assert self.publisher.map[phase_topic] == PollingPhase.OFF.value + assert self.publisher.publish_count[phase_topic] == 3 + @staticmethod def get_topic(sub_topic: str) -> str: return f"/vehicles/{VIN}/{sub_topic}"