Keep WeatherFlow last lightning strike values across restarts (#183124)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Ehn
2026-09-30 15:30:51 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent ff95dab828
commit c38c31e582
3 changed files with 111 additions and 7 deletions
+22 -5
View File
@@ -16,8 +16,8 @@ from pyweatherflowudp.device import (
)
from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
@@ -61,13 +61,18 @@ class WeatherFlowSensorEntityDescription(SensorEntityDescription):
raw_data_conv_fn: Callable[[Any], datetime | StateType]
device_attr: str | None = None
restore_last_value: bool = False
# No value means there is nothing to report, not that the device is unavailable.
none_is_unknown: bool = False
event_subscriptions: list[str] = field(default_factory=lambda: [EVENT_OBSERVATION])
def get_raw_value(self, device: WeatherFlowDevice) -> Any:
"""Return the sensor value as reported by the device."""
return getattr(device, self.device_attr or self.key)
def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType:
"""Return the parsed sensor value."""
if (raw_sensor_data := getattr(device, self.device_attr or self.key)) is None:
if (raw_sensor_data := self.get_raw_value(device)) is None:
return None
return self.raw_data_conv_fn(raw_sensor_data)
@@ -161,6 +166,7 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = (
WeatherFlowSensorEntityDescription(
key="lightning_strike_last_distance",
device_attr="last_lightning_strike_event",
restore_last_value=True,
translation_key="lightning_strike_last_distance",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.DISTANCE,
@@ -175,6 +181,7 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = (
WeatherFlowSensorEntityDescription(
key="lightning_strike_last_energy",
device_attr="last_lightning_strike_event",
restore_last_value=True,
translation_key="lightning_strike_last_energy",
state_class=SensorStateClass.MEASUREMENT,
none_is_unknown=True,
@@ -184,6 +191,7 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = (
WeatherFlowSensorEntityDescription(
key="lightning_strike_last_epoch",
device_attr="last_lightning_strike_event",
restore_last_value=True,
translation_key="lightning_strike_last_epoch",
device_class=SensorDeviceClass.TIMESTAMP,
none_is_unknown=True,
@@ -358,7 +366,7 @@ async def async_setup_entry(
)
class WeatherFlowSensorEntity(SensorEntity):
class WeatherFlowSensorEntity(RestoreSensor):
"""Defines a WeatherFlow sensor entity."""
entity_description: WeatherFlowSensorEntityDescription
@@ -404,8 +412,17 @@ class WeatherFlowSensorEntity(SensorEntity):
@override
async def async_added_to_hass(self) -> None:
"""Subscribe to events."""
self._async_update_state()
"""Restore the last value if configured, then subscribe to events."""
if (
self.entity_description.restore_last_value
and self.entity_description.get_raw_value(self.device) is None
and (last_sensor_data := await self.async_get_last_sensor_data())
and last_sensor_data.native_value is not None
):
# Strikes are only reported as events, so keep the last one.
self._attr_native_value = last_sensor_data.native_value
else:
self._async_update_state()
for event in self.entity_description.event_subscriptions:
self.async_on_remove(
self.device.on(event, lambda _: self._async_update_state())
@@ -0,0 +1,6 @@
{
"serial_number": "ST-00000001",
"type": "evt_strike",
"hub_sn": "HB-00000001",
"evt": [1510855990, 27, 3848]
}
+83 -2
View File
@@ -7,7 +7,7 @@ from pyweatherflowudp.aioudp import LocalEndpoint
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers import entity_registry as er
from homeassistant.util.unit_system import (
METRIC_SYSTEM,
@@ -17,10 +17,16 @@ from homeassistant.util.unit_system import (
from . import HUB_ADDRESS, setup_integration
from tests.common import MockConfigEntry, load_fixture_bytes, snapshot_platform
from tests.common import (
MockConfigEntry,
load_fixture_bytes,
mock_restore_cache_with_extra_data,
snapshot_platform,
)
LAST_STRIKE_DISTANCE = "sensor.st_00000001_lightning_last_distance"
LAST_STRIKE_ENERGY = "sensor.st_00000001_lightning_last_energy"
LAST_STRIKE_TIME = "sensor.st_00000001_lightning_last_strike"
RAIN_LAST_MINUTE = "sensor.st_00000001_precipitation"
STATION_PRESSURE = "sensor.st_00000001_air_pressure"
VAPOR_PRESSURE = "sensor.st_00000001_vapor_pressure"
@@ -67,6 +73,51 @@ async def test_unit_system(
assert hass.states.get(entity_id).attributes[ATTR_UNIT_OF_MEASUREMENT] == unit
async def test_last_strike_restored_until_next_strike(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_udp_endpoint: LocalEndpoint,
) -> None:
"""Test the last strike is restored after a restart until a new strike."""
mock_restore_cache_with_extra_data(
hass,
[
(
State(LAST_STRIKE_DISTANCE, "5"),
{"native_value": 5, "native_unit_of_measurement": "km"},
),
(
State(LAST_STRIKE_ENERGY, "1234"),
{"native_value": 1234, "native_unit_of_measurement": None},
),
(
State(LAST_STRIKE_TIME, "2026-09-23T23:13:10+00:00"),
{
"native_value": {
"__type": "<class 'datetime.datetime'>",
"isoformat": "2026-09-23T23:13:10+00:00",
},
"native_unit_of_measurement": None,
},
),
],
)
await setup_integration(hass, mock_config_entry, mock_udp_endpoint)
assert hass.states.get(LAST_STRIKE_DISTANCE).state == "5"
assert hass.states.get(LAST_STRIKE_ENERGY).state == "1234"
assert hass.states.get(LAST_STRIKE_TIME).state == "2026-09-23T23:13:10+00:00"
mock_udp_endpoint.feed_datagram(
load_fixture_bytes("evt_strike.json", "weatherflow"), HUB_ADDRESS
)
await hass.async_block_till_done()
assert hass.states.get(LAST_STRIKE_DISTANCE).state == "27"
assert hass.states.get(LAST_STRIKE_ENERGY).state == "3848"
assert hass.states.get(LAST_STRIKE_TIME).state == "2017-11-16T18:13:10+00:00"
async def test_last_strike_out_of_range(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
@@ -82,3 +133,33 @@ async def test_last_strike_out_of_range(
assert hass.states.get(LAST_STRIKE_DISTANCE).state == STATE_UNKNOWN
assert hass.states.get(LAST_STRIKE_ENERGY).state == "3848"
async def test_last_strike_out_of_range_not_restored(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_udp_endpoint: LocalEndpoint,
) -> None:
"""Test a strike cached before the sensors are added isn't replaced on restore."""
mock_restore_cache_with_extra_data(
hass,
[
(
State(LAST_STRIKE_DISTANCE, "5"),
{"native_value": 5, "native_unit_of_measurement": "km"},
),
],
)
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# The sensors are added once the device status and an observation have arrived.
for fixture in ("evt_strike_out_of_range.json", "device.json", "obs_st.json"):
mock_udp_endpoint.feed_datagram(
load_fixture_bytes(fixture, "weatherflow"), HUB_ADDRESS
)
await hass.async_block_till_done()
assert hass.states.get(LAST_STRIKE_DISTANCE).state == STATE_UNKNOWN
assert hass.states.get(LAST_STRIKE_ENERGY).state == "3848"