Change Ambient Network timestamp updates (#116941)
This commit is contained in:
parent
f353b3fa54
commit
180c244a78
7 changed files with 1954 additions and 53 deletions
|
@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.const import CONF_MAC
|
||||
from homeassistant.core import HomeAssistant
|
||||
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 .helper import get_station_name
|
||||
|
@ -24,6 +25,7 @@ class AmbientNetworkDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
|||
|
||||
config_entry: ConfigEntry
|
||||
station_name: str
|
||||
last_measured: datetime | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: OpenAPI) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
|
@ -47,19 +49,13 @@ class AmbientNetworkDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
|||
f"Station '{self.config_entry.title}' did not report any data"
|
||||
)
|
||||
|
||||
# Eliminate data if the station hasn't been updated for a while.
|
||||
if (created_at := last_data.get("created_at")) is None:
|
||||
raise UpdateFailed(
|
||||
f"Station '{self.config_entry.title}' did not report a time stamp"
|
||||
)
|
||||
|
||||
# Eliminate data that has been generated more than an hour ago. The station is
|
||||
# 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"
|
||||
# Some stations do not report a "created_at" or "dateutc".
|
||||
# See https://github.com/home-assistant/core/issues/116917
|
||||
if (ts := last_data.get("created_at")) is not None or (
|
||||
ts := last_data.get("dateutc")
|
||||
) is not None:
|
||||
self.last_measured = datetime.fromtimestamp(
|
||||
ts / 1000, tz=dt_util.DEFAULT_TIME_ZONE
|
||||
)
|
||||
|
||||
return cast(dict[str, Any], last_data)
|
||||
|
|
|
@ -299,12 +299,10 @@ class AmbientNetworkSensor(AmbientNetworkEntity, SensorEntity):
|
|||
mac_address: str,
|
||||
) -> None:
|
||||
"""Initialize a sensor object."""
|
||||
|
||||
super().__init__(coordinator, description, mac_address)
|
||||
|
||||
def _update_attrs(self) -> None:
|
||||
"""Update sensor attributes."""
|
||||
|
||||
value = self.coordinator.data.get(self.entity_description.key)
|
||||
|
||||
# Treatments for special units.
|
||||
|
@ -315,3 +313,8 @@ class AmbientNetworkSensor(AmbientNetworkEntity, SensorEntity):
|
|||
|
||||
self._attr_available = value is not None
|
||||
self._attr_native_value = value
|
||||
|
||||
if self.coordinator.last_measured is not None:
|
||||
self._attr_extra_state_attributes = {
|
||||
"last_measured": self.coordinator.last_measured
|
||||
}
|
||||
|
|
|
@ -3,5 +3,8 @@
|
|||
"macAddress": "BB:BB:BB:BB:BB:BB",
|
||||
"info": {
|
||||
"name": "Station B"
|
||||
},
|
||||
"lastData": {
|
||||
"tempf": 82.9
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"macAddress": "CC:CC:CC:CC:CC:CC",
|
||||
"lastData": {
|
||||
"stationtype": "AMBWeatherPro_V5.0.6",
|
||||
"dateutc": 1699474320000,
|
||||
"dateutc": 1717687683000,
|
||||
"tempf": 82.9,
|
||||
"dewPoint": 82.0,
|
||||
"feelsLike": 85.0,
|
||||
|
|
|
@ -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
|
@ -1,7 +1,7 @@
|
|||
"""Test Ambient Weather Network sensors."""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aioambient import OpenAPI
|
||||
from aioambient.errors import RequestError
|
||||
|
@ -9,6 +9,7 @@ from freezegun import freeze_time
|
|||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
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
|
||||
|
||||
|
||||
@freeze_time("2023-11-08")
|
||||
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
|
||||
@freeze_time("2023-11-9")
|
||||
@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")
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
open_api: OpenAPI,
|
||||
aioambient,
|
||||
config_entry,
|
||||
aioambient: AsyncMock,
|
||||
config_entry: ConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test all sensors under normal operation."""
|
||||
await setup_platform(True, hass, config_entry)
|
||||
|
||||
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)
|
||||
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:
|
||||
"""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")
|
||||
assert sensor is None
|
||||
|
||||
|
||||
@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
|
||||
sensor = hass.states.get("sensor.station_b_temperature")
|
||||
assert sensor is not None
|
||||
assert "last_measured" not in sensor.attributes
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
|
||||
async def test_sensors_disappearing(
|
||||
hass: HomeAssistant,
|
||||
open_api: OpenAPI,
|
||||
aioambient,
|
||||
config_entry,
|
||||
aioambient: AsyncMock,
|
||||
config_entry: ConfigEntry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that we log errors properly."""
|
||||
|
|
Loading…
Add table
Reference in a new issue