-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathsolaredge.py
177 lines (159 loc) · 6.73 KB
/
solaredge.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import argparse
import asyncio
import datetime
import logging
from aiohttp import ClientConnectionError
from aioinflux import InfluxDBClient, InfluxDBWriteError
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pyModbusTCP.client import ModbusClient
datapoint = {"measurement": "SolarEdge", "tags": {"inverter": "1"}, "fields": {}}
logger = logging.getLogger("solaredge")
# Sentinel value to indicate the feature in a register is not implemented
NOT_IMPLEMENTED_VALUE = 65535
async def write_to_influx(dbhost, dbport, period, dbname="solaredge"):
global client
def trunc_float(floatval):
return float("%.2f" % floatval)
def decode_value(data, scalefactor):
if data == NOT_IMPLEMENTED_VALUE:
return None
try:
return trunc_float(data * scalefactor)
except OverflowError:
logger.warning("decode of %f failed", data)
try:
solar_client = InfluxDBClient(host=dbhost, port=dbport, db=dbname)
await solar_client.create_database(db=dbname)
except ClientConnectionError as e:
logger.error(f"Error during connection to InfluxDb {dbhost}: {e}")
return
logger.info("Database opened and initialized")
while True:
try:
reg_block = client.read_holding_registers(40069, 38)
if reg_block:
# print(reg_block)
data = BinaryPayloadDecoder.fromRegisters(
reg_block, byteorder=Endian.BIG, wordorder=Endian.BIG
)
data.skip_bytes(12)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-10)
# Register 40072-40075
datapoint["fields"]["AC Total Current"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
datapoint["fields"]["AC Current phase A"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
datapoint["fields"]["AC Current phase B"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
datapoint["fields"]["AC Current phase C"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
data.skip_bytes(14)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-8)
# register 40080-40082
datapoint["fields"]["AC Voltage phase A"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
datapoint["fields"]["AC Voltage phase B"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
datapoint["fields"]["AC Voltage phase C"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
data.skip_bytes(4)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-4)
# register 40084
datapoint["fields"]["AC Power output"] = decode_value(
data.decode_16bit_int(), scalefactor
)
data.skip_bytes(22)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-6)
# register 40094
datapoint["fields"]["AC Lifetimeproduction"] = decode_value(
data.decode_32bit_uint(), scalefactor
)
data.skip_bytes(4)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-4)
# register 40097
datapoint["fields"]["DC Current"] = decode_value(
data.decode_16bit_uint() * scalefactor
)
data.skip_bytes(4)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-4)
# register 40099
datapoint["fields"]["DC Voltage"] = decode_value(
data.decode_16bit_uint(), scalefactor
)
data.skip_bytes(4)
scalefactor = 10 ** data.decode_16bit_int()
data.skip_bytes(-4)
# datapoint 40101
datapoint["fields"]["DC Power input"] = decode_value(
data.decode_16bit_int(), scalefactor
)
datapoint["time"] = str(
datetime.datetime.utcnow()
.replace(tzinfo=datetime.timezone.utc)
.isoformat()
)
logger.debug(f"Writing to Influx: {str(datapoint)}")
await solar_client.write(datapoint)
else:
logger.error(
"Failed while connecting or receiving data from "
f"SolarEdge inverter {client.host}: {client.last_error_as_txt}"
)
except InfluxDBWriteError as e:
logger.error(f"Failed to write to InfluxDb: {e}")
except IOError as e:
logger.error(f"I/O exception during operation: {e}")
except Exception as e:
logger.exception(f"Unhandled exception: {e}")
await asyncio.sleep(period)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--influxdb", default="localhost")
parser.add_argument("--influxport", type=int, default=8086)
parser.add_argument(
"--port", type=int, default=502, help="ModBus TCP port number to use"
)
parser.add_argument(
"--unitid", type=int, default=1, help="ModBus unit id to use in communication"
)
parser.add_argument(
"solaredge",
metavar="SolarEdge IP",
help="IP address of the SolarEdge inverter to monitor",
)
parser.add_argument("--period", "-p", type=int, default=5)
parser.add_argument("--debug", "-d", action="count")
args = parser.parse_args()
logging.basicConfig()
if args.debug and args.debug >= 1:
logging.getLogger("solaredge").setLevel(logging.DEBUG)
if args.debug and args.debug == 2:
logging.getLogger("aioinflux").setLevel(logging.DEBUG)
print("Starting up solaredge monitoring")
print(
f"Connecting to Solaredge inverter {args.solaredge} on port {args.port} using unitid {args.unitid}"
)
print(
f"Writing data to influxDb {args.influxdb} on port {args.influxport} every {args.period} seconds"
)
client = ModbusClient(
args.solaredge, port=args.port, unit_id=args.unitid, auto_open=True
)
logger.debug("Running eventloop")
asyncio.get_event_loop().run_until_complete(
write_to_influx(args.influxdb, args.influxport, args.period)
)