From 199464f084105143692cfc68cecb59c7571447a9 Mon Sep 17 00:00:00 2001
From: Brian Rogers <brg468@hotmail.com>
Date: Tue, 20 Oct 2020 14:14:39 -0400
Subject: [PATCH] Add rachio service to start multiple zones (#42092)

---
 homeassistant/components/rachio/const.py      |  1 +
 homeassistant/components/rachio/device.py     |  4 ++
 homeassistant/components/rachio/services.yaml |  9 +++
 homeassistant/components/rachio/switch.py     | 62 +++++++++++++++++--
 4 files changed, 71 insertions(+), 5 deletions(-)

diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py
index a25d329c7c1..943e1b33199 100644
--- a/homeassistant/components/rachio/const.py
+++ b/homeassistant/components/rachio/const.py
@@ -52,6 +52,7 @@ STATUS_ONLINE = "ONLINE"
 SCHEDULE_TYPE_FIXED = "FIXED"
 SCHEDULE_TYPE_FLEX = "FLEX"
 SERVICE_SET_ZONE_MOISTURE = "set_zone_moisture_percent"
+SERVICE_START_MULTIPLE_ZONES = "start_multiple_zone_schedule"
 
 SIGNAL_RACHIO_UPDATE = f"{DOMAIN}_update"
 SIGNAL_RACHIO_CONTROLLER_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_controller"
diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py
index 64066ca7bd7..9d7c3057939 100644
--- a/homeassistant/components/rachio/device.py
+++ b/homeassistant/components/rachio/device.py
@@ -80,6 +80,10 @@ class RachioPerson:
         """Get a list of controllers managed by this account."""
         return self._controllers
 
+    def start_multiple_zones(self, zones) -> None:
+        """Start multiple zones."""
+        self.rachio.zone.start_multiple(zones)
+
 
 class RachioIro:
     """Represent a Rachio Iro."""
diff --git a/homeassistant/components/rachio/services.yaml b/homeassistant/components/rachio/services.yaml
index 0b22a0953a0..480d53aa454 100644
--- a/homeassistant/components/rachio/services.yaml
+++ b/homeassistant/components/rachio/services.yaml
@@ -7,3 +7,12 @@ set_zone_moisture_percent:
     percent:
       description: Set the desired zone moisture percentage from 0 to 100. [Required]
       example: 50
+start_multiple_zone_schedule:
+  description: Create a custom schedule of zones and runtimes.
+  fields:
+    entity_id:
+      description: Name of the zone or zones to run. Zones should all be on the same controller, attempting to start zones on multiple controllers may have undesired results. [Required]
+      example: "switch.front_yard, switch.side_yard"
+    duration:
+      description: Number of minutes to run the zone(s). If only 1 duration is given, that time will be used for all zones. If given a list of durations, the durations will apply to the respective zone listed above. [Required]
+      example: 15, 20
diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py
index 4af61d4f71b..8009d79b224 100644
--- a/homeassistant/components/rachio/switch.py
+++ b/homeassistant/components/rachio/switch.py
@@ -6,7 +6,9 @@ import logging
 import voluptuous as vol
 
 from homeassistant.components.switch import SwitchEntity
+from homeassistant.const import ATTR_ENTITY_ID
 from homeassistant.core import callback
+from homeassistant.exceptions import HomeAssistantError
 from homeassistant.helpers import config_validation as cv, entity_platform
 from homeassistant.helpers.dispatcher import async_dispatcher_connect
 from homeassistant.helpers.event import async_track_point_in_utc_time
@@ -37,6 +39,7 @@ from .const import (
     SCHEDULE_TYPE_FIXED,
     SCHEDULE_TYPE_FLEX,
     SERVICE_SET_ZONE_MOISTURE,
+    SERVICE_START_MULTIPLE_ZONES,
     SIGNAL_RACHIO_CONTROLLER_UPDATE,
     SIGNAL_RACHIO_RAIN_DELAY_UPDATE,
     SIGNAL_RACHIO_SCHEDULE_UPDATE,
@@ -63,32 +66,81 @@ from .webhooks import (
 
 _LOGGER = logging.getLogger(__name__)
 
+ATTR_DURATION = "duration"
+ATTR_ID = "id"
 ATTR_PERCENT = "percent"
 ATTR_SCHEDULE_SUMMARY = "Summary"
 ATTR_SCHEDULE_ENABLED = "Enabled"
 ATTR_SCHEDULE_DURATION = "Duration"
 ATTR_SCHEDULE_TYPE = "Type"
+ATTR_SORT_ORDER = "sortOrder"
 ATTR_ZONE_NUMBER = "Zone number"
 ATTR_ZONE_SHADE = "Shade"
 ATTR_ZONE_SLOPE = "Slope"
 ATTR_ZONE_SUMMARY = "Summary"
 ATTR_ZONE_TYPE = "Type"
 
+START_MULTIPLE_ZONES_SCHEMA = vol.Schema(
+    {
+        vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
+        vol.Required(ATTR_DURATION): cv.ensure_list_csv,
+    }
+)
+
 
 async def async_setup_entry(hass, config_entry, async_add_entities):
     """Set up the Rachio switches."""
+    zone_entities = []
     has_flex_sched = False
     entities = await hass.async_add_executor_job(_create_entities, hass, config_entry)
     for entity in entities:
+        if isinstance(entity, RachioZone):
+            zone_entities.append(entity)
         if isinstance(entity, RachioSchedule) and entity.type == SCHEDULE_TYPE_FLEX:
             has_flex_sched = True
-            break
 
     async_add_entities(entities)
     _LOGGER.info("%d Rachio switch(es) added", len(entities))
 
-    platform = entity_platform.current_platform.get()
+    def start_multiple(service):
+        """Service to start multiple zones in sequence."""
+        zones_list = []
+        person = hass.data[DOMAIN_RACHIO][config_entry.entry_id]
+        entity_id = service.data[ATTR_ENTITY_ID]
+        duration = iter(service.data[ATTR_DURATION])
+        default_time = service.data[ATTR_DURATION][0]
+        entity_to_zone_id = {
+            entity.entity_id: entity.zone_id for entity in zone_entities
+        }
+
+        for (count, data) in enumerate(entity_id):
+            if data in entity_to_zone_id:
+                # Time can be passed as a list per zone,
+                # or one time for all zones
+                time = int(next(duration, default_time)) * 60
+                zones_list.append(
+                    {
+                        ATTR_ID: entity_to_zone_id.get(data),
+                        ATTR_DURATION: time,
+                        ATTR_SORT_ORDER: count,
+                    }
+                )
+
+        if len(zones_list) != 0:
+            person.start_multiple_zones(zones_list)
+            _LOGGER.debug("Starting zone(s) %s", entity_id)
+        else:
+            raise HomeAssistantError("No matching zones found in given entity_ids")
+
+    hass.services.async_register(
+        DOMAIN_RACHIO,
+        SERVICE_START_MULTIPLE_ZONES,
+        start_multiple,
+        schema=START_MULTIPLE_ZONES_SCHEMA,
+    )
+
     if has_flex_sched:
+        platform = entity_platform.current_platform.get()
         platform.async_register_entity_service(
             SERVICE_SET_ZONE_MOISTURE,
             {vol.Required(ATTR_PERCENT): cv.positive_int},
@@ -289,7 +341,7 @@ class RachioZone(RachioSwitch):
 
     def __init__(self, person, controller, data, current_schedule):
         """Initialize a new Rachio Zone."""
-        self._id = data[KEY_ID]
+        self.id = data[KEY_ID]
         self._zone_name = data[KEY_NAME]
         self._zone_number = data[KEY_ZONE_NUMBER]
         self._zone_enabled = data[KEY_ENABLED]
@@ -309,7 +361,7 @@ class RachioZone(RachioSwitch):
     @property
     def zone_id(self) -> str:
         """How the Rachio API refers to the zone."""
-        return self._id
+        return self.id
 
     @property
     def name(self) -> str:
@@ -381,7 +433,7 @@ class RachioZone(RachioSwitch):
     def set_moisture_percent(self, percent) -> None:
         """Set the zone moisture percent."""
         _LOGGER.debug("Setting %s moisture to %s percent", self._zone_name, percent)
-        self._controller.rachio.zone.set_moisture_percent(self._id, percent / 100)
+        self._controller.rachio.zone.set_moisture_percent(self.id, percent / 100)
 
     @callback
     def _async_handle_update(self, *args, **kwargs) -> None: