diff --git a/custom_components/sonic/CHANGELOG_0.2.4.md b/custom_components/sonic/CHANGELOG_0.2.4.md new file mode 100644 index 0000000..0cc0c8f --- /dev/null +++ b/custom_components/sonic/CHANGELOG_0.2.4.md @@ -0,0 +1,13 @@ +# SONIC 0.2.4 — test build + +- Removed legacy timezone dependency from telemetry timestamp handling. +- Uses module-level ZoneInfo for the Sonic API timestamp. +- Handles a missing telemetry timestamp safely. +- Uses a timezone-aware datetime. +- Uses Home Assistant's standard L/min flow-rate unit. +- Replaced async_timeout with asyncio.timeout. +- Removed redundant one-task asyncio.gather calls. +- Runs Sonic details and telemetry API requests concurrently. +- Fixed PropertyEntity.async_update reference. +- Uses coordinator update success for property availability. +- Version 0.2.4. diff --git a/custom_components/sonic/device.py b/custom_components/sonic/device.py index 253b0ff..e40b092 100644 --- a/custom_components/sonic/device.py +++ b/custom_components/sonic/device.py @@ -5,7 +5,6 @@ from datetime import timedelta from typing import Any -from async_timeout import timeout from herolabsapi.client import Client from herolabsapi.errors import RequestError @@ -35,12 +34,8 @@ def __init__(self, hass: HomeAssistant, api_client: Client, device_id: str) -> N async def _async_update_data(self): """Update data via library.""" try: - async with timeout(10): - await asyncio.gather( - *[ - self._update_device(), - ] - ) + async with asyncio.timeout(10): + await self._update_device() except (RequestError) as error: raise UpdateFailed(error) from error @@ -111,19 +106,19 @@ def battery_state(self) -> str: @property def auto_shut_off_enabled(self) -> bool: """Return the auto shut off enabled boolean""" - return self._device_information["auto_shut_off_enabled"] + return self._device_information.get("auto_shut_off_enabled") @property def auto_shut_off_time_limit(self) -> int: """Return the Sonic offline auto shut off water usage time limit in seconds[0;integer::max). When set to 0 usage time check is not performed.""" - return self._device_information["auto_shut_off_time_limit"] + return self._device_information.get("auto_shut_off_time_limit") @property def auto_shut_off_volume_limit(self) -> int: """Return the Sonic offline auto shut off used water volume limit in millilitres [0;integer::max). When set to 0 volume used check is not performed.""" - return self._device_information["auto_shut_off_volume_limit"] + return self._device_information.get("auto_shut_off_volume_limit") @property def signal_id(self) -> str: diff --git a/custom_components/sonic/entity.py b/custom_components/sonic/entity.py index 3493f71..f579a20 100644 --- a/custom_components/sonic/entity.py +++ b/custom_components/sonic/entity.py @@ -90,7 +90,7 @@ def available(self) -> bool: async def async_update(self): """Update Property entity.""" - await self._property.async_request_refresh() + await self._device.async_request_refresh() async def async_added_to_hass(self): """When entity is added to hass.""" diff --git a/custom_components/sonic/manifest.json b/custom_components/sonic/manifest.json index 29c2f1e..49020b0 100644 --- a/custom_components/sonic/manifest.json +++ b/custom_components/sonic/manifest.json @@ -1,12 +1,18 @@ { "domain": "sonic", "name": "Sonic (Hero Labs)", - "codeowners": ["@markvader"], + "codeowners": [ + "@markvader" + ], "config_flow": true, "documentation": "https://github.com/markvader/sonic_hacs", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/markvader/sonic_hacs/issues", - "loggers": ["sonic"], - "requirements": ["herolabsapi==0.5.1"], - "version": "v0.2.3" + "loggers": [ + "sonic" + ], + "requirements": [ + "herolabsapi==0.5.1" + ], + "version": "0.2.4" } diff --git a/custom_components/sonic/property.py b/custom_components/sonic/property.py index 43de318..76e942f 100644 --- a/custom_components/sonic/property.py +++ b/custom_components/sonic/property.py @@ -5,7 +5,6 @@ from datetime import timedelta from typing import Any -from async_timeout import timeout from herolabsapi.client import Client from herolabsapi.errors import RequestError @@ -36,12 +35,8 @@ def __init__(self, hass: HomeAssistant, api_client: Client, property_id: str) -> async def _async_update_data(self): """Update data via library.""" try: - async with timeout(10): - await asyncio.gather( - *[ - self._update_property(), - ] - ) + async with asyncio.timeout(10): + await self._update_property() except (RequestError) as error: raise UpdateFailed(error) from error diff --git a/custom_components/sonic/sensor.py b/custom_components/sonic/sensor.py index 7f2e854..21f7437 100644 --- a/custom_components/sonic/sensor.py +++ b/custom_components/sonic/sensor.py @@ -1,8 +1,7 @@ """The Sonic Water Shut-off Valve integration.""" from __future__ import annotations from datetime import datetime -import pytz - +from zoneinfo import ZoneInfo from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -13,6 +12,7 @@ UnitOfPressure, UnitOfTemperature, UnitOfVolume, + UnitOfVolumeFlowRate, UnitOfTime, ) from homeassistant.core import HomeAssistant @@ -80,18 +80,18 @@ async def async_setup_entry( async_add_entities(entities) + +SONIC_TIMEZONE = ZoneInfo("Europe/London") class SonicCurrentFlowRateSensor(SonicEntity, SensorEntity): """Monitors the current water flow rate.""" _attr_icon = GAUGE_ICON - _attr_native_unit_of_measurement = "litres per min" + _attr_native_unit_of_measurement = UnitOfVolumeFlowRate.LITERS_PER_MINUTE _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT def __init__(self, device): """Initialize the flow rate sensor.""" super().__init__("current_flow_rate", NAME_FLOW_RATE, device) - self._state: float = None - @property def native_value(self) -> float | None: """Return the current flow rate in Litre per minute.""" @@ -110,8 +110,6 @@ class SonicTemperatureSensor(SonicEntity, SensorEntity): def __init__(self, device): """Initialize the temperature sensor.""" super().__init__("temperature", NAME_WATER_TEMPERATURE, device) - self._state: float = None - @property def native_value(self) -> float | None: """Return the current temperature.""" @@ -130,8 +128,6 @@ class SonicPressureSensor(SonicEntity, SensorEntity): def __init__(self, device): """Initialize the water pressure sensor.""" super().__init__("water_pressure", NAME_WATER_PRESSURE, device) - self._state: float = None - @property def native_value(self) -> float | None: """Return the current water pressure in bar.""" @@ -153,7 +149,7 @@ def __init__(self, device): self._state: str = None @property - def native_value(self) -> str | None: + def native_value(self) -> datetime | None: """Return the current battery state.""" return self._device.battery_state @@ -174,10 +170,11 @@ def __init__(self, device): def native_value(self) -> str | None: """Return the current telemetry time state.""" telemetry_timestamp = self._device.last_heard_from_time - # telemetry_timezone = self._device.property_timezone - timezone = pytz.timezone("Europe/London") - telemetry_datetime = datetime.fromtimestamp(telemetry_timestamp, timezone) - return telemetry_datetime + + if telemetry_timestamp is None: + return None + + return datetime.fromtimestamp(telemetry_timestamp, SONIC_TIMEZONE) class SonicValveStateSensor(SonicEntity, SensorEntity): @@ -232,7 +229,8 @@ def __init__(self, device): @property def native_value(self) -> int | None: """Return the auto_shut_off_time_limit state in minutes.""" - return round((self._device.auto_shut_off_time_limit)/60) + val = self._device.auto_shut_off_time_limit + return round(val / 60) if val is not None else None class SonicAutoShutOffVolumeLimitSensor(SonicEntity, SensorEntity): @@ -250,7 +248,8 @@ def __init__(self, device): @property def native_value(self) -> int | None: """Return the auto_shut_off_volume_limit state.""" - return round((self._device.auto_shut_off_volume_limit)/1000) + val = self._device.auto_shut_off_volume_limit + return round(val / 1000) if val is not None else None class PropertyLongFlowNotificationDelay(PropertyEntity, SensorEntity): """Return the long flow notification delay in minutes at property"""