-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdbus-generic-mqtt-pvinverter.py
executable file
·217 lines (181 loc) · 10.1 KB
/
dbus-generic-mqtt-pvinverter.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/usr/bin/env python3
'''
Venus OS Dbus Service that subscribes to a MQTT broker to read current measurement data and publishes it as PV inverter data on dbus
currently, only 1 phase rms current is read and multiplied by 230 to get an estimated active power value
Possible extension:
in onmessage() iterate over topics to also read the other phases, for 3-phase inverters
'''
from gi.repository import GLib as gobject
import platform
import logging
import sys
import os
import _thread as thread
import paho.mqtt.client as mqtt
# insert your own values
Broker_Address = '192.168.168.112'
InverterType = 'voltwerk'
Topics = {
'current':'iot/pv/voltwerk/ac_current_A',
'voltage':'iot/pv/voltwerk/ac_voltage_V',
'power':'iot/pv/voltwerk/ac_active_power_kW',
'status':'iot/pv/voltwerk/service',
}
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "/opt/victronenergy/dbus-systemcalc-py/ext/velib_python",),)
from vedbus import VeDbusService
path_UpdateIndex = '/UpdateIndex'
class mqtt_inverter:
def __init__(self, topics='/my/pv/inverter', broker_address = '127.0.0.1'):
self._dbusservice = []
self.broker_address = broker_address
self.is_connected = False
self.is_online = False
self.topics = topics
self.client = mqtt.Client('Venus_Generic_Mqtt_Inverter_Driver')
self.client.on_disconnect = self.on_disconnect
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
self.client.connect(broker_address) # connect to broker
self.client.loop_start()
self.registers = {
# name : nr , format, factor, unit
"Active Power": ['W', 0],
"Energy Today": ['kWh', 0],
"Energy Total": ['kWh', 0],
"A phase Voltage": ['V', 0],
"B phase Voltage": ['V', 0],
"C phase Voltage": ['V', 0],
"A phase Current": ['A', 0],
"B phase Current": ['A', 0],
"C phase Current": ['A', 0],
}
def on_disconnect(self, client, userdata, rc):
print("mqtt disconnected")
if rc != 0:
print('Unexpected MQTT disconnect. Will auto-reconnect')
try:
client.connect(self.broker_address)
self.is_connected = True
except Exception as e:
logging.error("Failed to Reconnect to " + self.broker_address + " " + str(e))
print("Failed to Reconnect to " + self.broker_address + " " + str(e))
self.is_connected = False
def on_connect(self, client, userdata, flags, rc):
if rc == 0:
logging.info("Connected to MQTT Broker " + self.broker_address)
self.is_connected = True
for topic in self.topics.values():
logging.info("subscribe to " + topic)
client.subscribe(topic)
else:
logging.error("Failed to connect, return code %d\n", rc)
def on_message(self, client, userdata, msg):
try:
self.is_online = True
if msg.topic == self.topics['current']:
self.registers['A phase Current'][1] = float(msg.payload)
elif msg.topic == self.topics['voltage']:
self.registers['A phase Voltage'][1] = float(msg.payload)
elif msg.topic == self.topics['power']:
self.registers['Active Power'][1] = float(msg.payload)*1000
elif msg.topic == self.topics['status']:
self.is_online = True if msg.payload==b'online' else False
print(str(msg.payload.decode("utf-8")))
#todo
#self.registers['Energy Total'][1] = 0
except Exception as e:
logging.warning("Message parsing error " + str(e))
print(e)
class DbusGenenricMqttPvinverterService:
def __init__(self, topics, servicename, deviceinstance=290, productname='Generic MQTT PV Inverter', broker_address='127.0.0.1'):
self._dbusservice = VeDbusService(servicename)
logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance))
self.inverter = mqtt_inverter(topics, broker_address)
# Create the management objects, as specified in the ccgx dbus-api document
self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
self._dbusservice.add_path('/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version())
self._dbusservice.add_path('/Mgmt/Connection', 'MQTT_' + broker_address)
# Create the mandatory objects
self._dbusservice.add_path('/DeviceInstance', deviceinstance)
self._dbusservice.add_path('/ProductId', 1234) # pv inverter?
self._dbusservice.add_path('/ProductName', productname)
self._dbusservice.add_path('/FirmwareVersion', '--')
self._dbusservice.add_path('/HardwareVersion', 'Generic MQTT PV Inverter')
self._dbusservice.add_path('/Connected', 1)
self._dbusservice.add_path('/Ac/Power', None, writeable=True, gettextcallback=lambda a, x: "{:.0f}W".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Ac/Current', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}A".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Ac/MaxPower', None, writeable=True, gettextcallback=lambda a, x: "{:.0f}W".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Ac/Energy/Forward', None, writeable=True, gettextcallback=lambda a, x: "{:.0f}kWh".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Ac/L1/Voltage', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}V".format(x), onchangecallback=self._handlechangedvalue)
#self._dbusservice.add_path('/Ac/L2/Voltage', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}V".format(x), onchangecallback=self._handlechangedvalue)
#self._dbusservice.add_path('/Ac/L3/Voltage', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}V".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Ac/L1/Current', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}A".format(x), onchangecallback=self._handlechangedvalue)
#self._dbusservice.add_path('/Ac/L2/Current', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}A".format(x), onchangecallback=self._handlechangedvalue)
#self._dbusservice.add_path('/Ac/L3/Current', None, writeable=True, gettextcallback=lambda a, x: "{:.1f}A".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Ac/L1/Power', None, writeable=True, gettextcallback=lambda a, x: "{:.0f}W".format(x), onchangecallback=self._handlechangedvalue)
#self._dbusservice.add_path('/Ac/L2/Power', None, writeable=True, gettextcallback=lambda a, x: "{:.0f}W".format(x), onchangecallback=self._handlechangedvalue)
#self._dbusservice.add_path('/Ac/L3/Power', None, writeable=True, gettextcallback=lambda a, x: "{:.0f}W".format(x), onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/ErrorCode', 0, writeable=True, onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/StatusCode', 0, writeable=True, onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path('/Position', 0, writeable=True, onchangecallback=self._handlechangedvalue)
self._dbusservice.add_path(path_UpdateIndex, 0, writeable=True, onchangecallback=self._handlechangedvalue)
gobject.timeout_add(1000, self._update) # pause x ms before the next request
def _update(self):
try:
if self.inverter.is_online==False or self.inverter.is_connected==False:
self._dbusservice['/Ac/Power'] = None
self._dbusservice['/Ac/Current'] = None
self._dbusservice['/Ac/MaxPower'] = None
self._dbusservice['/Ac/Energy/Forward'] = None
self._dbusservice['/Ac/L1/Voltage'] = None
self._dbusservice['/Ac/L1/Current'] = None
self._dbusservice['/Ac/L1/Power'] = None
self._dbusservice['/StatusCode'] = None
else:
self._dbusservice['/Ac/Power'] = self.inverter.registers["Active Power"][1]
self._dbusservice['/Ac/Current'] = self.inverter.registers["A phase Current"][1]
self._dbusservice['/Ac/MaxPower'] = 5000
self._dbusservice['/Ac/Energy/Forward'] = self.inverter.registers["Energy Total"][1]
self._dbusservice['/Ac/L1/Voltage'] = self.inverter.registers["A phase Voltage"][1]
self._dbusservice['/Ac/L1/Current'] = self.inverter.registers["A phase Current"][1]
self._dbusservice['/Ac/L1/Power'] = self.inverter.registers["Active Power"][1]
self._dbusservice['/ErrorCode'] = 0
self._dbusservice['/StatusCode'] = 7
except Exception as e:
logging.info("WARNING: Could not read from Solis S5 Inverter", exc_info=sys.exc_info()[0])
self._dbusservice['/Ac/Power'] = 0 # TODO: any better idea to signal an issue?
# increment UpdateIndex - to show that new data is available
self._dbusservice[path_UpdateIndex] = (self._dbusservice[path_UpdateIndex] + 1) % 255
return True
def _handlechangedvalue(self, path, value):
logging.debug("extern update %s to %s" % (path, value))
return True
def main():
thread.daemon = True # allow the program to quit
logging.basicConfig(format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO,
handlers=[
logging.FileHandler(
"%s/current.log" % (os.path.dirname(os.path.realpath(__file__)))),
logging.StreamHandler()
])
try:
logging.info("Start Generic MQTT PV Inverter modbus service")
from dbus.mainloop.glib import DBusGMainLoop
# Have a mainloop, so we can send/receive asynchronous calls to and from dbus
DBusGMainLoop(set_as_default=True)
pvac_output = DbusGenenricMqttPvinverterService(
topics = Topics,
servicename = 'com.victronenergy.pvinverter.mqtt_' + InverterType,
deviceinstance = 290,
broker_address= Broker_Address,
)
logging.info('Connected to dbus, and switching over to gobject.MainLoop() (= event based)')
mainloop = gobject.MainLoop()
mainloop.run()
except Exception as e:
logging.critical('Error at %s', 'main', exc_info=e)
sys.exit(1)
if __name__ == "__main__":
main()