Skip to content

Commit 845c9ee

Browse files
authored
Fix Starlink's ever updating uptime (home-assistant#155574)
Signed-off-by: David Rapan <[email protected]>
1 parent dedf6b1 commit 845c9ee

File tree

4 files changed

+87
-14
lines changed

4 files changed

+87
-14
lines changed

homeassistant/components/starlink/coordinator.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def __init__(self, hass: HomeAssistant, config_entry: StarlinkConfigEntry) -> No
7272
def _get_starlink_data(self) -> StarlinkData:
7373
"""Retrieve Starlink data."""
7474
context = self.channel_context
75-
status = status_data(context)
7675
location = location_data(context)
7776
sleep = get_sleep_config(context)
7877
status, obstruction, alert = status_data(context)

homeassistant/components/starlink/sensor.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2929
from homeassistant.helpers.typing import StateType
3030
from homeassistant.util.dt import now
31+
from homeassistant.util.variance import ignore_variance
3132

3233
from .coordinator import StarlinkConfigEntry, StarlinkData
3334
from .entity import StarlinkEntity
@@ -91,6 +92,10 @@ async def async_added_to_hass(self) -> None:
9192
self._attr_native_value = last_native_value
9293

9394

95+
uptime_to_stable_datetime = ignore_variance(
96+
lambda value: now() - timedelta(seconds=value), timedelta(minutes=1)
97+
)
98+
9499
SENSORS: tuple[StarlinkSensorEntityDescription, ...] = (
95100
StarlinkSensorEntityDescription(
96101
key="ping",
@@ -150,9 +155,7 @@ async def async_added_to_hass(self) -> None:
150155
translation_key="last_restart",
151156
device_class=SensorDeviceClass.TIMESTAMP,
152157
entity_category=EntityCategory.DIAGNOSTIC,
153-
value_fn=lambda data: (
154-
now() - timedelta(seconds=data.status["uptime"], milliseconds=-500)
155-
).replace(microsecond=0),
158+
value_fn=lambda data: uptime_to_stable_datetime(data.status["uptime"]),
156159
entity_class=StarlinkSensorEntity,
157160
),
158161
StarlinkSensorEntityDescription(

tests/components/starlink/patchers.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@
99
"homeassistant.components.starlink.async_setup_entry", return_value=True
1010
)
1111

12-
STATUS_DATA_SUCCESS_PATCHER = patch(
13-
"homeassistant.components.starlink.coordinator.status_data",
14-
return_value=json.loads(load_fixture("status_data_success.json", "starlink")),
15-
)
16-
1712
LOCATION_DATA_SUCCESS_PATCHER = patch(
1813
"homeassistant.components.starlink.coordinator.location_data",
1914
return_value=json.loads(load_fixture("location_data_success.json", "starlink")),
@@ -24,6 +19,12 @@
2419
return_value=json.loads(load_fixture("sleep_data_success.json", "starlink")),
2520
)
2621

22+
STATUS_DATA_TARGET = "homeassistant.components.starlink.coordinator.status_data"
23+
STATUS_DATA_FIXTURE = json.loads(load_fixture("status_data_success.json", "starlink"))
24+
STATUS_DATA_SUCCESS_PATCHER = patch(
25+
STATUS_DATA_TARGET, return_value=STATUS_DATA_FIXTURE
26+
)
27+
2728
HISTORY_STATS_SUCCESS_PATCHER = patch(
2829
"homeassistant.components.starlink.coordinator.history_stats",
2930
return_value=json.loads(load_fixture("history_stats_success.json", "starlink")),

tests/components/starlink/test_init.py

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
"""Tests Starlink integration init/unload."""
22

3+
from copy import deepcopy
4+
from datetime import datetime, timedelta
35
from unittest.mock import patch
46

7+
from freezegun import freeze_time
8+
59
from homeassistant.components.starlink.const import DOMAIN
610
from homeassistant.config_entries import ConfigEntryState
711
from homeassistant.const import CONF_IP_ADDRESS
812
from homeassistant.core import HomeAssistant, State
13+
from homeassistant.util import dt as dt_util
914

1015
from .patchers import (
1116
HISTORY_STATS_SUCCESS_PATCHER,
1217
LOCATION_DATA_SUCCESS_PATCHER,
1318
SLEEP_DATA_SUCCESS_PATCHER,
19+
STATUS_DATA_FIXTURE,
1420
STATUS_DATA_SUCCESS_PATCHER,
21+
STATUS_DATA_TARGET,
1522
)
1623

17-
from tests.common import MockConfigEntry, mock_restore_cache_with_extra_data
24+
from tests.common import (
25+
MockConfigEntry,
26+
async_fire_time_changed,
27+
mock_restore_cache_with_extra_data,
28+
)
1829

1930

2031
async def test_successful_entry(hass: HomeAssistant) -> None:
@@ -25,9 +36,9 @@ async def test_successful_entry(hass: HomeAssistant) -> None:
2536
)
2637

2738
with (
28-
STATUS_DATA_SUCCESS_PATCHER,
2939
LOCATION_DATA_SUCCESS_PATCHER,
3040
SLEEP_DATA_SUCCESS_PATCHER,
41+
STATUS_DATA_SUCCESS_PATCHER,
3142
HISTORY_STATS_SUCCESS_PATCHER,
3243
):
3344
entry.add_to_hass(hass)
@@ -48,9 +59,9 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
4859
)
4960

5061
with (
51-
STATUS_DATA_SUCCESS_PATCHER,
5262
LOCATION_DATA_SUCCESS_PATCHER,
5363
SLEEP_DATA_SUCCESS_PATCHER,
64+
STATUS_DATA_SUCCESS_PATCHER,
5465
HISTORY_STATS_SUCCESS_PATCHER,
5566
):
5667
entry.add_to_hass(hass)
@@ -65,7 +76,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
6576

6677

6778
async def test_restore_cache_with_accumulation(hass: HomeAssistant) -> None:
68-
"""Test configuring Starlink."""
79+
"""Test Starlink accumulation."""
6980
entry = MockConfigEntry(
7081
domain=DOMAIN,
7182
data={CONF_IP_ADDRESS: "1.2.3.4:0000"},
@@ -89,9 +100,9 @@ async def test_restore_cache_with_accumulation(hass: HomeAssistant) -> None:
89100
)
90101

91102
with (
92-
STATUS_DATA_SUCCESS_PATCHER,
93103
LOCATION_DATA_SUCCESS_PATCHER,
94104
SLEEP_DATA_SUCCESS_PATCHER,
105+
STATUS_DATA_SUCCESS_PATCHER,
95106
HISTORY_STATS_SUCCESS_PATCHER,
96107
):
97108
entry.add_to_hass(hass)
@@ -112,3 +123,62 @@ async def test_restore_cache_with_accumulation(hass: HomeAssistant) -> None:
112123
await entry.runtime_data.async_refresh()
113124

114125
assert hass.states.get(entity_id).state == str(1 + 0.01572462736977)
126+
127+
128+
async def test_last_restart_state(hass: HomeAssistant) -> None:
129+
"""Test Starlink last restart state."""
130+
entry = MockConfigEntry(
131+
domain=DOMAIN,
132+
data={CONF_IP_ADDRESS: "1.2.3.4:0000"},
133+
)
134+
entity_id = "sensor.starlink_last_restart"
135+
utc_now = datetime.fromisoformat("2025-10-22T13:31:29+00:00")
136+
137+
with (
138+
LOCATION_DATA_SUCCESS_PATCHER,
139+
SLEEP_DATA_SUCCESS_PATCHER,
140+
STATUS_DATA_SUCCESS_PATCHER,
141+
HISTORY_STATS_SUCCESS_PATCHER,
142+
):
143+
with freeze_time(utc_now):
144+
entry.add_to_hass(hass)
145+
146+
await hass.config_entries.async_setup(entry.entry_id)
147+
await hass.async_block_till_done()
148+
149+
assert hass.states.get(entity_id).state == "2025-10-13T06:09:11+00:00"
150+
151+
with patch.object(entry.runtime_data, "always_update", return_value=True):
152+
status_data = deepcopy(STATUS_DATA_FIXTURE)
153+
status_data[0]["uptime"] = 804144
154+
155+
with (
156+
freeze_time(utc_now + timedelta(seconds=5)),
157+
patch(STATUS_DATA_TARGET, return_value=status_data),
158+
):
159+
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5))
160+
await hass.async_block_till_done(wait_background_tasks=True)
161+
162+
assert hass.states.get(entity_id).state == "2025-10-13T06:09:11+00:00"
163+
164+
status_data[0]["uptime"] = 804134
165+
166+
with (
167+
freeze_time(utc_now + timedelta(seconds=10)),
168+
patch(STATUS_DATA_TARGET, return_value=status_data),
169+
):
170+
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
171+
await hass.async_block_till_done(wait_background_tasks=True)
172+
173+
assert hass.states.get(entity_id).state == "2025-10-13T06:09:11+00:00"
174+
175+
status_data[0]["uptime"] = 100
176+
177+
with (
178+
freeze_time(utc_now + timedelta(seconds=15)),
179+
patch(STATUS_DATA_TARGET, return_value=status_data),
180+
):
181+
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15))
182+
await hass.async_block_till_done(wait_background_tasks=True)
183+
184+
assert hass.states.get(entity_id).state == "2025-10-22T13:30:04+00:00"

0 commit comments

Comments
 (0)