Compare commits
2 Commits
8b436c70d2
...
a804818792
Author | SHA1 | Date | |
---|---|---|---|
a804818792 | |||
418fd48a25 |
@ -17,7 +17,8 @@ metric_data = {
|
|||||||
"ac_voltage_volts": ["gauge", "Output bus voltage"],
|
"ac_voltage_volts": ["gauge", "Output bus voltage"],
|
||||||
'ac_frequency_hertz': ["gauge", "Output bus frequency"],
|
'ac_frequency_hertz': ["gauge", "Output bus frequency"],
|
||||||
'ac_power_watts': ["gauge", "Active/Reactive/Apparent AC power"],
|
'ac_power_watts': ["gauge", "Active/Reactive/Apparent AC power"],
|
||||||
'ac_power_factor': ["gauge", "AC Phase factor"]
|
'ac_power_factor': ["gauge", "AC Phase factor"],
|
||||||
|
'meter_event': ["gauge", 'Power meter event']
|
||||||
}
|
}
|
||||||
|
|
||||||
def export(d, m):
|
def export(d, m):
|
||||||
@ -51,6 +52,8 @@ def export(d, m):
|
|||||||
yield ('energy_watthours_total', {'meter': meter}, m.energy_real[meter])
|
yield ('energy_watthours_total', {'meter': meter}, m.energy_real[meter])
|
||||||
|
|
||||||
yield ('ac_frequency_hertz', {'meter': 'mains'}, m.frequency)
|
yield ('ac_frequency_hertz', {'meter': 'mains'}, m.frequency)
|
||||||
|
for event in m.events:
|
||||||
|
yield ('meter_event', {'event': event}, float(m.events[event]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +79,8 @@ def main():
|
|||||||
|
|
||||||
yield (date, 'firmware_version', {'version': common.version}, 1)
|
yield (date, 'firmware_version', {'version': common.version}, 1)
|
||||||
for (m,a,v) in export(inv, meters):
|
for (m,a,v) in export(inv, meters):
|
||||||
yield (date,m,a,v)
|
if v is not None:
|
||||||
|
yield (date,m,a,v)
|
||||||
|
|
||||||
|
|
||||||
prom.run(collect, metric_data, 'SolarEdge Modbus/TCP Exporter\n',
|
prom.run(collect, metric_data, 'SolarEdge Modbus/TCP Exporter\n',
|
||||||
|
40
sunspec.py
40
sunspec.py
@ -45,6 +45,16 @@ class Registers:
|
|||||||
def read_scale(self, address):
|
def read_scale(self, address):
|
||||||
return 10 ** self.read_int16(address)
|
return 10 ** self.read_int16(address)
|
||||||
|
|
||||||
|
class Header:
|
||||||
|
def __init__(self, regs, base):
|
||||||
|
self.did = regs.read_uint16(0)
|
||||||
|
self.len = regs.read_uint16(1)
|
||||||
|
self.base = base
|
||||||
|
self.end = base + self.len
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"SunSpec(DID={self.did}, [{self.base}:{self.end}]"
|
||||||
|
|
||||||
class CommonBlock:
|
class CommonBlock:
|
||||||
def __init__(self, regs):
|
def __init__(self, regs):
|
||||||
self.did = regs.read_uint16(0)
|
self.did = regs.read_uint16(0)
|
||||||
@ -55,6 +65,14 @@ class CommonBlock:
|
|||||||
self.serial = regs.read_bytes(50, 32)
|
self.serial = regs.read_bytes(50, 32)
|
||||||
self.device = regs.read_uint16(66)
|
self.device = regs.read_uint16(66)
|
||||||
|
|
||||||
|
meter_events_bits = { 2: 'Power Failure',
|
||||||
|
3: 'Under Voltage',
|
||||||
|
4: 'Low PF',
|
||||||
|
5: 'Over Current',
|
||||||
|
6: 'Over Voltage',
|
||||||
|
7: 'Missing Sensor' }
|
||||||
|
|
||||||
|
|
||||||
class Meter:
|
class Meter:
|
||||||
def __init__(self, regs):
|
def __init__(self, regs):
|
||||||
blocks16 = (('ac_current', 0, ('total', 'a', 'b', 'c'), 0.1),
|
blocks16 = (('ac_current', 0, ('total', 'a', 'b', 'c'), 0.1),
|
||||||
@ -90,11 +108,18 @@ class Meter:
|
|||||||
data = {}
|
data = {}
|
||||||
scale = regs.read_scale(offset + len(labels)*2)
|
scale = regs.read_scale(offset + len(labels)*2)
|
||||||
for (i, key) in enumerate(labels):
|
for (i, key) in enumerate(labels):
|
||||||
data[key] = regs.read_uint32(offset + 2*i) * scale / 10
|
value = regs.read_uint32(offset + 2*i) * scale / 10
|
||||||
|
data[key] = value if value > 1 else None
|
||||||
self.__dict__[metric] = data
|
self.__dict__[metric] = data
|
||||||
|
|
||||||
self.frequency = regs.read_int16(14, scale=15)
|
self.frequency = regs.read_int16(14, scale=15)
|
||||||
|
|
||||||
|
event_bits = regs.read_uint32(103)
|
||||||
|
self.events = {}
|
||||||
|
for bit in meter_events_bits:
|
||||||
|
self.events[meter_events_bits[bit]] = bool(event_bits & (1 << bit))
|
||||||
|
|
||||||
|
|
||||||
status_codes = [None, 'off', 'sleep', 'start', 'on', 'throttled', 'stop', 'fault', 'setup']
|
status_codes = [None, 'off', 'sleep', 'start', 'on', 'throttled', 'stop', 'fault', 'setup']
|
||||||
|
|
||||||
class InverterBlock:
|
class InverterBlock:
|
||||||
@ -116,7 +141,6 @@ class InverterBlock:
|
|||||||
self.ac_reactive_power = regs.read_int16(20, scale=21)
|
self.ac_reactive_power = regs.read_int16(20, scale=21)
|
||||||
|
|
||||||
self.power_factor = regs.read_int16(22, scale=23)
|
self.power_factor = regs.read_int16(22, scale=23)
|
||||||
self.ac_energy = regs.read_uint32(24, scale=26)
|
|
||||||
self.dc_current = regs.read_uint16(27, scale=28)
|
self.dc_current = regs.read_uint16(27, scale=28)
|
||||||
self.dc_voltage = regs.read_uint16(29, scale=30)
|
self.dc_voltage = regs.read_uint16(29, scale=30)
|
||||||
self.dc_power = regs.read_int16(31, scale=32)
|
self.dc_power = regs.read_int16(31, scale=32)
|
||||||
@ -125,6 +149,9 @@ class InverterBlock:
|
|||||||
self.status = status_codes[self.status_code]
|
self.status = status_codes[self.status_code]
|
||||||
self.status_vendor = regs.read_uint16(39)
|
self.status_vendor = regs.read_uint16(39)
|
||||||
|
|
||||||
|
ac_energy = regs.read_uint32(24, scale=26)
|
||||||
|
self.ac_energy = ac_energy if ac_energy > 1 else None
|
||||||
|
|
||||||
class Inverter:
|
class Inverter:
|
||||||
def __init__(self, host, port=1502, device=1):
|
def __init__(self, host, port=1502, device=1):
|
||||||
self._conn = ModbusTcpClient(host, port)
|
self._conn = ModbusTcpClient(host, port)
|
||||||
@ -134,11 +161,16 @@ class Inverter:
|
|||||||
if not self._conn.connect():
|
if not self._conn.connect():
|
||||||
raise ConnectionFailed()
|
raise ConnectionFailed()
|
||||||
|
|
||||||
def registers(self, addr, length):
|
def registers(self, addr, length, device=None):
|
||||||
|
if device is None:
|
||||||
|
device = self._device
|
||||||
self.connect()
|
self.connect()
|
||||||
rs = self._conn.read_holding_registers(addr, length, slave=self._device)
|
rs = self._conn.read_holding_registers(addr, length, slave=device)
|
||||||
return Registers(rs, length)
|
return Registers(rs, length)
|
||||||
|
|
||||||
|
def header(self, base):
|
||||||
|
return Header(self.registers(base, 2), base)
|
||||||
|
|
||||||
def common_block(self):
|
def common_block(self):
|
||||||
return CommonBlock(self.registers(40002, 67))
|
return CommonBlock(self.registers(40002, 67))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user