From 3861c77f948f71b8559b5c04d592b64e6b8c6b75 Mon Sep 17 00:00:00 2001 From: K Sand <35273571+ksand012@users.noreply.github.com> Date: Sat, 21 Jan 2023 14:37:18 -0800 Subject: [PATCH 1/5] Add multi-controller support. --- hydrawiser/__init__.py | 4 +- hydrawiser/core.py | 91 ++++++++++++++++++++++++++++-------------- hydrawiser/helpers.py | 80 ++++++++++++++++++++++++++++++------- 3 files changed, 127 insertions(+), 48 deletions(-) diff --git a/hydrawiser/__init__.py b/hydrawiser/__init__.py index c93873e..4326fc8 100644 --- a/hydrawiser/__init__.py +++ b/hydrawiser/__init__.py @@ -6,6 +6,6 @@ """ __author__ = 'David Ryan' -__version__ = '0.2' -__copyright__ = 'Copyright (c) 2020 David Ryan' +__version__ = '0.3' +__copyright__ = 'Copyright (c) 2023 David Ryan' __license__ = 'MIT' diff --git a/hydrawiser/core.py b/hydrawiser/core.py index 65cf9f9..5352f66 100644 --- a/hydrawiser/core.py +++ b/hydrawiser/core.py @@ -27,13 +27,16 @@ def __init__(self, user_token): self.controller_info = [] self.controller_status = [] self.current_controller = [] - self.status = None - self.controller_id = None + self.statuses = [] + self.controller_ids_info = [] + self.controller_ids = [] self.customer_id = None - self.num_relays = None + self.num_relays = 0 self.relays = [] + self.controllers_to_relays = [] + self.controllers_to_sensors = [] self.status = None - self.name = None + self.controller_names = [] self.sensors = [] self.running = None @@ -47,31 +50,56 @@ def update_controller_info(self): :rtype: boolean """ - # Read the controller information. + # Read the customer information and gather all controller info. self.controller_info = customer_details(self._user_token) - self.controller_status = status_schedule(self._user_token) - if self.controller_info is None or self.controller_status is None: + if self.controller_info is None: return False - # Only supports one controller right now. - # Use the first one from the array. - self.current_controller = self.controller_info['controllers'][0] - self.status = self.current_controller['status'] - self.controller_id = self.current_controller['controller_id'] self.customer_id = self.controller_info['customer_id'] - self.num_relays = len(self.controller_status['relays']) - self.relays = self.controller_status['relays'] - self.name = self.controller_info['controllers'][0]['name'] - self.sensors = self.controller_status['sensors'] - try: - self.running = self.controller_status['running'] - except KeyError: - self.running = None - return True + self.controller_ids_info = self.controller_info['controllers'] + + # Hydrawise supports a maximum of 5 controllers. + + for x in self.controller_ids_info: + self.controller_status = status_schedule(self._user_token, self.controller_info['controllers'][x]['controller_id']) + + if self.controller_status is None: + continue + + # Use the current one from the array. + + # These arrays are meant for keeping track of all controller information. + self.current_controller = self.controller_info['controllers'][x] + self.controller_names.append(self.controller_info['controllers'][x]['name']) + self.statuses.append(self.current_controller['status']) + self.controller_ids.append(self.current_controller['controller_id']) + + + # These arrays are meant for keeping track of all relay/sensor information. + + self.num_relays += len(self.controller_status['relays']) + self.relays.append(self.controller_status['relays']) + + # We keep an array in-sync with all relays and which controller ID they belong to. + for y in len(self.controller_status['relays']): + self.controllers_to_relays.append(self.current_controller['controller_id']) + + self.sensors.append(self.controller_status['sensors']) + + # We keep an array in-sync with all sensors and which controller ID they belong to. + for z in len(self.controller_status['sensors']): + self.controllers_to_sensors.append(self.current_controller['controller_id']) + + try: + self.running = self.controller_status['running'] + except KeyError: + self.running = None + + return True - def controller(self): + def controllers(self): """ Check if multiple controllers are connected. @@ -79,12 +107,8 @@ def controller(self): :rtype: string """ - if hasattr(self, 'controller_id'): - if len(self.controller_info['controllers']) > 1: - raise TypeError( - 'Only one controller per account is supported.' - ) - return self.controller_id + if hasattr(self, 'controller_ids'): + return self.controller_ids raise AttributeError('No controllers assigned to this account.') def __repr__(self): @@ -95,7 +119,7 @@ def __repr__(self): """ return "<{0}: {1}>".format(self.__class__.__name__, - self.controller_id) + self.controller_ids) def relay_info(self, relay, attribute=None): """ @@ -140,12 +164,14 @@ def suspend_zone(self, days, zone=None): if zone is None: zone_cmd = 'suspendall' relay_id = None + controller_id = None else: if zone < 0 or zone > (len(self.relays) - 1): return None else: zone_cmd = 'suspend' relay_id = self.relays[zone]['relay_id'] + controller_id = self.controllers_to_relays[zone] # If days is 0 then remove suspension if days <= 0: @@ -154,7 +180,7 @@ def suspend_zone(self, days, zone=None): # 1 day = 60 * 60 * 24 seconds = 86400 time_cmd = time.mktime(time.localtime()) + (days * 86400) - return set_zones(self._user_token, zone_cmd, relay_id, time_cmd) + return set_zones(self._user_token, zone_cmd, relay_id, controller_id, time_cmd) def run_zone(self, minutes, zone=None): """ @@ -171,23 +197,26 @@ def run_zone(self, minutes, zone=None): if zone is None: zone_cmd = 'runall' relay_id = None + controller_id = None else: if zone < 0 or zone > (len(self.relays) - 1): return None else: zone_cmd = 'run' relay_id = self.relays[zone]['relay_id'] + controller_id = self.controllers_to_relays[zone] if minutes <= 0: time_cmd = 0 if zone is None: zone_cmd = 'stopall' + controller_id = None else: zone_cmd = 'stop' else: time_cmd = minutes * 60 - return set_zones(self._user_token, zone_cmd, relay_id, time_cmd) + return set_zones(self._user_token, zone_cmd, relay_id, controller_id, time_cmd) def list_running_zones(self): """ diff --git a/hydrawiser/helpers.py b/hydrawiser/helpers.py index 84a0265..8b4c64e 100644 --- a/hydrawiser/helpers.py +++ b/hydrawiser/helpers.py @@ -32,6 +32,34 @@ def status_schedule(token): return None +def status_schedule(token, controller_id): + """ + Returns the json string from the Hydrawise server after calling + statusschedule.php. + + :param token: The users API token. + :type token: string + :param controller_id: The specific controller ID we are looking at. + :type controller_id: string + :returns: The response from the controller. If there was an error returns + None. + :rtype: string or None + """ + + url = 'https://app.hydrawise.com/api/v1/statusschedule.php' + + payload = { + 'api_key': token, + 'controller_id': controller_id} + + get_response = requests.get(url, params=payload, timeout=REQUESTS_TIMEOUT) + + if get_response.status_code == 200 and \ + 'error_msg' not in get_response.json(): + return get_response.json() + + return None + def customer_details(token): """ @@ -60,7 +88,7 @@ def customer_details(token): return None -def set_zones(token, action, relay=None, time=None): +def set_zones(token, action, relay=None, controller=None, time=None): """ Controls the zone relays to turn sprinklers on and off. @@ -72,6 +100,10 @@ def set_zones(token, action, relay=None, time=None): :param relay: The zone to take action on. If no zone is specified then the action will be on all zones. :type relay: int or None + :param controller: The controller that the zone is attached to. If no zone + is specified then the action will be on all zones of the + primary controller. + :type controller: int or None :param time: The number of seconds to run or unix epoch time to suspend. :type time: int or None :returns: The response from the controller. If there was an error returns @@ -115,20 +147,38 @@ def set_zones(token, action, relay=None, time=None): # If action is on a single relay then make sure a relay is specified. if action in ['stop', 'run', 'suspend'] and relay is None: return None + if controller is None: + get_response = requests.get('https://app.hydrawise.com/api/v1/' + 'setzone.php?' + '&api_key={}' + '&action={}{}{}{}' + .format(token, + action, + relay_cmd, + period_cmd, + custom_cmd), + timeout=REQUESTS_TIMEOUT) + + if get_response.status_code == 200 and \ + 'error_msg' not in get_response.json(): + return get_response.json() - get_response = requests.get('https://app.hydrawise.com/api/v1/' - 'setzone.php?' - '&api_key={}' - '&action={}{}{}{}' - .format(token, - action, - relay_cmd, - period_cmd, - custom_cmd), - timeout=REQUESTS_TIMEOUT) - - if get_response.status_code == 200 and \ - 'error_msg' not in get_response.json(): - return get_response.json() + else: + get_response = requests.get('https://app.hydrawise.com/api/v1/' + 'setzone.php?' + '&api_key={}' + '&action={}{}{}{}' + '&controller={}' + .format(token, + action, + relay_cmd, + period_cmd, + custom_cmd, + controller), + timeout=REQUESTS_TIMEOUT) + + if get_response.status_code == 200 and \ + 'error_msg' not in get_response.json(): + return get_response.json() return None From cce4f86388a75c9bd513bb8d47841bb37be4b0e8 Mon Sep 17 00:00:00 2001 From: K Sand <35273571+ksand012@users.noreply.github.com> Date: Sat, 21 Jan 2023 14:41:36 -0800 Subject: [PATCH 2/5] Update README and version in setup file. --- README.md | 6 ++---- setup.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ab05e26..90f074f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ This is a Python 2 and 3 library for controlling the [Hunter](https://www.hunterindustries.com) Pro-HC sprinkler controller. +* More than one controller is NOW supported! + *Note that this project has no official relationship to Hunter Industries. It was developed using the Hydrawise API v1.4. Use at your own risk.* Hydrawise Youtube video: https://youtu.be/raOEK8JjSUA
@@ -74,7 +76,3 @@ True hw.time_remaining(3) 247 ``` - -## Limitations - -* Only one controller is supported diff --git a/setup.py b/setup.py index 3e043d9..77a8d98 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='Hydrawiser', packages=['hydrawiser'], - version='0.2', + version='0.3', description='A Python library to communicate with Hunter ' + 'Wi-Fi irrigation controllers ' + '(https://www.hunter.com) that support the ' + From b588eba13de2a4c0090a8ac03edc285e5a404fbd Mon Sep 17 00:00:00 2001 From: K Sand <35273571+ksand012@users.noreply.github.com> Date: Sat, 21 Jan 2023 14:43:36 -0800 Subject: [PATCH 3/5] Update docs to remove limitation. --- docs/conf.py | 2 +- docs/index.rst | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 217fbb0..4bec338 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # -- Project information ----------------------------------------------------- project = 'Hydrawiser' -copyright = '2018, David Ryan' +copyright = '2023, David Ryan' author = 'David Ryan' # The short X.Y version diff --git a/docs/index.rst b/docs/index.rst index 52f5e02..da47e81 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -83,8 +83,6 @@ The following are examples for using the Hydrawiser library. Limitations =========== -* Only one controller is supported - .. toctree:: :maxdepth: 4 :caption: Contents: From b7c5279f115f2c246b1431461463fbc321eef505 Mon Sep 17 00:00:00 2001 From: K Sand <35273571+ksand012@users.noreply.github.com> Date: Sat, 21 Jan 2023 14:49:34 -0800 Subject: [PATCH 4/5] Update README with current limitations. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 90f074f..70f627f 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,6 @@ True hw.time_remaining(3) 247 ``` + +# Limitations +* 'runall', 'stopall', and 'suspendall' commands only work on the primary controller. From 0b38c471d8140a61c9499db2a56c4e13c6850558 Mon Sep 17 00:00:00 2001 From: K Sand <35273571+ksand012@users.noreply.github.com> Date: Sat, 21 Jan 2023 14:50:26 -0800 Subject: [PATCH 5/5] Update documentation with current limitations. --- docs/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index da47e81..9df2d2a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,8 @@ Welcome to Hydrawiser's documentation! ====================================== This is a Python 2 and 3 library for controlling the Hunter (https://www.hunterindustries.com) Pro-HC sprinkler controller. +More than one controller is NOW supported! + .. warning:: Note that this project has no official relationship to Hunter Industries. It was developed using the Hydrawise API (https://support.hydrawise.com/hc/en-us/article_attachments/205632298/Hydrawise_API.pdf). Use at your own risk. @@ -83,6 +85,8 @@ The following are examples for using the Hydrawiser library. Limitations =========== +* 'runall', 'stopall', and 'suspendall' commands only work on the primary controller. + .. toctree:: :maxdepth: 4 :caption: Contents: