From bdf274d7918e1c251e74fc1c6e09d7fb930842f6 Mon Sep 17 00:00:00 2001 From: brg468 Date: Sun, 26 Apr 2020 11:15:02 -0400 Subject: [PATCH 1/4] Add Rachio Rain Delay Switch --- homeassistant/components/rachio/const.py | 2 + homeassistant/components/rachio/switch.py | 71 ++++++++++++++++++++- homeassistant/components/rachio/webhooks.py | 6 ++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index 3508e24eb4b644..99e26f6383566b 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -23,6 +23,7 @@ KEY_MODEL = "model" KEY_ON = "on" KEY_DURATION = "totalDuration" +KEY_RAIN_DELAY = "rainDelayExpirationDate" KEY_STATUS = "status" KEY_SUBTYPE = "subType" KEY_SUMMARY = "summary" @@ -56,6 +57,7 @@ SIGNAL_RACHIO_UPDATE = f"{DOMAIN}_update" SIGNAL_RACHIO_CONTROLLER_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_controller" +SIGNAL_RACHIO_RAIN_DELAY_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_rain_delay" SIGNAL_RACHIO_ZONE_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_zone" SIGNAL_RACHIO_SCHEDULE_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_schedule" diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index f9cfa6ab1220df..57d2ca500d2fdc 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -2,6 +2,7 @@ from abc import abstractmethod from datetime import timedelta import logging +import time from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback @@ -22,17 +23,21 @@ KEY_IMAGE_URL, KEY_NAME, KEY_ON, + KEY_RAIN_DELAY, KEY_SCHEDULE_ID, KEY_SUBTYPE, KEY_SUMMARY, KEY_ZONE_ID, KEY_ZONE_NUMBER, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_RAIN_DELAY_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, ) from .entity import RachioDevice from .webhooks import ( + SUBTYPE_RAIN_DELAY_OFF, + SUBTYPE_RAIN_DELAY_ON, SUBTYPE_SCHEDULE_COMPLETED, SUBTYPE_SCHEDULE_STARTED, SUBTYPE_SCHEDULE_STOPPED, @@ -67,6 +72,7 @@ def _create_entities(hass, config_entry): # in order to avoid every zone doing it for controller in person.controllers: entities.append(RachioStandbySwitch(controller)) + entities.append(RachioRainDelay(controller)) zones = controller.list_zones() schedules = controller.list_schedules() flex_schedules = controller.list_flex_schedules() @@ -179,6 +185,69 @@ async def async_added_to_hass(self): ) +class RachioRainDelay(RachioSwitch): + """Representation of a rain delay status/switch.""" + + def __init__(self, controller): + """Instantiate a new Rachio rain delay switch.""" + super().__init__(controller, poll=True) + self._poll_update(controller.init_data) + + @property + def name(self) -> str: + """Return the name of the switch.""" + return f"{self._controller.name} rain delay" + + @property + def unique_id(self) -> str: + """Return a unique id by combining controller id and purpose.""" + return f"{self._controller.controller_id}-delay" + + @property + def icon(self) -> str: + """Return an icon for rain delay.""" + return "mdi:camera-timer" + + def _poll_update(self, data=None) -> bool: + """Request the state from the API.""" + # API returns either 0 or currnent UNIX time when rain delay was canceled + # depending if it was canceled from the app or via the API + if data is None: + data = self._controller.rachio.device.get(self._controller.controller_id)[1] + + return data[KEY_RAIN_DELAY] / 1000 > time.time() + + @callback + def _async_handle_update(self, *args, **kwargs) -> None: + """Update the state using webhook data.""" + if args[0][0][KEY_SUBTYPE] == SUBTYPE_RAIN_DELAY_ON: + self._state = True + elif args[0][0][KEY_SUBTYPE] == SUBTYPE_RAIN_DELAY_OFF: + self._state = False + + self.async_write_ha_state() + + def turn_on(self, **kwargs) -> None: + """Activate a 24 hour rain delay on the controller.""" + self._controller.rachio.device.rainDelay(self._controller.controller_id, 86400) + _LOGGER.debug("Starting rain delay for 24 hours") + + def turn_off(self, **kwargs) -> None: + """Resume controller functionality.""" + self._controller.rachio.device.rainDelay(self._controller.controller_id, 0) + _LOGGER.debug("Canceling rain delay") + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_RACHIO_RAIN_DELAY_UPDATE, + self._async_handle_any_update, + ) + ) + + class RachioZone(RachioSwitch): """Representation of one zone of sprinklers connected to the Rachio Iro.""" @@ -320,7 +389,7 @@ def unique_id(self) -> str: @property def icon(self) -> str: """Return the icon to display.""" - return "mdi:water" + return "mdi:water" if self.schedule_is_enabled else "mdi:water-off" @property def device_state_attributes(self) -> dict: diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index a5960b8b28bacc..0586921f337efd 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -15,6 +15,7 @@ KEY_EXTERNAL_ID, KEY_TYPE, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_RAIN_DELAY_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, ) @@ -30,6 +31,9 @@ SUBTYPE_BROWNOUT_VALVE = "BROWNOUT_VALVE" SUBTYPE_RAIN_SENSOR_DETECTION_ON = "RAIN_SENSOR_DETECTION_ON" SUBTYPE_RAIN_SENSOR_DETECTION_OFF = "RAIN_SENSOR_DETECTION_OFF" + +# Rain dealay values +TYPE_RAIN_DELAY_STATUS = "RAIN_DELAY" SUBTYPE_RAIN_DELAY_ON = "RAIN_DELAY_ON" SUBTYPE_RAIN_DELAY_OFF = "RAIN_DELAY_OFF" @@ -55,6 +59,7 @@ LISTEN_EVENT_TYPES = [ "DEVICE_STATUS_EVENT", "ZONE_STATUS_EVENT", + "RAIN_DELAY_EVENT", "SCHEDULE_STATUS_EVENT", ] WEBHOOK_CONST_ID = "homeassistant.rachio:" @@ -62,6 +67,7 @@ SIGNAL_MAP = { TYPE_CONTROLLER_STATUS: SIGNAL_RACHIO_CONTROLLER_UPDATE, + TYPE_RAIN_DELAY_STATUS: SIGNAL_RACHIO_RAIN_DELAY_UPDATE, TYPE_SCHEDULE_STATUS: SIGNAL_RACHIO_SCHEDULE_UPDATE, TYPE_ZONE_STATUS: SIGNAL_RACHIO_ZONE_UPDATE, } From 37679f0a3ca413b9aef511e4bd08b69057fce354 Mon Sep 17 00:00:00 2001 From: brg468 Date: Sun, 26 Apr 2020 17:33:34 -0400 Subject: [PATCH 2/4] Typo --- homeassistant/components/rachio/switch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 57d2ca500d2fdc..33f321b830b7f3 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -210,8 +210,8 @@ def icon(self) -> str: def _poll_update(self, data=None) -> bool: """Request the state from the API.""" - # API returns either 0 or currnent UNIX time when rain delay was canceled - # depending if it was canceled from the app or via the API + # API returns either 0 or current UNIX time when rain delay was canceled + # depending if it was done from the app or via the API if data is None: data = self._controller.rachio.device.get(self._controller.controller_id)[1] From 6cb31066d7bd9572d7159a4d3966c16563d1767f Mon Sep 17 00:00:00 2001 From: brg468 Date: Sun, 26 Apr 2020 21:14:36 -0400 Subject: [PATCH 3/4] Catch KeyError --- homeassistant/components/rachio/switch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 33f321b830b7f3..541c870b99ebd8 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -215,7 +215,10 @@ def _poll_update(self, data=None) -> bool: if data is None: data = self._controller.rachio.device.get(self._controller.controller_id)[1] - return data[KEY_RAIN_DELAY] / 1000 > time.time() + try: + return data[KEY_RAIN_DELAY] / 1000 > time.time() + except KeyError: + return False @callback def _async_handle_update(self, *args, **kwargs) -> None: From edfeb3a0d9bf6b541c18153a97f00bf06fd5c36e Mon Sep 17 00:00:00 2001 From: brg468 Date: Sun, 26 Apr 2020 22:18:30 -0400 Subject: [PATCH 4/4] Use HA dt module in place of time --- homeassistant/components/rachio/switch.py | 4 ++-- homeassistant/components/rachio/webhooks.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 541c870b99ebd8..f1f2cb26687fb5 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -2,11 +2,11 @@ from abc import abstractmethod from datetime import timedelta import logging -import time from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.dt import as_timestamp, now from .const import ( ATTR_ZONE_SHADE, @@ -216,7 +216,7 @@ def _poll_update(self, data=None) -> bool: data = self._controller.rachio.device.get(self._controller.controller_id)[1] try: - return data[KEY_RAIN_DELAY] / 1000 > time.time() + return data[KEY_RAIN_DELAY] / 1000 > as_timestamp(now()) except KeyError: return False diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index 0586921f337efd..a3f95d5a5f3f8c 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -32,7 +32,7 @@ SUBTYPE_RAIN_SENSOR_DETECTION_ON = "RAIN_SENSOR_DETECTION_ON" SUBTYPE_RAIN_SENSOR_DETECTION_OFF = "RAIN_SENSOR_DETECTION_OFF" -# Rain dealay values +# Rain delay values TYPE_RAIN_DELAY_STATUS = "RAIN_DELAY" SUBTYPE_RAIN_DELAY_ON = "RAIN_DELAY_ON" SUBTYPE_RAIN_DELAY_OFF = "RAIN_DELAY_OFF"