Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a DataUpdateCoordinator to Hydrawise #93223

Merged
merged 7 commits into from
May 24, 2023
Merged
50 changes: 23 additions & 27 deletions homeassistant/components/hydrawise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Support for Hydrawise cloud."""


from hydrawiser.core import Hydrawiser
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
Expand All @@ -8,19 +9,10 @@
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(
{
Expand All @@ -35,37 +27,41 @@
)


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}<br />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
dknowles2 marked this conversation as resolved.
Show resolved Hide resolved

# 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}<br />You will need to restart hass after fixing.",
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID,
)


class HydrawiseHub:
"""Representation of a base Hydrawise device."""

Expand Down
41 changes: 25 additions & 16 deletions homeassistant/components/hydrawise/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -10,12 +11,13 @@
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(
Expand Down Expand Up @@ -52,37 +54,44 @@ 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)


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()
1 change: 0 additions & 1 deletion homeassistant/components/hydrawise/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
NOTIFICATION_ID = "hydrawise_notification"
NOTIFICATION_TITLE = "Hydrawise Setup"

DATA_HYDRAWISE = "hydrawise"
DOMAIN = "hydrawise"
DEFAULT_WATERING_TIME = 15

Expand Down
29 changes: 29 additions & 0 deletions homeassistant/components/hydrawise/coordinator.py
Original file line number Diff line number Diff line change
@@ -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")
36 changes: 16 additions & 20 deletions homeassistant/components/hydrawise/entity.py
Original file line number Diff line number Diff line change
@@ -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."""
Expand Down
18 changes: 11 additions & 7 deletions homeassistant/components/hydrawise/sensor.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -10,13 +11,14 @@
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, ...] = (
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -86,3 +89,4 @@ def update(self) -> None:
self._attr_native_value = dt.utc_from_timestamp(
dt.as_timestamp(dt.now()) + next_cycle
)
super()._handle_coordinator_update()