Add Rachio smart hose timer support (#107901)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
3c13a28357
commit
dbb4cf0ee7
9 changed files with 263 additions and 17 deletions
|
@ -11,19 +11,28 @@ import voluptuous as vol
|
|||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_platform,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import as_timestamp, now, parse_datetime, utc_from_timestamp
|
||||
|
||||
from .const import (
|
||||
CONF_MANUAL_RUN_MINS,
|
||||
DEFAULT_MANUAL_RUN_MINS,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN as DOMAIN_RACHIO,
|
||||
KEY_CONNECTED,
|
||||
KEY_CURRENT_STATUS,
|
||||
KEY_CUSTOM_CROP,
|
||||
KEY_CUSTOM_SHADE,
|
||||
KEY_CUSTOM_SLOPE,
|
||||
|
@ -36,7 +45,9 @@ from .const import (
|
|||
KEY_ON,
|
||||
KEY_RAIN_DELAY,
|
||||
KEY_RAIN_DELAY_END,
|
||||
KEY_REPORTED_STATE,
|
||||
KEY_SCHEDULE_ID,
|
||||
KEY_STATE,
|
||||
KEY_SUBTYPE,
|
||||
KEY_SUMMARY,
|
||||
KEY_TYPE,
|
||||
|
@ -46,6 +57,7 @@ from .const import (
|
|||
SCHEDULE_TYPE_FLEX,
|
||||
SERVICE_SET_ZONE_MOISTURE,
|
||||
SERVICE_START_MULTIPLE_ZONES,
|
||||
SERVICE_START_WATERING,
|
||||
SIGNAL_RACHIO_CONTROLLER_UPDATE,
|
||||
SIGNAL_RACHIO_RAIN_DELAY_UPDATE,
|
||||
SIGNAL_RACHIO_SCHEDULE_UPDATE,
|
||||
|
@ -55,6 +67,7 @@ from .const import (
|
|||
SLOPE_SLIGHT,
|
||||
SLOPE_STEEP,
|
||||
)
|
||||
from .coordinator import RachioUpdateCoordinator
|
||||
from .device import RachioPerson
|
||||
from .entity import RachioDevice
|
||||
from .webhooks import (
|
||||
|
@ -80,6 +93,7 @@ ATTR_SCHEDULE_ENABLED = "Enabled"
|
|||
ATTR_SCHEDULE_DURATION = "Duration"
|
||||
ATTR_SCHEDULE_TYPE = "Type"
|
||||
ATTR_SORT_ORDER = "sortOrder"
|
||||
ATTR_WATERING_DURATION = "Watering Duration seconds"
|
||||
ATTR_ZONE_NUMBER = "Zone number"
|
||||
ATTR_ZONE_SHADE = "Shade"
|
||||
ATTR_ZONE_SLOPE = "Slope"
|
||||
|
@ -141,6 +155,19 @@ async def async_setup_entry(
|
|||
else:
|
||||
raise HomeAssistantError("No matching zones found in given entity_ids")
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_START_WATERING,
|
||||
{
|
||||
vol.Optional(ATTR_DURATION): cv.positive_int,
|
||||
},
|
||||
"turn_on",
|
||||
)
|
||||
|
||||
# If only hose timers on account, none of these services apply
|
||||
if not zone_entities:
|
||||
return
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN_RACHIO,
|
||||
SERVICE_START_MULTIPLE_ZONES,
|
||||
|
@ -176,6 +203,11 @@ def _create_entities(hass: HomeAssistant, config_entry: ConfigEntry) -> list[Ent
|
|||
RachioSchedule(person, controller, schedule, current_schedule)
|
||||
for schedule in schedules + flex_schedules
|
||||
)
|
||||
entities.extend(
|
||||
RachioValve(person, base_station, valve, base_station.coordinator)
|
||||
for base_station in person.base_stations
|
||||
for valve in base_station.coordinator.data.values()
|
||||
)
|
||||
return entities
|
||||
|
||||
|
||||
|
@ -246,9 +278,9 @@ class RachioRainDelay(RachioSwitch):
|
|||
_attr_has_entity_name = True
|
||||
_attr_translation_key = "rain_delay"
|
||||
|
||||
def __init__(self, controller):
|
||||
def __init__(self, controller) -> None:
|
||||
"""Set up a Rachio rain delay switch."""
|
||||
self._cancel_update = None
|
||||
self._cancel_update: CALLBACK_TYPE | None = None
|
||||
super().__init__(controller)
|
||||
|
||||
@property
|
||||
|
@ -324,7 +356,7 @@ class RachioZone(RachioSwitch):
|
|||
|
||||
_attr_icon = "mdi:water"
|
||||
|
||||
def __init__(self, person, controller, data, current_schedule):
|
||||
def __init__(self, person, controller, data, current_schedule) -> None:
|
||||
"""Initialize a new Rachio Zone."""
|
||||
self.id = data[KEY_ID]
|
||||
self._attr_name = data[KEY_NAME]
|
||||
|
@ -379,11 +411,14 @@ class RachioZone(RachioSwitch):
|
|||
self.turn_off()
|
||||
|
||||
# Start this zone
|
||||
manual_run_time = timedelta(
|
||||
minutes=self._person.config_entry.options.get(
|
||||
CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS
|
||||
if ATTR_DURATION in kwargs:
|
||||
manual_run_time = timedelta(minutes=kwargs[ATTR_DURATION])
|
||||
else:
|
||||
manual_run_time = timedelta(
|
||||
minutes=self._person.config_entry.options.get(
|
||||
CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS
|
||||
)
|
||||
)
|
||||
)
|
||||
# The API limit is 3 hours, and requires an int be passed
|
||||
self._controller.rachio.zone.start(self.zone_id, manual_run_time.seconds)
|
||||
_LOGGER.debug(
|
||||
|
@ -435,7 +470,7 @@ class RachioZone(RachioSwitch):
|
|||
class RachioSchedule(RachioSwitch):
|
||||
"""Representation of one fixed schedule on the Rachio Iro."""
|
||||
|
||||
def __init__(self, person, controller, data, current_schedule):
|
||||
def __init__(self, person, controller, data, current_schedule) -> None:
|
||||
"""Initialize a new Rachio Schedule."""
|
||||
self._schedule_id = data[KEY_ID]
|
||||
self._duration = data[KEY_DURATION]
|
||||
|
@ -509,3 +544,70 @@ class RachioSchedule(RachioSwitch):
|
|||
self.hass, SIGNAL_RACHIO_SCHEDULE_UPDATE, self._async_handle_update
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class RachioValve(CoordinatorEntity[RachioUpdateCoordinator], SwitchEntity):
|
||||
"""Representation of one smart hose timer valve."""
|
||||
|
||||
def __init__(
|
||||
self, person, base, data, coordinator: RachioUpdateCoordinator
|
||||
) -> None:
|
||||
"""Initialize a new smart hose valve."""
|
||||
super().__init__(coordinator)
|
||||
self._person = person
|
||||
self._base = base
|
||||
self.id = data[KEY_ID]
|
||||
self._attr_name = data[KEY_NAME]
|
||||
self._attr_unique_id = f"{self.id}-valve"
|
||||
self._static_attrs = data[KEY_STATE][KEY_REPORTED_STATE]
|
||||
self._attr_is_on = KEY_CURRENT_STATUS in self._static_attrs
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
(
|
||||
DOMAIN_RACHIO,
|
||||
self.id,
|
||||
)
|
||||
},
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self._base.mac_address)},
|
||||
manufacturer=DEFAULT_NAME,
|
||||
model="Smart Hose Timer",
|
||||
name=self._attr_name,
|
||||
configuration_url="https://app.rach.io",
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the valve is available."""
|
||||
return super().available and self._static_attrs[KEY_CONNECTED]
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on this valve."""
|
||||
if ATTR_DURATION in kwargs:
|
||||
manual_run_time = timedelta(minutes=kwargs[ATTR_DURATION])
|
||||
else:
|
||||
manual_run_time = timedelta(
|
||||
minutes=self._person.config_entry.options.get(
|
||||
CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS
|
||||
)
|
||||
)
|
||||
|
||||
self._base.start_watering(self.id, manual_run_time.seconds)
|
||||
self._attr_is_on = True
|
||||
self.schedule_update_ha_state(force_refresh=True)
|
||||
_LOGGER.debug("Starting valve %s for %s", self.name, str(manual_run_time))
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off this valve."""
|
||||
self._base.stop_watering(self.id)
|
||||
self._attr_is_on = False
|
||||
self.schedule_update_ha_state(force_refresh=True)
|
||||
_LOGGER.debug("Stopping watering on valve %s", self.name)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated coordinator data."""
|
||||
data = self.coordinator.data[self.id]
|
||||
|
||||
self._static_attrs = data[KEY_STATE][KEY_REPORTED_STATE]
|
||||
self._attr_is_on = KEY_CURRENT_STATUS in self._static_attrs
|
||||
super()._handle_coordinator_update()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue