Skip to content

Commit 9a5a804

Browse files
Better error handling, rate limit handling.
Set tenacity to `reraise` exceptions, allowing them to be handled programmatically. Configure backoff interval of 3x `POLL_INTERVAL` or `5m`, whichever is longer. Don't crash upon errors during processing.
1 parent 83a2529 commit 9a5a804

1 file changed

Lines changed: 33 additions & 7 deletions

File tree

pwmon.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import requests
1616
import tenacity
1717
from dotenv import load_dotenv
18+
from tesla_powerwall.error import APIError
1819
from tesla_powerwall.powerwall import Powerwall
1920
from tesla_powerwall.const import MeterType
2021
from tesla_powerwall.responses import Meter, Battery
@@ -98,7 +99,8 @@ def get_now():
9899
return int(time.time() * 1000)
99100

100101

101-
@tenacity.retry(stop=tenacity.stop_after_attempt(1),
102+
@tenacity.retry(reraise=True,
103+
stop=tenacity.stop_after_attempt(1),
102104
wait=tenacity.wait_random(min=3, max=7))
103105
def post_metrics(data):
104106
"""POST a block of data and headers to a URL."""
@@ -115,7 +117,8 @@ def post_metrics(data):
115117
# and it has some absurdly low rate limit
116118

117119

118-
@tenacity.retry(stop=tenacity.stop_after_attempt(7),
120+
@tenacity.retry(reraise=True,
121+
stop=tenacity.stop_after_attempt(7),
119122
wait=tenacity.wait_random(min=3, max=7))
120123
def get_pw():
121124
"""Return a Powerwall connection object."""
@@ -130,7 +133,8 @@ def connect():
130133
return pw, pw.get_meters()
131134

132135

133-
@tenacity.retry(stop=tenacity.stop_after_attempt(7),
136+
@tenacity.retry(reraise=True,
137+
stop=tenacity.stop_after_attempt(7),
134138
wait=tenacity.wait_random(min=3, max=7))
135139
def get_weather():
136140
"""Return weather for a given lat/lon."""
@@ -313,10 +317,32 @@ def run_from_cli():
313317

314318
while True:
315319
start = time.time()
316-
data = get_data()
317-
ret = post_metrics(data)
318-
319-
print('Submitted at', dt.now())
320+
try:
321+
data = get_data()
322+
ret = post_metrics(data)
323+
324+
print('Submitted at', dt.now())
325+
except APIError as apiEx:
326+
print('Powerwall API Error: %s', apiEx)
327+
# If this is an HTTP 429, back off immediately for at least 5 minutes
328+
if str(apiEx).find('429: Too Many Requests') > 0:
329+
FIVE_MINUTES = 5 * 60
330+
elapsed = time.time() - start
331+
# Back off for at least 3x POLL_INTERVAL, for a minimum of 5 minutes to allow things to cool down
332+
backoffInterval = POLL_INTERVAL * 3
333+
if backoffInterval < FIVE_MINUTES:
334+
backoffInterval = FIVE_MINUTES
335+
print('Backing off for %s seconds because of HTTP 429...', backoffInterval - elapsed)
336+
time.sleep(backoffInterval - elapsed)
337+
# Determine if we need to wait until the start of the minute again
338+
if POLL_INTERVAL % 60 == 0 and AS_SERVICE:
339+
wait_time = 60 - time.localtime().tm_sec
340+
time.sleep(wait_time)
341+
# Reset the start time to coincide with the top of the minute
342+
start = time.time()
343+
print(' Done.')
344+
except Exception as ex:
345+
print('Failed to gather data: %s', ex)
320346

321347
if not AS_SERVICE:
322348
run_from_cli()

0 commit comments

Comments
 (0)