From 0a05365b92a4e17f90293523e87d682da276aad2 Mon Sep 17 00:00:00 2001 From: brg468 Date: Sun, 29 Mar 2020 15:45:21 -0400 Subject: [PATCH] Add Rachio Schedules --- homeassistant/components/rachio/__init__.py | 9 +- homeassistant/components/rachio/const.py | 3 + homeassistant/components/rachio/switch.py | 105 ++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 7eaa76dedd4..772cd0085d3 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -35,6 +35,7 @@ from .const import ( KEY_TYPE, KEY_USERNAME, KEY_ZONES, + KEY_SCHEDULES, RACHIO_API_EXCEPTIONS, ) @@ -94,7 +95,7 @@ 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 SIGNAL_RACHIO_UPDATE = DOMAIN + "_update" @@ -258,6 +259,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) @@ -295,6 +297,7 @@ class RachioIro: for event_type in self.rachio.notification.getWebhookEventType()[1]: if event_type[KEY_NAME] in LISTEN_EVENT_TYPES: event_types.append({"id": event_type[KEY_ID]}) + # Register to listen to these events from the device url = self.rachio.webhook_url @@ -341,6 +344,10 @@ class RachioIro: return zone 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.""" diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index fb66d4378f1..27ab5e831a7 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" # Yes we really do get all these exceptions (hopefully rachiopy switches to requests) RACHIO_API_EXCEPTIONS = ( diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 5320d434d00..f0e5dbb8b54 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -9,11 +9,15 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( SIGNAL_RACHIO_CONTROLLER_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, + SIGNAL_RACHIO_SCHEDULE_UPDATE, SUBTYPE_SLEEP_MODE_OFF, SUBTYPE_SLEEP_MODE_ON, SUBTYPE_ZONE_COMPLETED, SUBTYPE_ZONE_STARTED, SUBTYPE_ZONE_STOPPED, + SUBTYPE_SCHEDULE_COMPLETED, + SUBTYPE_SCHEDULE_STOPPED, + SUBTYPE_SCHEDULE_STARTED, RachioDeviceInfoProvider, ) from .const import ( @@ -25,17 +29,23 @@ from .const import ( KEY_ID, KEY_IMAGE_URL, KEY_NAME, + KEY_DURATION, KEY_ON, KEY_SUBTYPE, KEY_SUMMARY, KEY_ZONE_ID, KEY_ZONE_NUMBER, + KEY_SCHEDULES, + KEY_SCHEDULE_ID, ) _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): @@ -54,11 +64,16 @@ 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) + _LOGGER.debug("Added schedule: %s", schedules) 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 @@ -265,3 +280,93 @@ class RachioZone(RachioSwitch): async_dispatcher_connect( self.hass, SIGNAL_RACHIO_ZONE_UPDATE, self._handle_update ) + +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._person = person + 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) + + def __str__(self): + """Display the schedule as a string.""" + return 'Rachio Schedule "{}" on {}'.format(self.name, str(self._controller)) + + @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 self._schedule_name + + @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 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 indvidual 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.""" + async_dispatcher_connect( + self.hass, SIGNAL_RACHIO_SCHEDULE_UPDATE, self._handle_update + ) +