mirror of
https://github.com/home-assistant/core.git
synced 2026-10-06 06:15:47 -04:00
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:
co-authored by
Claude Opus 5.5
parent
ff95dab828
commit
c38c31e582
@@ -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]
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user