From 023ca4517f6dab52e23e9dcf3c8cf0cd9261c870 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 27 Aug 2026 15:25:17 +0200 Subject: [PATCH] Stop deriving PrusaLink job timestamps after the print ends (#180402) --- homeassistant/components/prusalink/sensor.py | 11 +++- tests/components/prusalink/test_sensor.py | 60 ++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/prusalink/sensor.py b/homeassistant/components/prusalink/sensor.py index d12db25aa33b..41ee6caab873 100644 --- a/homeassistant/components/prusalink/sensor.py +++ b/homeassistant/components/prusalink/sensor.py @@ -48,6 +48,13 @@ class PrusaLinkSensorEntityDescription[ value_fn: Callable[[T], datetime | StateType] +# Both job timestamps are derived from the wall clock, so they only hold while +# the job is actually progressing. Once it ends the printer keeps reporting the +# job with a frozen printing time and nothing remaining, which would drift the +# start forward and pin the finish to "now". +JOB_IN_PROGRESS_STATES = {PrinterState.PRINTING.value, PrinterState.PAUSED.value} + + SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { "status": ( PrusaLinkSensorEntityDescription[PrinterStatus]( @@ -195,7 +202,7 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { ), available_fn=lambda data: ( data.get("time_printing") is not None - and data.get("state") != PrinterState.IDLE.value + and data.get("state") in JOB_IN_PROGRESS_STATES ), ), PrusaLinkSensorEntityDescription[JobInfo]( @@ -212,7 +219,7 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { ), available_fn=lambda data: ( data.get("time_remaining") is not None - and data.get("state") != PrinterState.IDLE.value + and data.get("state") in JOB_IN_PROGRESS_STATES ), ), ), diff --git a/tests/components/prusalink/test_sensor.py b/tests/components/prusalink/test_sensor.py index 454e80f6cc87..50c3c4c6e74b 100644 --- a/tests/components/prusalink/test_sensor.py +++ b/tests/components/prusalink/test_sensor.py @@ -18,6 +18,7 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, REVOLUTIONS_PER_MINUTE, + STATE_UNAVAILABLE, Platform, UnitOfLength, UnitOfTemperature, @@ -365,3 +366,62 @@ async def test_min_extrusion_temp_not_created_when_absent( hass.states.get("sensor.workshop_mock_title_minimum_extrusion_temperature") is None ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_job_timestamps_not_derived_after_the_print( + hass: HomeAssistant, + mock_config_entry, + mock_api, + mock_get_status_printing, + mock_job_api_printing, + mock_job_api_attention, +) -> None: + """Test the job timestamps stop being derived once the print has ended.""" + # A finished print keeps reporting the job while the printer waits for the + # part to be removed, with the printing time frozen and nothing remaining. + mock_job_api_printing["time_remaining"] = 0 + + with patch( + "homeassistant.components.prusalink.sensor.utcnow", + return_value=datetime(2022, 8, 27, 14, 0, 0, tzinfo=UTC), + ): + assert await async_setup_component(hass, DOMAIN, {}) + + state = hass.states.get("sensor.workshop_mock_title") + assert state is not None + assert state.state == "attention" + + # Deriving these from the wall clock now only produces a drifting start and + # a finish that is forever "just now". + state = hass.states.get("sensor.workshop_mock_title_print_start") + assert state is not None + assert state.state == STATE_UNAVAILABLE + + state = hass.states.get("sensor.workshop_mock_title_print_finish") + assert state is not None + assert state.state == STATE_UNAVAILABLE + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_job_timestamps_kept_while_paused( + hass: HomeAssistant, + mock_config_entry, + mock_api, + mock_get_status_printing, + mock_job_api_paused, +) -> None: + """Test the job timestamps stay available while the job is paused.""" + with patch( + "homeassistant.components.prusalink.sensor.utcnow", + return_value=datetime(2022, 8, 27, 14, 0, 0, tzinfo=UTC), + ): + assert await async_setup_component(hass, DOMAIN, {}) + + state = hass.states.get("sensor.workshop_mock_title_print_start") + assert state is not None + assert state.state == "2022-08-27T01:46:53+00:00" + + state = hass.states.get("sensor.workshop_mock_title_print_finish") + assert state is not None + assert state.state == "2022-08-28T10:17:00+00:00"