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 flow and rain sensor support to Hydrawise #116303

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 51 additions & 20 deletions homeassistant/components/hydrawise/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
Expand All @@ -15,22 +18,40 @@
from .coordinator import HydrawiseDataUpdateCoordinator
from .entity import HydrawiseEntity

BINARY_SENSOR_STATUS = BinarySensorEntityDescription(
key="status",
device_class=BinarySensorDeviceClass.CONNECTIVITY,

@dataclass(frozen=True, kw_only=True)
class HydrawiseBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes Hydrawise binary sensor."""

value_fn: Callable[[HydrawiseBinarySensor], bool | None]


CONTROLLER_BINARY_SENSORS: tuple[HydrawiseBinarySensorEntityDescription, ...] = (
HydrawiseBinarySensorEntityDescription(
key="status",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
value_fn=lambda status_sensor: status_sensor.coordinator.last_update_success,
),
)

BINARY_SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription(
key="is_watering",
translation_key="watering",
RAIN_SENSOR_BINARY_SENSOR: tuple[HydrawiseBinarySensorEntityDescription, ...] = (
HydrawiseBinarySensorEntityDescription(
key="rain_sensor",
translation_key="rain_sensor",
device_class=BinarySensorDeviceClass.MOISTURE,
value_fn=lambda rain_sensor: rain_sensor.sensor.status.active,
),
)

BINARY_SENSOR_KEYS: list[str] = [
desc.key for desc in (BINARY_SENSOR_STATUS, *BINARY_SENSOR_TYPES)
]
ZONE_BINARY_SENSORS: tuple[HydrawiseBinarySensorEntityDescription, ...] = (
HydrawiseBinarySensorEntityDescription(
key="is_watering",
translation_key="watering",
device_class=BinarySensorDeviceClass.RUNNING,
value_fn=lambda watering_sensor: watering_sensor.zone.scheduled_runs.current_run
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
is not None,
),
)


async def async_setup_entry(
Expand All @@ -42,26 +63,36 @@ async def async_setup_entry(
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
entities = []
entities: list[HydrawiseBinarySensor] = []
for controller in coordinator.data.controllers.values():
entities.append(
HydrawiseBinarySensor(coordinator, BINARY_SENSOR_STATUS, controller)
entities.extend(
HydrawiseBinarySensor(coordinator, description, controller)
for description in CONTROLLER_BINARY_SENSORS
)
entities.extend(
HydrawiseBinarySensor(
coordinator,
description,
controller,
sensor_id=sensor.id,
)
for sensor in controller.sensors
for description in RAIN_SENSOR_BINARY_SENSOR
if "rain sensor" in sensor.model.name.lower()
)
entities.extend(
HydrawiseBinarySensor(coordinator, description, controller, zone)
HydrawiseBinarySensor(coordinator, description, controller, zone_id=zone.id)
for zone in controller.zones
for description in BINARY_SENSOR_TYPES
for description in ZONE_BINARY_SENSORS
)
async_add_entities(entities)


class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
"""A sensor implementation for Hydrawise device."""

entity_description: HydrawiseBinarySensorEntityDescription

def _update_attrs(self) -> None:
"""Update state attributes."""
if self.entity_description.key == "status":
self._attr_is_on = self.coordinator.last_update_success
elif self.entity_description.key == "is_watering":
assert self.zone is not None
self._attr_is_on = self.zone.scheduled_runs.current_run is not None
self._attr_is_on = self.entity_description.value_fn(self)
35 changes: 30 additions & 5 deletions homeassistant/components/hydrawise/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from dataclasses import dataclass
from datetime import timedelta

from pydrawise import HydrawiseBase
from pydrawise.schema import Controller, User, Zone
from pydrawise import Hydrawise
from pydrawise.schema import Controller, ControllerWaterUseSummary, Sensor, User, Zone

from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util.dt import now

from .const import DOMAIN, LOGGER

Expand All @@ -21,15 +22,17 @@ class HydrawiseData:
user: User
controllers: dict[int, Controller]
zones: dict[int, Zone]
sensors: dict[int, Sensor]
daily_water_use: dict[int, ControllerWaterUseSummary]


class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[HydrawiseData]):
"""The Hydrawise Data Update Coordinator."""

api: HydrawiseBase
api: Hydrawise

def __init__(
self, hass: HomeAssistant, api: HydrawiseBase, scan_interval: timedelta
self, hass: HomeAssistant, api: Hydrawise, scan_interval: timedelta
) -> None:
"""Initialize HydrawiseDataUpdateCoordinator."""
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=scan_interval)
Expand All @@ -40,8 +43,30 @@ async def _async_update_data(self) -> HydrawiseData:
user = await self.api.get_user()
controllers = {}
zones = {}
sensors = {}
daily_water_use: dict[int, ControllerWaterUseSummary] = {}
for controller in user.controllers:
controllers[controller.id] = controller
for zone in controller.zones:
zones[zone.id] = zone
return HydrawiseData(user=user, controllers=controllers, zones=zones)
for sensor in controller.sensors:
sensors[sensor.id] = sensor
if any(
thomaskistler marked this conversation as resolved.
Show resolved Hide resolved
"flow meter" in sensor.model.name.lower()
for sensor in controller.sensors
):
daily_water_use[controller.id] = await self.api.get_water_use_summary(
controller,
now().replace(hour=0, minute=0, second=0, microsecond=0),
now(),
)
else:
daily_water_use[controller.id] = ControllerWaterUseSummary()

return HydrawiseData(
user=user,
controllers=controllers,
zones=zones,
sensors=sensors,
daily_water_use=daily_water_use,
)
32 changes: 24 additions & 8 deletions homeassistant/components/hydrawise/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from pydrawise.schema import Controller, Zone
from pydrawise.schema import Controller, Sensor, Zone

from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
Expand All @@ -24,24 +24,42 @@ def __init__(
coordinator: HydrawiseDataUpdateCoordinator,
description: EntityDescription,
controller: Controller,
zone: Zone | None = None,
*,
zone_id: int | None = None,
sensor_id: int | None = None,
) -> None:
"""Initialize the Hydrawise entity."""
super().__init__(coordinator=coordinator)
self.entity_description = description
self.controller = controller
self.zone = zone
self._device_id = str(controller.id if zone is None else zone.id)
self.zone_id = zone_id
self.sensor_id = sensor_id
self._device_id = str(zone_id) if zone_id is not None else str(controller.id)
self._attr_unique_id = f"{self._device_id}_{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
name=controller.name if zone is None else zone.name,
name=self.zone.name if zone_id is not None else controller.name,
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
model="Zone"
if zone_id is not None
else controller.hardware.model.description,
manufacturer=MANUFACTURER,
)
if zone is not None:
if zone_id is not None or sensor_id is not None:
self._attr_device_info["via_device"] = (DOMAIN, str(controller.id))
self._update_attrs()

@property
def zone(self) -> Zone:
"""Return the entity zone."""
assert self.zone_id is not None # needed for mypy
return self.coordinator.data.zones[self.zone_id]

@property
def sensor(self) -> Sensor:
"""Return the entity sensor."""
assert self.sensor_id is not None # needed for mypy
return self.coordinator.data.sensors[self.sensor_id]

def _update_attrs(self) -> None:
"""Update state attributes."""
return # pragma: no cover
Expand All @@ -50,7 +68,5 @@ def _update_attrs(self) -> None:
def _handle_coordinator_update(self) -> None:
"""Get the latest data and updates the state."""
self.controller = self.coordinator.data.controllers[self.controller.id]
if self.zone:
self.zone = self.coordinator.data.zones[self.zone.id]
self._update_attrs()
super()._handle_coordinator_update()
23 changes: 22 additions & 1 deletion homeassistant/components/hydrawise/icons.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
{
"entity": {
"sensor": {
"daily_active_water_use": {
"default": "mdi:water"
},
"daily_inactive_water_use": {
"default": "mdi:water"
},
"daily_total_water_use": {
"default": "mdi:water"
},
"next_cycle": {
"default": "mdi:clock-outline"
},
"watering_time": {
"default": "mdi:water-pump"
"default": "mdi:timer-outline"
}
},
"binary_sensor": {
"rain_sensor": {
"default": "mdi:weather-sunny",
"state": {
"off": "mdi:weather-sunny",
"on": "mdi:weather-pouring"
}
}
}
}
Expand Down