Add a DataUpdateCoordinator to Hydrawise (#93223)
* Add a DataUpdateCoordinator to Hydrawise * Replace DATA_HYDRAWISE with DOMAIN * Replace persistent notification with a ConfigEntryNotReady exception * Changes requested during PR review * Add a type annotation to the `monitored_conditions` field. Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --------- Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
parent
f355f0cc6d
commit
ace45f31ff
7 changed files with 136 additions and 91 deletions
|
@ -1,5 +1,6 @@
|
|||
"""Support for Hydrawise cloud."""
|
||||
|
||||
|
||||
from hydrawiser.core import Hydrawiser
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
@ -8,19 +9,10 @@ from homeassistant.components import persistent_notification
|
|||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
DATA_HYDRAWISE,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
NOTIFICATION_ID,
|
||||
NOTIFICATION_TITLE,
|
||||
SCAN_INTERVAL,
|
||||
SIGNAL_UPDATE_HYDRAWISE,
|
||||
)
|
||||
from .const import DOMAIN, LOGGER, NOTIFICATION_ID, NOTIFICATION_TITLE, SCAN_INTERVAL
|
||||
from .coordinator import HydrawiseDataUpdateCoordinator
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -35,35 +27,39 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Hunter Hydrawise component."""
|
||||
conf = config[DOMAIN]
|
||||
access_token = conf[CONF_ACCESS_TOKEN]
|
||||
scan_interval = conf.get(CONF_SCAN_INTERVAL)
|
||||
|
||||
try:
|
||||
hydrawise = Hydrawiser(user_token=access_token)
|
||||
hass.data[DATA_HYDRAWISE] = HydrawiseHub(hydrawise)
|
||||
hydrawise = await hass.async_add_executor_job(Hydrawiser, access_token)
|
||||
except (ConnectTimeout, HTTPError) as ex:
|
||||
LOGGER.error("Unable to connect to Hydrawise cloud service: %s", str(ex))
|
||||
_show_failure_notification(hass, str(ex))
|
||||
return False
|
||||
|
||||
if not hydrawise.current_controller:
|
||||
LOGGER.error("Failed to fetch Hydrawise data")
|
||||
_show_failure_notification(hass, "Failed to fetch Hydrawise data.")
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN] = HydrawiseDataUpdateCoordinator(hass, hydrawise, scan_interval)
|
||||
|
||||
# NOTE: We don't need to call async_config_entry_first_refresh() because
|
||||
# data is fetched when the Hydrawiser object is instantiated.
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _show_failure_notification(hass: HomeAssistant, error: str):
|
||||
persistent_notification.create(
|
||||
hass,
|
||||
f"Error: {ex}<br />You will need to restart hass after fixing.",
|
||||
f"Error: {error}<br />You will need to restart hass after fixing.",
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID,
|
||||
)
|
||||
return False
|
||||
|
||||
def hub_refresh(event_time):
|
||||
"""Call Hydrawise hub to refresh information."""
|
||||
LOGGER.debug("Updating Hydrawise Hub component")
|
||||
hass.data[DATA_HYDRAWISE].data.update_controller_info()
|
||||
dispatcher_send(hass, SIGNAL_UPDATE_HYDRAWISE)
|
||||
|
||||
# Call the Hydrawise API to refresh updates
|
||||
track_time_interval(hass, hub_refresh, scan_interval)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class HydrawiseHub:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Support for Hydrawise sprinkler binary sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from hydrawiser.core import Hydrawiser
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
|
@ -10,12 +11,13 @@ from homeassistant.components.binary_sensor import (
|
|||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DATA_HYDRAWISE, LOGGER
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import HydrawiseDataUpdateCoordinator
|
||||
from .entity import HydrawiseEntity
|
||||
|
||||
BINARY_SENSOR_STATUS = BinarySensorEntityDescription(
|
||||
|
@ -52,23 +54,29 @@ def setup_platform(
|
|||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up a sensor for a Hydrawise device."""
|
||||
hydrawise = hass.data[DATA_HYDRAWISE].data
|
||||
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN]
|
||||
hydrawise: Hydrawiser = coordinator.api
|
||||
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
|
||||
|
||||
entities = []
|
||||
if BINARY_SENSOR_STATUS.key in monitored_conditions:
|
||||
entities.append(
|
||||
HydrawiseBinarySensor(hydrawise.current_controller, BINARY_SENSOR_STATUS)
|
||||
HydrawiseBinarySensor(
|
||||
data=hydrawise.current_controller,
|
||||
coordinator=coordinator,
|
||||
description=BINARY_SENSOR_STATUS,
|
||||
)
|
||||
)
|
||||
|
||||
# create a sensor for each zone
|
||||
entities.extend(
|
||||
[
|
||||
HydrawiseBinarySensor(zone, description)
|
||||
for zone in hydrawise.relays
|
||||
for description in BINARY_SENSOR_TYPES
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
for zone in hydrawise.relays:
|
||||
for description in BINARY_SENSOR_TYPES:
|
||||
if description.key not in monitored_conditions:
|
||||
continue
|
||||
entities.append(
|
||||
HydrawiseBinarySensor(
|
||||
data=zone, coordinator=coordinator, description=description
|
||||
)
|
||||
)
|
||||
|
||||
add_entities(entities, True)
|
||||
|
@ -77,12 +85,13 @@ def setup_platform(
|
|||
class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
|
||||
"""A sensor implementation for Hydrawise device."""
|
||||
|
||||
def update(self) -> None:
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Get the latest data and updates the state."""
|
||||
LOGGER.debug("Updating Hydrawise binary sensor: %s", self.name)
|
||||
mydata = self.hass.data[DATA_HYDRAWISE].data
|
||||
if self.entity_description.key == "status":
|
||||
self._attr_is_on = mydata.status == "All good!"
|
||||
self._attr_is_on = self.coordinator.api.status == "All good!"
|
||||
elif self.entity_description.key == "is_watering":
|
||||
relay_data = mydata.relays[self.data["relay"] - 1]
|
||||
relay_data = self.coordinator.api.relays[self.data["relay"] - 1]
|
||||
self._attr_is_on = relay_data["timestr"] == "Now"
|
||||
super()._handle_coordinator_update()
|
||||
|
|
|
@ -11,7 +11,6 @@ CONF_WATERING_TIME = "watering_minutes"
|
|||
NOTIFICATION_ID = "hydrawise_notification"
|
||||
NOTIFICATION_TITLE = "Hydrawise Setup"
|
||||
|
||||
DATA_HYDRAWISE = "hydrawise"
|
||||
DOMAIN = "hydrawise"
|
||||
DEFAULT_WATERING_TIME = 15
|
||||
|
||||
|
|
29
homeassistant/components/hydrawise/coordinator.py
Normal file
29
homeassistant/components/hydrawise/coordinator.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
"""DataUpdateCoordinator for the Hydrawise integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from hydrawiser.core import Hydrawiser
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
"""The Hydrawise Data Update Coordinator."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: Hydrawiser, scan_interval: timedelta
|
||||
) -> None:
|
||||
"""Initialize HydrawiseDataUpdateCoordinator."""
|
||||
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=scan_interval)
|
||||
self.api = api
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch the latest data from Hydrawise."""
|
||||
result = await self.hass.async_add_executor_job(self.api.update_controller_info)
|
||||
if not result:
|
||||
raise UpdateFailed("Failed to refresh Hydrawise data")
|
|
@ -1,36 +1,32 @@
|
|||
"""Base classes for Hydrawise entities."""
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from typing import Any
|
||||
|
||||
from .const import SIGNAL_UPDATE_HYDRAWISE
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
|
||||
|
||||
class HydrawiseEntity(Entity):
|
||||
class HydrawiseEntity(CoordinatorEntity):
|
||||
"""Entity class for Hydrawise devices."""
|
||||
|
||||
_attr_attribution = "Data provided by hydrawise.com"
|
||||
|
||||
def __init__(self, data, description: EntityDescription) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
data: dict[str, Any],
|
||||
coordinator: DataUpdateCoordinator,
|
||||
description: EntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Hydrawise entity."""
|
||||
self.entity_description = description
|
||||
super().__init__(coordinator=coordinator)
|
||||
self.data = data
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{self.data['name']} {description.name}"
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_UPDATE_HYDRAWISE, self._update_callback
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_callback(self):
|
||||
"""Call update method."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Support for Hydrawise sprinkler sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from hydrawiser.core import Hydrawiser
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
|
@ -10,13 +11,14 @@ from homeassistant.components.sensor import (
|
|||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import dt
|
||||
|
||||
from .const import DATA_HYDRAWISE, LOGGER
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .coordinator import HydrawiseDataUpdateCoordinator
|
||||
from .entity import HydrawiseEntity
|
||||
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
|
@ -54,11 +56,12 @@ def setup_platform(
|
|||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up a sensor for a Hydrawise device."""
|
||||
hydrawise = hass.data[DATA_HYDRAWISE].data
|
||||
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN]
|
||||
hydrawise: Hydrawiser = coordinator.api
|
||||
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
|
||||
|
||||
entities = [
|
||||
HydrawiseSensor(zone, description)
|
||||
HydrawiseSensor(data=zone, coordinator=coordinator, description=description)
|
||||
for zone in hydrawise.relays
|
||||
for description in SENSOR_TYPES
|
||||
if description.key in monitored_conditions
|
||||
|
@ -70,11 +73,11 @@ def setup_platform(
|
|||
class HydrawiseSensor(HydrawiseEntity, SensorEntity):
|
||||
"""A sensor implementation for Hydrawise device."""
|
||||
|
||||
def update(self) -> None:
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Get the latest data and updates the states."""
|
||||
mydata = self.hass.data[DATA_HYDRAWISE].data
|
||||
LOGGER.debug("Updating Hydrawise sensor: %s", self.name)
|
||||
relay_data = mydata.relays[self.data["relay"] - 1]
|
||||
relay_data = self.coordinator.api.relays[self.data["relay"] - 1]
|
||||
if self.entity_description.key == "watering_time":
|
||||
if relay_data["timestr"] == "Now":
|
||||
self._attr_native_value = int(relay_data["run"] / 60)
|
||||
|
@ -86,3 +89,4 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity):
|
|||
self._attr_native_value = dt.utc_from_timestamp(
|
||||
dt.as_timestamp(dt.now()) + next_cycle
|
||||
)
|
||||
super()._handle_coordinator_update()
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from hydrawiser.core import Hydrawiser
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
|
@ -12,18 +13,20 @@ from homeassistant.components.switch import (
|
|||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import (
|
||||
ALLOWED_WATERING_TIME,
|
||||
CONF_WATERING_TIME,
|
||||
DATA_HYDRAWISE,
|
||||
DEFAULT_WATERING_TIME,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
)
|
||||
from .coordinator import HydrawiseDataUpdateCoordinator
|
||||
from .entity import HydrawiseEntity
|
||||
|
||||
SWITCH_TYPES: tuple[SwitchEntityDescription, ...] = (
|
||||
|
@ -60,12 +63,18 @@ def setup_platform(
|
|||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up a sensor for a Hydrawise device."""
|
||||
hydrawise = hass.data[DATA_HYDRAWISE].data
|
||||
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
|
||||
default_watering_timer = config[CONF_WATERING_TIME]
|
||||
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN]
|
||||
hydrawise: Hydrawiser = coordinator.api
|
||||
monitored_conditions: list[str] = config[CONF_MONITORED_CONDITIONS]
|
||||
default_watering_timer: int = config[CONF_WATERING_TIME]
|
||||
|
||||
entities = [
|
||||
HydrawiseSwitch(zone, description, default_watering_timer)
|
||||
HydrawiseSwitch(
|
||||
data=zone,
|
||||
coordinator=coordinator,
|
||||
description=description,
|
||||
default_watering_timer=default_watering_timer,
|
||||
)
|
||||
for zone in hydrawise.relays
|
||||
for description in SWITCH_TYPES
|
||||
if description.key in monitored_conditions
|
||||
|
@ -78,38 +87,41 @@ class HydrawiseSwitch(HydrawiseEntity, SwitchEntity):
|
|||
"""A switch implementation for Hydrawise device."""
|
||||
|
||||
def __init__(
|
||||
self, data, description: SwitchEntityDescription, default_watering_timer
|
||||
self,
|
||||
*,
|
||||
data: dict[str, Any],
|
||||
coordinator: DataUpdateCoordinator,
|
||||
description: SwitchEntityDescription,
|
||||
default_watering_timer: int,
|
||||
) -> None:
|
||||
"""Initialize a switch for Hydrawise device."""
|
||||
super().__init__(data, description)
|
||||
super().__init__(data=data, coordinator=coordinator, description=description)
|
||||
self._default_watering_timer = default_watering_timer
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
relay_data = self.data["relay"] - 1
|
||||
if self.entity_description.key == "manual_watering":
|
||||
self.hass.data[DATA_HYDRAWISE].data.run_zone(
|
||||
self._default_watering_timer, relay_data
|
||||
)
|
||||
self.coordinator.api.run_zone(self._default_watering_timer, relay_data)
|
||||
elif self.entity_description.key == "auto_watering":
|
||||
self.hass.data[DATA_HYDRAWISE].data.suspend_zone(0, relay_data)
|
||||
self.coordinator.api.suspend_zone(0, relay_data)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
relay_data = self.data["relay"] - 1
|
||||
if self.entity_description.key == "manual_watering":
|
||||
self.hass.data[DATA_HYDRAWISE].data.run_zone(0, relay_data)
|
||||
self.coordinator.api.run_zone(0, relay_data)
|
||||
elif self.entity_description.key == "auto_watering":
|
||||
self.hass.data[DATA_HYDRAWISE].data.suspend_zone(365, relay_data)
|
||||
self.coordinator.api.suspend_zone(365, relay_data)
|
||||
|
||||
def update(self) -> None:
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update device state."""
|
||||
relay_data = self.data["relay"] - 1
|
||||
mydata = self.hass.data[DATA_HYDRAWISE].data
|
||||
LOGGER.debug("Updating Hydrawise switch: %s", self.name)
|
||||
timestr = self.coordinator.api.relays[relay_data]["timestr"]
|
||||
if self.entity_description.key == "manual_watering":
|
||||
self._attr_is_on = mydata.relays[relay_data]["timestr"] == "Now"
|
||||
self._attr_is_on = timestr == "Now"
|
||||
elif self.entity_description.key == "auto_watering":
|
||||
self._attr_is_on = (mydata.relays[relay_data]["timestr"] != "") and (
|
||||
mydata.relays[relay_data]["timestr"] != "Now"
|
||||
)
|
||||
self._attr_is_on = timestr not in {"", "Now"}
|
||||
super()._handle_coordinator_update()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue