Change Ambient Network timestamp updates (#116941)

This commit is contained in:
Thomas Kistler 2024-06-21 07:36:53 -07:00 committed by GitHub
parent f353b3fa54
commit 180c244a78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1954 additions and 53 deletions

View File

@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC from homeassistant.const import CONF_MAC
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
from .const import API_LAST_DATA, DOMAIN, LOGGER from .const import API_LAST_DATA, DOMAIN, LOGGER
from .helper import get_station_name from .helper import get_station_name
@ -24,6 +25,7 @@ class AmbientNetworkDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]])
config_entry: ConfigEntry config_entry: ConfigEntry
station_name: str station_name: str
last_measured: datetime | None = None
def __init__(self, hass: HomeAssistant, api: OpenAPI) -> None: def __init__(self, hass: HomeAssistant, api: OpenAPI) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
@ -47,19 +49,13 @@ class AmbientNetworkDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]])
f"Station '{self.config_entry.title}' did not report any data" f"Station '{self.config_entry.title}' did not report any data"
) )
# Eliminate data if the station hasn't been updated for a while. # Some stations do not report a "created_at" or "dateutc".
if (created_at := last_data.get("created_at")) is None: # See https://github.com/home-assistant/core/issues/116917
raise UpdateFailed( if (ts := last_data.get("created_at")) is not None or (
f"Station '{self.config_entry.title}' did not report a time stamp" ts := last_data.get("dateutc")
) ) is not None:
self.last_measured = datetime.fromtimestamp(
# Eliminate data that has been generated more than an hour ago. The station is ts / 1000, tz=dt_util.DEFAULT_TIME_ZONE
# probably offline.
if int(created_at / 1000) < int(
(datetime.now() - timedelta(hours=1)).timestamp()
):
raise UpdateFailed(
f"Station '{self.config_entry.title}' reported stale data"
) )
return cast(dict[str, Any], last_data) return cast(dict[str, Any], last_data)

View File

@ -299,12 +299,10 @@ class AmbientNetworkSensor(AmbientNetworkEntity, SensorEntity):
mac_address: str, mac_address: str,
) -> None: ) -> None:
"""Initialize a sensor object.""" """Initialize a sensor object."""
super().__init__(coordinator, description, mac_address) super().__init__(coordinator, description, mac_address)
def _update_attrs(self) -> None: def _update_attrs(self) -> None:
"""Update sensor attributes.""" """Update sensor attributes."""
value = self.coordinator.data.get(self.entity_description.key) value = self.coordinator.data.get(self.entity_description.key)
# Treatments for special units. # Treatments for special units.
@ -315,3 +313,8 @@ class AmbientNetworkSensor(AmbientNetworkEntity, SensorEntity):
self._attr_available = value is not None self._attr_available = value is not None
self._attr_native_value = value self._attr_native_value = value
if self.coordinator.last_measured is not None:
self._attr_extra_state_attributes = {
"last_measured": self.coordinator.last_measured
}

View File

@ -3,5 +3,8 @@
"macAddress": "BB:BB:BB:BB:BB:BB", "macAddress": "BB:BB:BB:BB:BB:BB",
"info": { "info": {
"name": "Station B" "name": "Station B"
},
"lastData": {
"tempf": 82.9
} }
} }

View File

@ -3,7 +3,7 @@
"macAddress": "CC:CC:CC:CC:CC:CC", "macAddress": "CC:CC:CC:CC:CC:CC",
"lastData": { "lastData": {
"stationtype": "AMBWeatherPro_V5.0.6", "stationtype": "AMBWeatherPro_V5.0.6",
"dateutc": 1699474320000, "dateutc": 1717687683000,
"tempf": 82.9, "tempf": 82.9,
"dewPoint": 82.0, "dewPoint": 82.0,
"feelsLike": 85.0, "feelsLike": 85.0,

View File

@ -0,0 +1,30 @@
{
"_id": "dddddddddddddddddddddddddddddddd",
"macAddress": "DD:DD:DD:DD:DD:DD",
"lastData": {
"stationtype": "AMBWeatherPro_V5.0.6",
"tempf": 82.9,
"dewPoint": 82.0,
"feelsLike": 85.0,
"humidity": 60,
"windspeedmph": 8.72,
"windgustmph": 9.17,
"maxdailygust": 22.82,
"winddir": 11,
"uv": 0,
"solarradiation": 37.64,
"hourlyrainin": 0,
"dailyrainin": 0,
"weeklyrainin": 0,
"monthlyrainin": 0,
"totalrainin": 26.402,
"baromrelin": 29.586,
"baromabsin": 28.869,
"batt_co2": 1,
"type": "weather-data",
"tz": "America/Chicago"
},
"info": {
"name": "Station D"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
"""Test Ambient Weather Network sensors.""" """Test Ambient Weather Network sensors."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from unittest.mock import patch from unittest.mock import AsyncMock, patch
from aioambient import OpenAPI from aioambient import OpenAPI
from aioambient.errors import RequestError from aioambient.errors import RequestError
@ -9,6 +9,7 @@ from freezegun import freeze_time
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -17,65 +18,47 @@ from .conftest import setup_platform
from tests.common import async_fire_time_changed, snapshot_platform from tests.common import async_fire_time_changed, snapshot_platform
@freeze_time("2023-11-08") @freeze_time("2023-11-9")
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True) @pytest.mark.parametrize(
"config_entry",
["AA:AA:AA:AA:AA:AA", "CC:CC:CC:CC:CC:CC", "DD:DD:DD:DD:DD:DD"],
indirect=True,
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensors( async def test_sensors(
hass: HomeAssistant, hass: HomeAssistant,
open_api: OpenAPI, open_api: OpenAPI,
aioambient, aioambient: AsyncMock,
config_entry, config_entry: ConfigEntry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test all sensors under normal operation.""" """Test all sensors under normal operation."""
await setup_platform(True, hass, config_entry) await setup_platform(True, hass, config_entry)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@freeze_time("2023-11-09")
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
async def test_sensors_with_stale_data(
hass: HomeAssistant, open_api: OpenAPI, aioambient, config_entry
) -> None:
"""Test that the sensors are not populated if the data is stale."""
await setup_platform(False, hass, config_entry)
sensor = hass.states.get("sensor.station_a_absolute_pressure")
assert sensor is None
@freeze_time("2023-11-08")
@pytest.mark.parametrize("config_entry", ["BB:BB:BB:BB:BB:BB"], indirect=True) @pytest.mark.parametrize("config_entry", ["BB:BB:BB:BB:BB:BB"], indirect=True)
async def test_sensors_with_no_data( async def test_sensors_with_no_data(
hass: HomeAssistant, open_api: OpenAPI, aioambient, config_entry hass: HomeAssistant,
open_api: OpenAPI,
aioambient: AsyncMock,
config_entry: ConfigEntry,
) -> None: ) -> None:
"""Test that the sensors are not populated if the last data is absent.""" """Test that the sensors are not populated if the last data is absent."""
await setup_platform(False, hass, config_entry) await setup_platform(True, hass, config_entry)
sensor = hass.states.get("sensor.station_b_absolute_pressure") sensor = hass.states.get("sensor.station_b_temperature")
assert sensor is None assert sensor is not None
assert "last_measured" not in sensor.attributes
@freeze_time("2023-11-08")
@pytest.mark.parametrize("config_entry", ["CC:CC:CC:CC:CC:CC"], indirect=True)
async def test_sensors_with_no_update_time(
hass: HomeAssistant, open_api: OpenAPI, aioambient, config_entry
) -> None:
"""Test that the sensors are not populated if the update time is missing."""
await setup_platform(False, hass, config_entry)
sensor = hass.states.get("sensor.station_c_absolute_pressure")
assert sensor is None
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True) @pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
async def test_sensors_disappearing( async def test_sensors_disappearing(
hass: HomeAssistant, hass: HomeAssistant,
open_api: OpenAPI, open_api: OpenAPI,
aioambient, aioambient: AsyncMock,
config_entry, config_entry: ConfigEntry,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test that we log errors properly.""" """Test that we log errors properly."""