Skip to content

Commit dca4c8a

Browse files
committed
Add Rachio Irrigation API Example
Example pulls data from your Rachio Irrigation Timer. This is just a basic example. More can be done with zone scheduling, actually running the irrigation system, etc.. will work on an advanced example in the future. This should be good enough for very basic communication with the API and timer.
1 parent fac4012 commit dca4c8a

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# SPDX-FileCopyrightText: 2024 DJDevon3
2+
# SPDX-License-Identifier: MIT
3+
# Coded for Circuit Python 9.x
4+
"""Rachio Irrigation Timer API Example"""
5+
6+
import os
7+
import time
8+
9+
import adafruit_connection_manager
10+
import wifi
11+
12+
import adafruit_requests
13+
14+
# Rachio API Key required (comes with purchase of a device)
15+
# API is rate limited to 1700 calls per day.
16+
# https://support.rachio.com/en_us/public-api-documentation-S1UydL1Fv
17+
# https://rachio.readme.io/reference/getting-started
18+
RACHIO_KEY = os.getenv("RACHIO_APIKEY")
19+
20+
# Get WiFi details, ensure these are setup in settings.toml
21+
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
22+
password = os.getenv("CIRCUITPY_WIFI_PASSWORD")
23+
24+
# API Polling Rate
25+
# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour
26+
SLEEP_TIME = 900
27+
28+
# Set debug to True for full JSON response.
29+
# WARNING: absolutely shows extremely sensitive personal information & credentials
30+
# Including your real name, latitude, longitude, account id, mac address, etc...
31+
DEBUG = False
32+
33+
# Initalize Wifi, Socket Pool, Request Session
34+
pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
35+
ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
36+
requests = adafruit_requests.Session(pool, ssl_context)
37+
38+
RACHIO_HEADER = {"Authorization": " Bearer " + RACHIO_KEY}
39+
RACHIO_SOURCE = "https://api.rach.io/1/public/person/info/"
40+
RACHIO_PERSON_SOURCE = "https://api.rach.io/1/public/person/"
41+
42+
43+
def obfuscating_asterix(obfuscate_object, direction, characters=2):
44+
"""
45+
Obfuscates a string with asterisks except for a specified number of characters.
46+
param object: str The string to obfuscate with asterisks
47+
param direction: str Option either 'prepend', 'append', or 'all' direction
48+
param characters: int The number of characters to keep unobfuscated (default is 2)
49+
"""
50+
object_len = len(obfuscate_object)
51+
if direction not in {"prepend", "append", "all"}:
52+
raise ValueError("Invalid direction. Use 'prepend', 'append', or 'all'.")
53+
if characters >= object_len and direction != "all":
54+
# If characters greater than or equal to string length,
55+
# return the original string as it can't be obfuscated.
56+
return obfuscate_object
57+
asterix_replace = "*" * object_len
58+
if direction == "append":
59+
asterix_final = obfuscate_object[:characters] + "*" * (object_len - characters)
60+
elif direction == "prepend":
61+
asterix_final = "*" * (object_len - characters) + obfuscate_object[-characters:]
62+
elif direction == "all":
63+
# Replace all characters with asterisks
64+
asterix_final = asterix_replace
65+
66+
return asterix_final
67+
68+
69+
def time_calc(input_time):
70+
"""Converts seconds to minutes/hours/days"""
71+
if input_time < 60:
72+
return f"{input_time:.0f} seconds"
73+
if input_time < 3600:
74+
return f"{input_time / 60:.0f} minutes"
75+
if input_time < 86400:
76+
return f"{input_time / 60 / 60:.0f} hours"
77+
return f"{input_time / 60 / 60 / 24:.1f} days"
78+
79+
80+
def _format_datetime(datetime):
81+
"""F-String formatted struct time conversion"""
82+
return (
83+
f"{datetime.tm_mon:02}/"
84+
+ f"{datetime.tm_mday:02}/"
85+
+ f"{datetime.tm_year:02} "
86+
+ f"{datetime.tm_hour:02}:"
87+
+ f"{datetime.tm_min:02}:"
88+
+ f"{datetime.tm_sec:02}"
89+
)
90+
91+
92+
while True:
93+
# Connect to Wi-Fi
94+
print("\nConnecting to WiFi...")
95+
while not wifi.radio.ipv4_address:
96+
try:
97+
wifi.radio.connect(ssid, password)
98+
except ConnectionError as e:
99+
print("❌ Connection Error:", e)
100+
print("Retrying in 10 seconds")
101+
print("✅ Wifi!")
102+
103+
try:
104+
print(" | Attempting to GET Rachio Authorization")
105+
try:
106+
with requests.get(
107+
url=RACHIO_SOURCE, headers=RACHIO_HEADER
108+
) as rachio_response:
109+
rachio_json = rachio_response.json()
110+
except ConnectionError as e:
111+
print("Connection Error:", e)
112+
print("Retrying in 10 seconds")
113+
print(" | ✅ Authorized")
114+
115+
rachio_id = rachio_json["id"]
116+
if DEBUG:
117+
print(" | | Person ID: ", rachio_id)
118+
print(" | | This ID will be used for subsequent calls")
119+
print("\nFull API GET URL: ", RACHIO_SOURCE)
120+
print(rachio_json)
121+
122+
except (ValueError, RuntimeError) as e:
123+
print(f"Failed to get data, retrying\n {e}")
124+
time.sleep(60)
125+
break
126+
127+
try:
128+
print(" | Attempting to GET Rachio JSON")
129+
try:
130+
with requests.get(
131+
url=RACHIO_PERSON_SOURCE + rachio_id, headers=RACHIO_HEADER
132+
) as rachio_response:
133+
rachio_json = rachio_response.json()
134+
except ConnectionError as e:
135+
print("Connection Error:", e)
136+
print("Retrying in 10 seconds")
137+
print(" | ✅ Rachio JSON")
138+
139+
rachio_id = rachio_json["id"]
140+
rachio_id_ast = obfuscating_asterix(rachio_id, "append", 3)
141+
print(" | | UserID: ", rachio_id_ast)
142+
143+
rachio_username = rachio_json["username"]
144+
rachio_username_ast = obfuscating_asterix(rachio_username, "append", 3)
145+
print(" | | Username: ", rachio_username_ast)
146+
147+
rachio_name = rachio_json["fullName"]
148+
rachio_name_ast = obfuscating_asterix(rachio_name, "append", 3)
149+
print(" | | Full Name: ", rachio_name_ast)
150+
151+
rachio_deleted = rachio_json["deleted"]
152+
if not rachio_deleted:
153+
print(" | | Account Status: Active")
154+
else:
155+
print(" | | Account Status?: Deleted!")
156+
157+
rachio_createdate = rachio_json["createDate"]
158+
rachio_timezone_offset = rachio_json["devices"][0]["utcOffset"]
159+
# Rachio Unix time is in milliseconds, convert to seconds
160+
rachio_createdate_seconds = rachio_createdate // 1000
161+
rachio_timezone_offset_seconds = rachio_timezone_offset // 1000
162+
# Apply timezone offset in seconds
163+
local_unix_time = rachio_createdate_seconds + rachio_timezone_offset_seconds
164+
if DEBUG:
165+
print(f" | | Unix Registration Date: {rachio_createdate}")
166+
print(f" | | Unix Timezone Offset: {rachio_timezone_offset}")
167+
current_struct_time = time.localtime(local_unix_time)
168+
final_timestamp = "{}".format(_format_datetime(current_struct_time))
169+
print(f" | | Registration Date: {final_timestamp}")
170+
171+
rachio_devices = rachio_json["devices"][0]["name"]
172+
print(" | | Device: ", rachio_devices)
173+
174+
rachio_model = rachio_json["devices"][0]["model"]
175+
print(" | | | Model: ", rachio_model)
176+
177+
rachio_serial = rachio_json["devices"][0]["serialNumber"]
178+
rachio_serial_ast = obfuscating_asterix(rachio_serial, "append")
179+
print(" | | | Serial Number: ", rachio_serial_ast)
180+
181+
rachio_mac = rachio_json["devices"][0]["macAddress"]
182+
rachio_mac_ast = obfuscating_asterix(rachio_mac, "append")
183+
print(" | | | MAC Address: ", rachio_mac_ast)
184+
185+
rachio_status = rachio_json["devices"][0]["status"]
186+
print(" | | | Device Status: ", rachio_status)
187+
188+
rachio_timezone = rachio_json["devices"][0]["timeZone"]
189+
print(" | | | Time Zone: ", rachio_timezone)
190+
191+
# Latitude & Longtitude are used for smart watering & rain delays
192+
rachio_latitude = str(rachio_json["devices"][0]["latitude"])
193+
rachio_lat_ast = obfuscating_asterix(rachio_latitude, "all")
194+
print(" | | | Latitude: ", rachio_lat_ast)
195+
196+
rachio_longitude = str(rachio_json["devices"][0]["longitude"])
197+
rachio_long_ast = obfuscating_asterix(rachio_longitude, "all")
198+
print(" | | | Latitude: ", rachio_long_ast)
199+
200+
rachio_rainsensor = rachio_json["devices"][0]["rainSensorTripped"]
201+
print(" | | | Rain Sensor: ", rachio_rainsensor)
202+
203+
rachio_zone0 = rachio_json["devices"][0]["zones"][0]["name"]
204+
rachio_zone1 = rachio_json["devices"][0]["zones"][1]["name"]
205+
rachio_zone2 = rachio_json["devices"][0]["zones"][2]["name"]
206+
rachio_zone3 = rachio_json["devices"][0]["zones"][3]["name"]
207+
zones = f"{rachio_zone0}, {rachio_zone1}, {rachio_zone2}, {rachio_zone3}"
208+
print(f" | | | Zones: {zones}")
209+
210+
if DEBUG:
211+
print(f"\nFull API GET URL: {RACHIO_PERSON_SOURCE+rachio_id}")
212+
print(rachio_json)
213+
214+
print("\nFinished!")
215+
print(f"Board Uptime: {time_calc(time.monotonic())}")
216+
print(f"Next Update: {time_calc(SLEEP_TIME)}")
217+
print("===============================")
218+
219+
except (ValueError, RuntimeError) as e:
220+
print(f"Failed to get data, retrying\n {e}")
221+
time.sleep(60)
222+
break
223+
224+
time.sleep(SLEEP_TIME)

0 commit comments

Comments
 (0)