Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<br/>
Expand Down Expand Up @@ -75,6 +77,5 @@ hw.time_remaining(3)
247
```

## Limitations

* Only one controller is supported
# Limitations
* 'runall', 'stopall', and 'suspendall' commands only work on the primary controller.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# -- Project information -----------------------------------------------------

project = 'Hydrawiser'
copyright = '2018, David Ryan'
copyright = '2023, David Ryan'
author = 'David Ryan'

# The short X.Y version
Expand Down
4 changes: 3 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -83,7 +85,7 @@ The following are examples for using the Hydrawiser library.
Limitations
===========

* Only one controller is supported
* 'runall', 'stopall', and 'suspendall' commands only work on the primary controller.

.. toctree::
:maxdepth: 4
Expand Down
4 changes: 2 additions & 2 deletions hydrawiser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
91 changes: 60 additions & 31 deletions hydrawiser/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -47,44 +50,65 @@ 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.

:returns: Return the controller_id of the active controller.
: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):
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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:
Expand All @@ -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):
"""
Expand All @@ -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):
"""
Expand Down
80 changes: 65 additions & 15 deletions hydrawiser/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ' +
Expand Down