Initial version
This commit is contained in:
commit
b76b7e2b66
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# SolarEdge Cloud API Exporter
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit the script and put your API key, site id and inverter serial number
|
||||
Edit the listening host and port if needed
|
||||
|
||||
## Usage
|
||||
|
||||
Run the script as a service, add the URL to prometheus.
|
145
solaredge.py
Executable file
145
solaredge.py
Executable file
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import bottle
|
||||
import datetime
|
||||
import requests
|
||||
from collections import defaultdict
|
||||
|
||||
api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
site_id = 4143190
|
||||
|
||||
inverter = '7B0E5700-E0'
|
||||
|
||||
endpoint = "https://monitoringapi.solaredge.com/"
|
||||
|
||||
delta = datetime.timedelta(minutes=20)
|
||||
|
||||
numeric_modes = {
|
||||
'OFF': 0,
|
||||
'SLEEPING': 1,
|
||||
'STARTING': 2,
|
||||
'MPPT': 3,
|
||||
'SHUTTING_DOWN': 4,
|
||||
'FAULT': 5,
|
||||
'STANDBY': 6,
|
||||
'LOCKED': 7,
|
||||
}
|
||||
|
||||
metric_data = {
|
||||
"dc_voltage_volts": ["gauge", "Input bus voltage"],
|
||||
"meter_energy_watthours": ["counter", "Power meter"],
|
||||
"temperature_celsius": ["gauge", "Temperature"],
|
||||
"inverter_mode": ["gauge", "Inverter state (0=off, 3=producing, 5=fault)"],
|
||||
"ac_phase_volts": ["gauge", "Output bus phase voltage"],
|
||||
"ac_current_amps": ["gauge", "Output bus current"],
|
||||
"ac_voltage_volts": ["gauge", "Output bus voltage"],
|
||||
'ac_frequency_hertz': ["gauge", "Output bus frequency"],
|
||||
'ac_apparent_power_watts': ["gauge", "Apparent AC power"],
|
||||
'ac_active_power_watts': ["gauge", "Active AC power"],
|
||||
'ac_reactive_power_watts': ["gauge", "Reactive AC power"],
|
||||
'ac_cos_phi': ["gauge", "AC Phase factor"]
|
||||
}
|
||||
|
||||
def numeric_mode(s):
|
||||
if s in numeric_modes:
|
||||
return numeric_modes[s]
|
||||
if s.startswith('LOCKED'):
|
||||
return 7
|
||||
|
||||
def time(t):
|
||||
return t.strftime("%F %T")
|
||||
|
||||
def ptime(s):
|
||||
return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def get(path, params={}, **kw):
|
||||
r = requests.get(endpoint + path, params | { "api_key": api_key }, **kw)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def details():
|
||||
return get(f"site/{site_id}/details.json")
|
||||
|
||||
def inventory():
|
||||
return get(f"site/{site_id}/inventory.json")
|
||||
|
||||
def get_inverters():
|
||||
return [ i['SN'] for i in inventory()['Inventory']['inverters'] ]
|
||||
|
||||
def energy(**kw):
|
||||
return get(f"site/{site_id}/energyDetails.json",
|
||||
kw | {"timeUnit": "QUARTER_OF_AN_HOUR"})
|
||||
|
||||
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)
|
||||
|
||||
def latest(method, **kw):
|
||||
end = datetime.datetime.now()
|
||||
start = end - delta
|
||||
return method(startTime=time(start), endTime=time(end), **kw)
|
||||
|
||||
def collect():
|
||||
tech = latest(tech_data)['data']['telemetries']
|
||||
ms = latest(meters)['meterEnergyDetails']['meters']
|
||||
|
||||
if len(tech):
|
||||
point = tech[-1]
|
||||
date = ptime(point['date'])
|
||||
yield (date, 'dc_voltage_volts', {}, point.get('dcVoltage'))
|
||||
yield (date, 'meter_energy_watthours', {'meter': 'production'}, point.get('totalEnergy'))
|
||||
yield (date, 'temperature_celsius', {}, point.get('temperature'))
|
||||
yield (date, 'inverter_mode', {}, numeric_mode(point.get('inverterMode')))
|
||||
yield (date, 'ac_phase_volts', {'phase':'1-2'}, point.get('vL1To2'))
|
||||
yield (date, 'ac_phase_volts', {'phase':'2-3'}, point.get('vL2To3'))
|
||||
yield (date, 'ac_phase_volts', {'phase':'3-1'}, point.get('vL3To1'))
|
||||
|
||||
for p in ('L1Data','L2Data','L3Data'):
|
||||
phase = point[p]
|
||||
yield (date, 'ac_current_amps', {'phase': p[1:2] }, phase.get('acCurrent'))
|
||||
yield (date, 'ac_voltage_volts', {'phase': p[1:2] }, phase.get('acVoltage'))
|
||||
yield (date, 'ac_frequency_hertz', {'phase': p[1:2] }, phase.get('acFrequency'))
|
||||
yield (date, 'ac_apparent_power_watts', {'phase': p[1:2] }, phase.get('apparentPower'))
|
||||
yield (date, 'ac_active_power_watts', {'phase': p[1:2] }, phase.get('activePower'))
|
||||
yield (date, 'ac_reactive_power_watts', {'phase': p[1:2] }, phase.get('reactivePower'))
|
||||
yield (date, 'ac_cos_phi', {'phase': p[1:2] }, phase.get('cosPhi'))
|
||||
|
||||
for m in ms:
|
||||
point = m['values'][-1]
|
||||
date = ptime(point['date'])
|
||||
yield (date, 'meter_energy_watthours', {'meter': m['meterType'].lower()}, point['value'])
|
||||
|
||||
def format_metrics(entries):
|
||||
collected = defaultdict(list)
|
||||
for entry in entries:
|
||||
collected[entry[1]].append(entry)
|
||||
|
||||
for metric in collected:
|
||||
md = metric_data.get(metric)
|
||||
if md is not None:
|
||||
yield "# TYPE {} {}\n".format(metric, md[0])
|
||||
yield "# HELP {} {}\n".format(metric, md[1])
|
||||
|
||||
for (time, metric, attrs, value) in collected[metric]:
|
||||
time_s = time.timestamp()
|
||||
attr_s = ""
|
||||
if attrs:
|
||||
attr_s = "{" + ','.join('{}="{}"'.format(k, attrs[k]) for k in attrs ) + "}"
|
||||
yield f"{metric} {attr_s} {value} {time_s}\n"
|
||||
|
||||
@bottle.route('/')
|
||||
def root():
|
||||
return "SolarEdge prometheus exporter"
|
||||
|
||||
@bottle.route('/metrics')
|
||||
def metrics():
|
||||
return format_metrics(collect())
|
||||
|
||||
def main():
|
||||
bottle.run(host='localhost', port=9150)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user