Klar. Gerne.
Für die RS485 Kommunikation:
1. JK BMS RS485 Adapter: https://de.aliexpress.com/item/1005003451100409.html
2. RS485 auf USB Adapter: https://www.waveshare.com/usb-to-rs485.htm
3. DBUS Serialbattery installieren: https://github.com/Louisvdw/dbus-serialbattery
Fertig.
Für die EVU Daten habe ich mein Modbus Solaredge Smartmeter. Da gäbe es jetzt verschiedene Varianten dieses in Venus OS zu bekommen. Ich habe noch eine openWB Wallbox und die Daten des Smartmeters liegen dort sowieso vor, weil sie per Modbus abgefragt werden. Gleichzeitig läuft auf der OpenWB ein MQTT Broker, der diese Daten aufbereitet und andern zur Verfügung stellt. Dazu habe ich folgende "Treiber" auf dem Venus OS installiert und auf mein Setting angepasst:
Wechselrichter (also PV Leistung):
#!/usr/bin/env python
try:
import gobject # Python 2.x
except:
from gi.repository import GLib as gobject # Python 3.x
import platform
import logging
import time
import sys
import json
import os
import paho.mqtt.client as mqtt
try:
import thread # for daemon = True / Python 2.x
except:
import _thread as thread # for daemon = True / Python 3.x
our own packages
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'
MQTT Setup
broker_address = "192.168.XXX"
port = 1883
MQTTNAME = "PVInverter"
username="XXXX"
password="XXX!"
Variblen setzen
verbunden = 0
durchlauf = 0
volt_ges = 0
volt_p1 = 0
volt_p2 = 0
volt_p3 = 0
amp_p1 = 0
amp_p2 = 0
amp_p3 = 0
pow_ges =0
MQTT Abfragen:
def on_disconnect(client, userdata, rc):
global verbunden
print("Client Got Disconnected")
if rc != 0:
print('Unexpected MQTT disconnection. Will auto-reconnect')
else:
print('rc value:' + str(rc))
try:
print("Trying to Reconnect")
client.connect(broker_address)
verbunden = 1
except Exception as e:
logging.exception("Fehler beim reconnecten mit Broker")
print("Error in Retrying to Connect with Broker")
verbunden = 0
print(e)
def on_connect(client, userdata, flags, rc):
print("Verbunden mit IoBroker: " + broker_address)
client.subscribe("openWB/pv/W")
def on_message(client, userdata, message):
global volt_ges,volt_p1, volt_p2, volt_p3, amp_p1, amp_p2, amp_p3, pow_ges
msg = str(message.payload.decode("utf-8"))
print("message received: ", msg)
print("message topic: ", message.topic)
if message.topic == "openWB/pv/W":
pow_ges = float(msg)
class DbusDummyService2:
def init(self, servicename, deviceinstance, paths, productname='Solaredge', connection='MQTT'):
self._dbusservice = VeDbusService(servicename)
self._paths = paths
logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance))
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', connection)
Create the mandatory objects
self._dbusservice.add_path('/DeviceInstance', deviceinstance)
self._dbusservice.add_path('/ProductId', 0xFFFF) # value used in ac_sensor_bridge.cpp of dbus-cgwacs
self._dbusservice.add_path('/ProductName', productname)
self._dbusservice.add_path('/Position', 0)
self._dbusservice.add_path('/FirmwareVersion', 0.1)
self._dbusservice.add_path('/HardwareVersion', 0)
self._dbusservice.add_path('/Connected', 1)
for path, settings in self._paths.items():
self._dbusservice.add_path(
path, settings['initial'], writeable=True, onchangecallback=self._handlechangedvalue)
gobject.timeout_add(1000, self._update) # pause 1000ms before the next request
def _update(self):
self._dbusservice['/Ac/Energy/Forward'] = pow_ges
self._dbusservice['/Ac/L1/Voltage'] = 230
self._dbusservice['/Ac/L2/Voltage'] = 230
self._dbusservice['/Ac/L3/Voltage'] = 230
self._dbusservice['/Ac/L1/Current'] = round(230*-3/pow_ges, 2)
self._dbusservice['/Ac/L2/Current'] = round(230*-3/pow_ges, 2)
self._dbusservice['/Ac/L3/Current'] = round(230*-3/pow_ges, 2)
self._dbusservice['/Ac/L1/Power'] = round(pow_ges/-3, 2)
self._dbusservice['/Ac/L2/Power'] = round(pow_ges/-3, 2)
self._dbusservice['/Ac/L3/Power'] = round(pow_ges/-3, 2)
self._dbusservice['/Ac/Power'] = pow_ges
logging.info("House Consumption: {:.0f}".format(pow_ges))
increment UpdateIndex - to show that new data is available
index = self._dbusservice[path_UpdateIndex] + 1 # increment index
if index > 255: # maximum value of the index
index = 0 # overflow from 255 to 0
self._dbusservice[path_UpdateIndex] = index
return True
def _handlechangedvalue(self, path, value):
logging.debug("someone else updated %s to %s" % (path, value))
return True # accept the change
def main():
logging.basicConfig(level=logging.DEBUG) # use .INFO for less logging
thread.daemon = True # allow the program to quit
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 = DbusDummyService2(
servicename='com.victronenergy.pvinverter.pv0',
deviceinstance=41,
paths={
'/Ac/Power': {'initial': 0},
'/Ac/L1/Voltage': {'initial': 0},
'/Ac/L2/Voltage': {'initial': 0},
'/Ac/L3/Voltage': {'initial': 0},
'/Ac/L1/Current': {'initial': 0},
'/Ac/L2/Current': {'initial': 0},
'/Ac/L3/Current': {'initial': 0},
'/Ac/L1/Power': {'initial': 0},
'/Ac/L2/Power': {'initial': 0},
'/Ac/L3/Power': {'initial': 0},
'/Ac/Energy/Forward': {'initial': 0},
path_UpdateIndex: {'initial': 0},
})
logging.info('Connected to dbus, and switching over to gobject.MainLoop() (= event based)')
mainloop = gobject.MainLoop()
mainloop.run()
Konfiguration MQTT
client = mqtt.Client(MQTTNAME) # create new instance
client.on_disconnect = on_disconnect
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(username=username, password=password)
client.connect(broker_address, port) # connect to broker
client.loop_start()
if name == "main":
main() (Ursprung: https://github.com/RalfZim/venus.dbus-fronius-smartmeter)
Smartmeter:
#!/usr/bin/env python
try:
import gobject # Python 2.x
except:
from gi.repository import GLib as gobject # Python 3.x
import platform
import logging
import time
import sys
import json
import os
import paho.mqtt.client as mqtt
try:
import thread # for daemon = True / Python 2.x
except:
import _thread as thread # for daemon = True / Python 3.x
our own packages
sys.path.insert(1, os.path.join(os.path.dirname(file), '../ext/velib_python'))
from vedbus import VeDbusService
path_UpdateIndex = '/UpdateIndex'
MQTT Setup
broker_address = "192.168.XXX"
port = 1883
MQTTNAME = "XXX"
username="XXX"
password="XXX"
Variblen setzen
verbunden = 0
durchlauf = 0
maxcellvoltage = 3.0
powercurr = 0
totalin = 0
totalout = 0
MQTT Abfragen:
def on_disconnect(client, userdata, rc):
global verbunden
print("Client Got Disconnected")
if rc != 0:
print('Unexpected MQTT disconnection. Will auto-reconnect')
else:
print('rc value:' + str(rc))
try:
print("Trying to Reconnect")
client.connect(broker_address)
verbunden = 1
except Exception as e:
logging.exception("Fehler beim reconnecten mit Broker")
print("Error in Retrying to Connect with Broker")
verbunden = 0
print(e)
def on_connect(client, userdata, flags, rc):
print("Verbunden mit IoBroker: " + broker_address)
client.subscribe("openWB/evu/W")
client.subscribe("openWB/evu/WPhase1")
client.subscribe("openWB/evu/WPhase2")
client.subscribe("openWB/evu/WPhase3")
client.subscribe("openWB/evu/WhImported")
client.subscribe("openWB/evu/WhExported")
def on_message(client, userdata, message):
global powercurr,power_p1, power_p2, power_p3, totalin, totalout
msg = str(message.payload.decode("utf-8"))
print("message received: ", msg)
print("message topic: ", message.topic)
if message.topic == "openWB/evu/W":
powercurr = float(msg)
if message.topic == "openWB/evu/WPhase1":
power_p1 = float(msg)
if message.topic == "openWB/evu/WPhase2":
power_p2 = float(msg)
if message.topic == "openWB/evu/WPhase3":
power_p3 = float(msg)
if message.topic == "openWB/evu/DailyYieldImportKwh":
totalin = float(msg)
if message.topic == "openWB/evu/DailyYieldExportKwh":
totalout = float(msg)
class DbusDummyService:
def init(self, servicename, deviceinstance, paths, productname='Smart Meter', connection='MQTT'):
self._dbusservice = VeDbusService(servicename)
self._paths = paths
logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance))
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', connection)
Create the mandatory objects
self._dbusservice.add_path('/DeviceInstance', deviceinstance)
self._dbusservice.add_path('/ProductId', 45069) # value used in ac_sensor_bridge.cpp of dbus-cgwacs
self._dbusservice.add_path('/ProductName', productname)
self._dbusservice.add_path('/FirmwareVersion', 0.1)
self._dbusservice.add_path('/HardwareVersion', 0)
self._dbusservice.add_path('/Connected', 1)
for path, settings in self._paths.items():
self._dbusservice.add_path(
path, settings['initial'], writeable=True, onchangecallback=self._handlechangedvalue)
gobject.timeout_add(1000, self._update) # pause 1000ms before the next request
def _update(self):
self._dbusservice['/Ac/Power'] = powercurr # positive: consumption, negative: feed into grid
self._dbusservice['/Ac/L1/Voltage'] = 230
self._dbusservice['/Ac/L2/Voltage'] = 230
self._dbusservice['/Ac/L3/Voltage'] = 230
self._dbusservice['/Ac/L1/Current'] = round(power_p1/230 ,2)
self._dbusservice['/Ac/L2/Current'] = round(power_p2/230 ,2)
self._dbusservice['/Ac/L3/Current'] = round(power_p3/230 ,2)
self._dbusservice['/Ac/L1/Power'] = round(power_p1, 2)
self._dbusservice['/Ac/L2/Power'] = round(power_p2, 2)
self._dbusservice['/Ac/L3/Power'] = round(power_p3, 2)
self._dbusservice['/Ac/Energy/Forward'] = totalin
self._dbusservice['/Ac/Energy/Reverse'] = totalout
logging.info("House Consumption: {:.0f}".format(powercurr))
increment UpdateIndex - to show that new data is available
index = self._dbusservice[path_UpdateIndex] + 1 # increment index
if index > 255: # maximum value of the index
index = 0 # overflow from 255 to 0
self._dbusservice[path_UpdateIndex] = index
return True
def _handlechangedvalue(self, path, value):
logging.debug("someone else updated %s to %s" % (path, value))
return True # accept the change
def main():
logging.basicConfig(level=logging.DEBUG) # use .INFO for less logging
thread.daemon = True # allow the program to quit
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 = DbusDummyService(
servicename='com.victronenergy.grid.cgwacs_ttyUSB0_mb1',
deviceinstance=0,
paths={
'/Ac/Power': {'initial': 0},
'/Ac/L1/Voltage': {'initial': 0},
'/Ac/L2/Voltage': {'initial': 0},
'/Ac/L3/Voltage': {'initial': 0},
'/Ac/L1/Current': {'initial': 0},
'/Ac/L2/Current': {'initial': 0},
'/Ac/L3/Current': {'initial': 0},
'/Ac/L1/Power': {'initial': 0},
'/Ac/L2/Power': {'initial': 0},
'/Ac/L3/Power': {'initial': 0},
'/Ac/Energy/Forward': {'initial': 0}, # energy bought from the grid
'/Ac/Energy/Reverse': {'initial': 0}, # energy sold to the grid
path_UpdateIndex: {'initial': 0},
})
logging.info('Connected to dbus, and switching over to gobject.MainLoop() (= event based)')
mainloop = gobject.MainLoop()
mainloop.run()
Konfiguration MQTT
client = mqtt.Client(MQTTNAME) # create new instance
client.on_disconnect = on_disconnect
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(username=username, password=password)
client.connect(broker_address, port) # connect to broker
client.loop_start()
if name == "main":
main()
Das war es schon. Sieht komplizierter aus, als es ist. Hab jetzt den zweiten Akku fertig. Da hat mich das ganze vielleicht 5 Minuten gedauert zu konfigurieren. Wichtig: Die anderen Dateien die hier (https://github.com/RalfZim/venus.dbus-fronius-smartmeter) zur Verfügung gestellt werden brauchst Du auch im Venus OS. Und dann bitte an die dortige Installationsanleitung halten. Der hier gepostete Anleitung ist nur meine persönliche Anpassung.