diff --git a/solaredge.py b/solaredge.py index a90da3b..6b8b1c3 100755 --- a/solaredge.py +++ b/solaredge.py @@ -2,8 +2,10 @@ import bottle import datetime +import logging import requests from collections import defaultdict +from sys import stderr api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" site_id = 4143190 @@ -12,7 +14,9 @@ inverter = '7B0E5700-E0' endpoint = "https://monitoringapi.solaredge.com/" -delta = datetime.timedelta(minutes=20) +query_delta = datetime.timedelta(minutes=20) +freshness_delta = datetime.timedelta(minutes=20) +cache = {} numeric_modes = { 'OFF': 0, @@ -40,6 +44,11 @@ metric_data = { 'ac_cos_phi': ["gauge", "AC Phase factor"] } +last_ts = {} + +def is_fresh(date): + return (datetime.datetime.now()) - date < freshness_delta + def numeric_mode(s): if s in numeric_modes: return numeric_modes[s] @@ -55,7 +64,7 @@ def ptime(s): def get(path, params={}, **kw): r = requests.get(endpoint + path, params | { "api_key": api_key }, **kw) r.raise_for_status() - return r.json() + return r.json() | { 'fetch_time': datetime.datetime.now().timestamp() } def details(): return get(f"site/{site_id}/details.json") @@ -69,22 +78,39 @@ def get_inverters(): def energy(**kw): return get(f"site/{site_id}/energyDetails.json", kw | {"timeUnit": "QUARTER_OF_AN_HOUR"}) - +11 def tech_data(**kw): return get(f"equipment/{site_id}/{inverter}/data.json", kw) def meters(**kw): - return get(f"site/{site_id}/meters.json", kw) + return get(f"site/{site_id}/meters.json", kw | {"timeUnit": "QUARTER_OF_AN_HOUR"}) -def latest(method, **kw): +def latest(method, timefn, **kw): + name = method.__name__ + if name in cache and is_fresh(timefn(cache[name])): + logging.info("Cache for {} is fresh", method.__name__) + return cache[name] + + logging.info("Cache for {} is stale, requesting", method.__name__) end = datetime.datetime.now() - start = end - delta - return method(startTime=time(start), endTime=time(end), **kw) + start = end - query_delta + data = method(startTime=time(start), endTime=time(end), **kw) + cache[name] = data + return data + +def tech_check(c): + if c['data']['telemetries']: + return ptime(c['data']['telemetries'][-1]['date']) + else: + return datetime.datetime.fromtimestamp(c['fetch_time']) + + +def meters_check(c): + return ptime(c['meterEnergyDetails']['meters'][0]['values'][-1]['date']) def collect(): - tech = latest(tech_data)['data']['telemetries'] - ms = latest(meters)['meterEnergyDetails']['meters'] - + tech = latest(tech_data, tech_check)['data']['telemetries'] + ms = latest(meters, meters_check)['meterEnergyDetails']['meters'] if len(tech): point = tech[-1] date = ptime(point['date']) @@ -111,7 +137,7 @@ def collect(): date = ptime(point['date']) yield (date, 'meter_energy_watthours_total', {'meter': m['meterType'].lower()}, point['value']) -def format_metrics(entries): +def format_metrics(entries, timestamps=False): collected = defaultdict(list) for entry in entries: collected[entry[1]].append(entry) @@ -127,7 +153,10 @@ def format_metrics(entries): attr_s = "" if attrs: attr_s = "{" + ','.join('{}="{}"'.format(k, attrs[k]) for k in attrs ) + "}" - yield f"{metric} {attr_s} {value} {time_s}\n" + if timestamps: + yield f"{metric} {attr_s} {value} {time_s}\n" + else: + yield f"{metric} {attr_s} {value}\n" @bottle.route('/') def root():