diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py
index ba561a14f82..d5b69617ba9 100644
--- a/homeassistant/components/hydrawise/__init__.py
+++ b/homeassistant/components/hydrawise/__init__.py
@@ -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,37 +27,41 @@ 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))
- persistent_notification.create(
- hass,
- f"Error: {ex}
You will need to restart hass after fixing.",
- title=NOTIFICATION_TITLE,
- notification_id=NOTIFICATION_ID,
- )
+ _show_failure_notification(hass, str(ex))
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)
+ if not hydrawise.current_controller:
+ LOGGER.error("Failed to fetch Hydrawise data")
+ _show_failure_notification(hass, "Failed to fetch Hydrawise data.")
+ return False
- # Call the Hydrawise API to refresh updates
- track_time_interval(hass, hub_refresh, scan_interval)
+ 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: {error}
You will need to restart hass after fixing.",
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID,
+ )
+
+
class HydrawiseHub:
"""Representation of a base Hydrawise device."""
diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py
index 93594d71436..2986bbb170e 100644
--- a/homeassistant/components/hydrawise/binary_sensor.py
+++ b/homeassistant/components/hydrawise/binary_sensor.py
@@ -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,24 +54,30 @@ 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()
diff --git a/homeassistant/components/hydrawise/const.py b/homeassistant/components/hydrawise/const.py
index 5a046530f01..515fdaec2b1 100644
--- a/homeassistant/components/hydrawise/const.py
+++ b/homeassistant/components/hydrawise/const.py
@@ -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
diff --git a/homeassistant/components/hydrawise/coordinator.py b/homeassistant/components/hydrawise/coordinator.py
new file mode 100644
index 00000000000..ea2e2dd2c4c
--- /dev/null
+++ b/homeassistant/components/hydrawise/coordinator.py
@@ -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")
diff --git a/homeassistant/components/hydrawise/entity.py b/homeassistant/components/hydrawise/entity.py
index 5c54c1ee580..405e5bc3fa3 100644
--- a/homeassistant/components/hydrawise/entity.py
+++ b/homeassistant/components/hydrawise/entity.py
@@ -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."""
diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py
index 2cec1309ec9..f47f404cfa0 100644
--- a/homeassistant/components/hydrawise/sensor.py
+++ b/homeassistant/components/hydrawise/sensor.py
@@ -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()
diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py
index ac9b0d27025..71c4f8df79e 100644
--- a/homeassistant/components/hydrawise/switch.py
+++ b/homeassistant/components/hydrawise/switch.py
@@ -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()