diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index 13e8029b512..2c439407c71 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -22,6 +22,7 @@ KEY_ID = "id" KEY_NAME = "name" KEY_MODEL = "model" KEY_ON = "on" +KEY_DURATION = "totalDuration" KEY_STATUS = "status" KEY_SUBTYPE = "subType" KEY_SUMMARY = "summary" @@ -33,6 +34,8 @@ KEY_USERNAME = "username" KEY_ZONE_ID = "zoneId" KEY_ZONE_NUMBER = "zoneNumber" KEY_ZONES = "zones" +KEY_SCHEDULES = "scheduleRules" +KEY_SCHEDULE_ID = "scheduleId" KEY_CUSTOM_SHADE = "customShade" KEY_CUSTOM_CROP = "customCrop" diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py index 949957ae8ec..fbf49ffc67f 100644 --- a/homeassistant/components/rachio/device.py +++ b/homeassistant/components/rachio/device.py @@ -13,6 +13,7 @@ from .const import ( KEY_MAC_ADDRESS, KEY_MODEL, KEY_NAME, + KEY_SCHEDULES, KEY_SERIAL_NUMBER, KEY_STATUS, KEY_USERNAME, @@ -90,6 +91,7 @@ class RachioIro: self.mac_address = data[KEY_MAC_ADDRESS] self.model = data[KEY_MODEL] self._zones = data[KEY_ZONES] + self._schedules = data[KEY_SCHEDULES] self._init_data = data self._webhooks = webhooks _LOGGER.debug('%s has ID "%s"', str(self), self.controller_id) @@ -174,6 +176,10 @@ class RachioIro: return None + def list_schedules(self) -> list: + """Return a list of schedules.""" + return self._schedules + def stop_watering(self) -> None: """Stop watering all zones connected to this controller.""" self.rachio.device.stopWater(self.controller_id) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 5df084a11a4..a4ba1a41fee 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -15,20 +15,26 @@ from .const import ( KEY_CUSTOM_CROP, KEY_CUSTOM_SHADE, KEY_DEVICE_ID, + KEY_DURATION, KEY_ENABLED, KEY_ID, KEY_IMAGE_URL, KEY_NAME, KEY_ON, + KEY_SCHEDULE_ID, KEY_SUBTYPE, KEY_SUMMARY, KEY_ZONE_ID, KEY_ZONE_NUMBER, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, ) from .entity import RachioDevice from .webhooks import ( + SUBTYPE_SCHEDULE_COMPLETED, + SUBTYPE_SCHEDULE_STARTED, + SUBTYPE_SCHEDULE_STOPPED, SUBTYPE_SLEEP_MODE_OFF, SUBTYPE_SLEEP_MODE_ON, SUBTYPE_ZONE_COMPLETED, @@ -40,6 +46,9 @@ _LOGGER = logging.getLogger(__name__) ATTR_ZONE_SUMMARY = "Summary" ATTR_ZONE_NUMBER = "Zone number" +ATTR_SCHEDULE_SUMMARY = "Summary" +ATTR_SCHEDULE_ENABLED = "Enabled" +ATTR_SCHEDULE_DURATION = "Duration" async def async_setup_entry(hass, config_entry, async_add_entities): @@ -58,11 +67,14 @@ def _create_entities(hass, config_entry): for controller in person.controllers: entities.append(RachioStandbySwitch(controller)) zones = controller.list_zones() + schedules = controller.list_schedules() current_schedule = controller.current_schedule - _LOGGER.debug("Rachio setting up zones: %s", zones) for zone in zones: _LOGGER.debug("Rachio setting up zone: %s", zone) entities.append(RachioZone(person, controller, zone, current_schedule)) + for sched in schedules: + _LOGGER.debug("Added schedule: %s", sched) + entities.append(RachioSchedule(person, controller, sched, current_schedule)) return entities @@ -276,3 +288,98 @@ class RachioZone(RachioSwitch): """Unsubscribe from updates.""" if self._undo_dispatcher: self._undo_dispatcher() + + +class RachioSchedule(RachioSwitch): + """Representation of one fixed schedule on the Rachio Iro.""" + + def __init__(self, person, controller, data, current_schedule): + """Initialize a new Rachio Schedule.""" + self._id = data[KEY_ID] + self._schedule_name = data[KEY_NAME] + self._duration = data[KEY_DURATION] + self._schedule_enabled = data[KEY_ENABLED] + self._summary = data[KEY_SUMMARY] + self._current_schedule = current_schedule + super().__init__(controller, poll=False) + self._state = self.schedule_id == self._current_schedule.get(KEY_SCHEDULE_ID) + self._undo_dispatcher = None + + @property + def schedule_id(self) -> str: + """How the Rachio API refers to the schedule.""" + return self._id + + @property + def name(self) -> str: + """Return the friendly name of the schedule.""" + return f"{self._schedule_name} Schedule" + + @property + def unique_id(self) -> str: + """Return a unique id by combining controller id and schedule.""" + return f"{self._controller.controller_id}-schedule-{self.schedule_id}" + + @property + def icon(self) -> str: + """Return the icon to display.""" + return "mdi:water" + + @property + def device_state_attributes(self) -> dict: + """Return the optional state attributes.""" + return { + ATTR_SCHEDULE_SUMMARY: self._summary, + ATTR_SCHEDULE_ENABLED: self.schedule_is_enabled, + ATTR_SCHEDULE_DURATION: self._duration / 60, + } + + @property + def schedule_is_enabled(self) -> bool: + """Return whether the schedule is allowed to run.""" + return self._schedule_enabled + + def turn_on(self, **kwargs) -> None: + """Start this schedule.""" + + self._controller.rachio.schedulerule.start(self.schedule_id) + _LOGGER.debug( + "Schedule %s started on %s", self.name, self._controller.name, + ) + + def turn_off(self, **kwargs) -> None: + """Stop watering all zones.""" + self._controller.stop_watering() + + def _poll_update(self, data=None) -> bool: + """Poll the API to check whether the schedule is running.""" + self._current_schedule = self._controller.current_schedule + return self.schedule_id == self._current_schedule.get(KEY_SCHEDULE_ID) + + def _handle_update(self, *args, **kwargs) -> None: + """Handle incoming webhook schedule data.""" + # Schedule ID not passed when running individual zones, so we catch that error + try: + if args[0][KEY_SCHEDULE_ID] == self.schedule_id: + if args[0][KEY_SUBTYPE] in [SUBTYPE_SCHEDULE_STARTED]: + self._state = True + elif args[0][KEY_SUBTYPE] in [ + SUBTYPE_SCHEDULE_STOPPED, + SUBTYPE_SCHEDULE_COMPLETED, + ]: + self._state = False + except KeyError: + pass + + self.schedule_update_ha_state() + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self._undo_dispatcher = async_dispatcher_connect( + self.hass, SIGNAL_RACHIO_SCHEDULE_UPDATE, self._handle_update + ) + + async def async_will_remove_from_hass(self): + """Unsubscribe from updates.""" + if self._undo_dispatcher: + self._undo_dispatcher() diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index c12f2ccfd3e..7051ec046d8 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -50,7 +50,11 @@ SUBTYPE_ZONE_CYCLING = "ZONE_CYCLING" SUBTYPE_ZONE_CYCLING_COMPLETED = "ZONE_CYCLING_COMPLETED" # Webhook callbacks -LISTEN_EVENT_TYPES = ["DEVICE_STATUS_EVENT", "ZONE_STATUS_EVENT"] +LISTEN_EVENT_TYPES = [ + "DEVICE_STATUS_EVENT", + "ZONE_STATUS_EVENT", + "SCHEDULE_STATUS_EVENT", +] WEBHOOK_CONST_ID = "homeassistant.rachio:" WEBHOOK_PATH = URL_API + DOMAIN