Skip to content

Commit e2967d8

Browse files
authored
Merge pull request #46 from dknowles2/legacy
Add LegacyHydrawise
2 parents 6d95375 + 6f0897b commit e2967d8

File tree

6 files changed

+656
-1
lines changed

6 files changed

+656
-1
lines changed

pydrawise/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class NotAuthorizedError(Error):
1313
"""Raised when invalid credentials are used."""
1414

1515

16+
class NotInitializedError(Error):
17+
"""Raised when the legacy client is not initialized."""
18+
19+
1620
class MutationError(Error):
1721
"""Raised when there is an error performing a mutation."""
1822

pydrawise/legacy/__init__.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
""""API for interacting with Hydrawise sprinkler controllers.
2+
3+
This library should remain compatible with https://github.com/ptcryan/hydrawiser.
4+
"""
5+
6+
import time
7+
8+
import requests
9+
10+
from ..exceptions import NotInitializedError, UnknownError
11+
12+
_BASE_URL = "https://app.hydrawise.com/api/v1"
13+
_TIMEOUT = 10 # seconds
14+
15+
16+
class LegacyHydrawise:
17+
"""Client library for interacting with Hydrawise v1 API.
18+
19+
This should remain (mostly) compatible with https://github.com/ptcryan/hydrawiser
20+
"""
21+
22+
def __init__(self, user_token: str, load_on_init: bool = True) -> None:
23+
self._api_key = user_token
24+
self.controller_info = {}
25+
self.controller_status = {}
26+
if load_on_init:
27+
self.update_controller_info()
28+
29+
@property
30+
def current_controller(self) -> dict:
31+
controllers = self.controller_info.get("controllers", [])
32+
if not controllers:
33+
return {}
34+
return controllers[0]
35+
36+
@property
37+
def status(self) -> str | None:
38+
return self.current_controller.get("status")
39+
40+
@property
41+
def controller_id(self) -> int | None:
42+
return self.current_controller.get("controller_id")
43+
44+
@property
45+
def customer_id(self) -> int | None:
46+
return self.controller_info.get("customer_id")
47+
48+
@property
49+
def num_relays(self) -> int:
50+
return len(self.controller_status.get("relays", []))
51+
52+
@property
53+
def relays(self) -> list[dict]:
54+
relays = self.controller_status.get("relays", [])
55+
return sorted(relays, key=lambda r: r["relay"])
56+
57+
@property
58+
def relays_by_id(self) -> dict[int, dict]:
59+
return {r["relay_id"]: r for r in self.controller_status.get("relays", [])}
60+
61+
@property
62+
def relays_by_zone_number(self) -> dict[int, dict]:
63+
return {r["relay"]: r for r in self.controller_status.get("relays", [])}
64+
65+
@property
66+
def name(self) -> str | None:
67+
return self.current_controller.get("name")
68+
69+
@property
70+
def sensors(self) -> list[dict]:
71+
return self.controller_status.get("sensors", [])
72+
73+
@property
74+
def running(self) -> str | None:
75+
return self.controller_status.get("running")
76+
77+
def update_controller_info(self) -> None:
78+
self.controller_info = self._get_controller_info()
79+
self.controller_status = self._get_controller_status()
80+
81+
def _get(self, path: str, **kwargs) -> dict:
82+
url = f"{_BASE_URL}/{path}"
83+
params = {"api_key": self._api_key}
84+
params.update(kwargs)
85+
resp = requests.get(url, params=params, timeout=_TIMEOUT)
86+
87+
if resp.status_code != 200:
88+
resp.raise_for_status()
89+
90+
resp_json = resp.json()
91+
if "error_message" in resp_json:
92+
raise UnknownError(resp_json["error_message"])
93+
94+
return resp_json
95+
96+
def _get_controller_info(self) -> dict:
97+
return self._get("customerdetails.php", type="controllers")
98+
99+
def _get_controller_status(self) -> dict:
100+
return self._get("statusschedule.php")
101+
102+
def suspend_zone(self, days: int, zone: int | None = None) -> dict:
103+
params = {}
104+
105+
if days > 0:
106+
params["custom"] = time.time() + (days * 24 * 60 * 60)
107+
params["period_id"] = 999
108+
109+
if zone is None:
110+
params["action"] = "suspendall"
111+
return self._get("setzone.php", **params)
112+
113+
if not self.relays:
114+
raise NotInitializedError("No zones loaded")
115+
116+
params["action"] = "suspend"
117+
params["relay_id"] = self.relays_by_zone_number[zone]["relay_id"]
118+
return self._get("setzone.php", **params)
119+
120+
def run_zone(self, minutes: int, zone: int | None = None) -> dict:
121+
params = {}
122+
123+
if zone is not None:
124+
if not self.relays:
125+
raise NotInitializedError("No zones loaded")
126+
params["relay_id"] = self.relays_by_zone_number[zone]["relay_id"]
127+
params["action"] = "run" if minutes > 0 else "stop"
128+
else:
129+
params["action"] = "runall" if minutes > 0 else "stopall"
130+
131+
if minutes > 0:
132+
params["custom"] = time.time() + (minutes * 60)
133+
params["period_id"] = 999
134+
135+
return self._get("setzone.php", **params)

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description = "Python API for interacting with Hydrawise sprinkler controlle
88
authors = [
99
{name = "David Knowles", email = "dknowles2@gmail.com"},
1010
]
11-
dependencies = ["aiohttp ", "apischema", "gql[aiohttp]", "graphql-core"]
11+
dependencies = ["aiohttp ", "apischema", "gql[aiohttp]", "graphql-core", "requests"]
1212
requires-python = ">=3.10"
1313
dynamic = ["readme", "version"]
1414
license = {text = "Apache License 2.0"}

requirements-test.txt

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ gql[aiohttp]==3.4.1
66
graphql-core==3.2.3
77
pytest==7.4.0
88
pytest-asyncio==0.21.0
9+
requests==2.31.0

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ aiohttp==3.8.4
22
apischema==0.18.0
33
gql[aiohttp]==3.4.1
44
graphql-core==3.2.3
5+
requests==2.31.0

0 commit comments

Comments
 (0)