Thanks for the debug commands! I will report the result but it can take some days.
The DBUS graph in VRM is interesting! I didnt kow it. Here a screenshot of the event in my original post.
Roundtrip seems to be much to long in this situation. As soon as i switch off the Multiplus the Rountrip is only 2-4ms.
On the Rasperry there is only Venus OS active and one Python Script that i wrote five years ago to read serial data (BMS, temperatures, tank sensors) from an arduino. The script is based on the Victron “simple battery” example. It worked flawless up to now. The System is installed in my Camper. Three month ago i decided to extend the system to use the camper as ESS for my house while it is parked. At the beginnig i used Wifi for data connection but there the swinging problem occured more often. I thought a LAN cable would solve the problem.
I could disable my python script to see if the system behavior is better. Do you have a link to an up to date example script?
Here is my script:
#!/usr/bin/env python3
#
# Version 1.2.002
# Zur Kommunikation mit Arduino hunkController2 V1.2.002
#
# V1.2.001 Uebertragung der Energiezaehler in Statistik-struct von float auf uint32_t umgestellt
# V1.2.002 2022.03.22 Angepasst an neues VenusOS mit Python3. 4 Temperatursensoren umgewandelt in 2 Sensoren mit jeweils Temp und Humidity
HUNKCONTROL_VERSION = 'HUNK-Control V1.2.002'
import serial
import time
import dbus
import struct
DEBUG_SERIAL = False #set to false for regular use to avoid massiv writing of log files on sd-card
DEBUG_ALARMS = False
DEBUG_VE_VALUES = False #set to false for regular use to avoid massiv writing of log files on sd-card
DEBUG_SIMULATE_BMS_VALUES = False #no serial communication required
DEBUG_DBUS = False #set to false for regular use to avoid massiv writing of log files on sd-card
MAX_RETRIES_REQDATA = 10
class SystemBus(dbus.bus.BusConnection):
def __new__(cls):
return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SYSTEM)
class SessionBus(dbus.bus.BusConnection):
def __new__(cls):
return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
def dbusconnection():
return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
class battDat():
def __init__(self):
self.extBmsDat = {'voltage': 0.0,
'voltageMin': 0.0,
'voltageMax': 0.0,
'current': 0.0,
'currentMin': 0.0,
'currentMax': 0.0,
'power' : 0.0,
'temp1' : 0.0,
'temp2': 0.0,
'consumedAmphours': 0.0,
'chargedAmphours': 0.0,
'soc': 0.0,
'sysMinCellVoltage': 0.0,
'sysMaxCellVoltage': 0.0,
'statDischargedAmpHours': 0,
'statChargedAmpHours': 0,
'statDischargedEnergy': 0,
'statChargedEnergy': 0,
'statMinVoltage': 0.0,
'statMaxVoltage': 0.0,
'statMinCellVoltage': 0.0,
'statMaxCellVoltage': 0.0,
'countAlarmUndervolt': 0,
'countAlarmOvervolt': 0,
'countAlarmChargeOvercurrent': 0,
'countAlarmDischargeOvercurrent': 0,
'countAlarmChargeOvertemp': 0,
'countAlarmChargeUndertemp': 0,
'countAlarmDischargeOvertemp': 0,
'countAlarmDischargeUndertemp': 0,
'countAlarmSingleUndervolt': 0,
'countAlarmSingleOvervolt': 0,
'countAlarmShortCircuit': 0,
'countAlarmFrontDetectionIC': 0,
'statChargeCycles' : 0,
'residualCapacity': 0,
'nominalCapacity': 0,
'cycleTimes': 0,
'protectionState': 0,
'balancedState': 0,
'mosfetState': 0}
#alarms are splited out of self.extBmsDat['protectionState']
self.alarm = {'singleOverVolt': 0,
'singleUnderVolt': 0,
'groupOverVolt': 0,
'groupUnderVolt': 0,
'chargeOverTemp': 0,
'chargeUnderTemp': 0,
'dischargeOverTemp': 0,
'dischargeUnderTemp': 0,
'chargeOverCurrent': 0,
'dischargeOverCurrent': 0,
'shortCircuit': 0,
'frontDetectionIC': 0,
'softwareLockMOS': 0}
class hunkController():
def __init__(self, port):
self.timeOut = 0.2 # sekunden
self.maxTries = 100 # number of requests until bms is assumed to be unavailable
self.b1 = battDat()
self.b2 = battDat()
self.bSum = battDat()
self.clima = {'indoorTemp': 0,
'indoorTempMin': 0,
'indoorTempMax': 0,
'indoorHumi': 0,
'indoorHumiMin': 0,
'indoorHumiMax': 0,
'outdoorTemp': 0,
'outdoorTempMin': 0,
'outdoorTempMax': 0,
'outdoorHumi': 0,
'outdoorHumiMin': 0,
'outdoorHumiMax': 0,
'spare1T': 0,
'spare1TMin': 0,
'spare1TMax': 0,
'spare1H': 0,
'spare1HMin': 0,
'spare1HMax': 0,
'spare2T': 0,
'spare2TMin': 0,
'spare2TMax': 0,
'spare2H': 0,
'spare2HMin': 0,
'spare2HMax': 0}
self.tankLevelRel = {'t1': 0.0,
't2': 0.0,
't3': 0.0}
self._port = port
self._errorCount = 0
self.__READ_TEL_LEN = 7
self.__WRITE_TEL_LEN = 9
self.__WRITE_ACK_LEN = 7
self.__REC_BASIC_TEL_LEN = 374
self.__hunkReadBasic = bytearray([0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77])
self.objectsLinked = False
self.dBusB0 = 0
self.dBusB1 = 0
self.dBusB2 = 0
self.dBusTemp1 = 0
self.dBusHumi1 = 0
self.dBusTemp2 = 0
self.dBusHumi2 = 0
self.dBusTank1 = 0
self.dBusTank2 = 0
self.txCount = 0
self.rxCount = 0
GLib.timeout_add(500, self._update)
def linkObjects(self, refBat0, refBat1, refBat2, refTemp1, refTemp2, refTank1, refTank2,):
if DEBUG_DBUS:
print('Link Objects')
self.dBusB0 = refBat0
self.dBusB1 = refBat1
self.dBusB2 = refBat2
self.dBusTemp1 = refTemp1
self.dBusTemp2 = refTemp2
self.dBusTank1 = refTank1
self.dBusTank2 = refTank2
self.objectsLinked = True
def _update(self):
if DEBUG_DBUS:
print("Update")
if self.objectsLinked:
comOk = self.reqData()
if comOk:
self.dBusB1.update(self.b1)
self.dBusB2.update(self.b2)
self.bSum = self.combineExtBmsDat(self.b1, self.b2)
self.dBusB0.update(self.bSum)
self.dBusTemp1.update(self.clima['indoorTemp'], self.clima['indoorTempMin'], self.clima['indoorTempMax'], self.clima['indoorHumi'], self.clima['indoorHumiMin'], self.clima['indoorHumiMax'])
self.dBusTemp2.update(self.clima['outdoorTemp'], self.clima['outdoorTempMin'], self.clima['outdoorTempMax'], self.clima['outdoorHumi'], self.clima['outdoorHumiMin'], self.clima['outdoorHumiMax'])
self.dBusTank1.update(self.tankLevelRel['t1'], 0.34)
self.dBusTank2.update(self.tankLevelRel['t2'], 0.07)
return True
def __quitWithMessage(self, e):
if DEBUG_DBUS:
print('Hunk Controller driver stopped! Maybe USB unplugged. Except: '+str(e))
quit()
def __twobytestofloat(self, b0, b1):
temp = (b0 << 8) | b1
if temp & 0x8000: # sign bit set?
temp = (~temp & 0x7FFF) / -1.0
else:
temp = temp / 1.0
return temp
def __checkChecksum(self, buff, len):
checksumOK = 0
checkSum = 0
for j in range(2, len-3): # build sum of bytes with index 2-30
checkSum += buff[j]
checkSum = ((~checkSum) & 0xFFFF) + 1 # 0xFFFF workaround for unsigned interpretation
if DEBUG_SERIAL:
print("Checksum " + hex(checkSum))
if checkSum == ((buff[len-2] << 8) | buff[len-3]):
checksumOK = 1
if DEBUG_SERIAL:
print("".join(" %02X" % k for k in buff)) # print all bytes off array with two characters per hex value
return checksumOK
def __receive(self, interface, buff, len):
rxcnt = interface.in_waiting
if DEBUG_SERIAL:
print("len: ", len, " rxcnt: ", rxcnt)
i = 0
if rxcnt == len:
while interface.in_waiting > 0:
bytesObject = interface.read(1)
buff[i] = bytesObject[0]
i += 1
else:
if DEBUG_SERIAL:
print("rxCount " + str(rxcnt))
rxcnt = 0
self._errorCount += 1
return rxcnt
def checkCom(self):
errorCount = 0
comOk = False
hunkRaw = bytearray(self.__REC_BASIC_TEL_LEN)
if DEBUG_SIMULATE_BMS_VALUES:
comOk = True
return comOk
try:
while errorCount < self.maxTries:
self._port.reset_input_buffer()
self._port.reset_output_buffer()
self.txCount = self._port.write(self.__hunkReadBasic)
time.sleep(0.2) # wait 200ms until bms hast transmitted all bytes
self.rxCount = self.__receive(self._port, hunkRaw, self.__REC_BASIC_TEL_LEN)
if self.rxCount == self.__REC_BASIC_TEL_LEN:
if self.__checkChecksum(hunkRaw, self.__REC_BASIC_TEL_LEN):
if DEBUG_SERIAL:
print("Com to Hunk Controller established")
comOk = True
break
else:
errorCount += 1
time.sleep(0.05) # little break before next try
except Exception as e:
self.__quitWithMessage(e)
return comOk
def minVal(self, a, b):
if a < b:
min = a
else:
min = b
return min
def maxVal(self, a, b):
if a > b:
max = a
else:
max = b
return max
def combineExtBmsDat(self, b1, b2):
bs = battDat() # virtual summary battery
bs.extBmsDat['voltage'] = (b1.extBmsDat['voltage'] + b2.extBmsDat['voltage']) / 2
bs.extBmsDat['voltageMin'] = self.minVal(b1.extBmsDat['voltageMin'], b2.extBmsDat['voltageMin'])
bs.extBmsDat['voltageMax'] = self.maxVal(b1.extBmsDat['voltageMax'], b2.extBmsDat['voltageMax'])
bs.extBmsDat['current'] = b1.extBmsDat['current'] + b2.extBmsDat['current']
bs.extBmsDat['currentMin'] = b1.extBmsDat['currentMin'] + b2.extBmsDat['currentMin']
bs.extBmsDat['currentMax'] = b1.extBmsDat['currentMax'] + b2.extBmsDat['currentMax']
bs.extBmsDat['power'] = b1.extBmsDat['power'] + b2.extBmsDat['power']
bs.extBmsDat['temp1'] = (b1.extBmsDat['temp1'] + b2.extBmsDat['temp1']) / 2
bs.extBmsDat['temp2'] = (b1.extBmsDat['temp2'] + b2.extBmsDat['temp2']) / 2
bs.extBmsDat['consumedAmphours'] = b1.extBmsDat['consumedAmphours'] + b2.extBmsDat['consumedAmphours']
bs.extBmsDat['chargedAmphours'] = b1.extBmsDat['chargedAmphours'] + b2.extBmsDat['chargedAmphours']
bs.extBmsDat['soc'] = (b1.extBmsDat['soc'] + b2.extBmsDat['soc']) / 2
bs.extBmsDat['sysMinCellVoltage'] = self.minVal(b1.extBmsDat['sysMinCellVoltage'], b2.extBmsDat['sysMinCellVoltage'])
bs.extBmsDat['sysMaxCellVoltage'] = self.maxVal(b1.extBmsDat['sysMaxCellVoltage'], b2.extBmsDat['sysMaxCellVoltage'])
bs.extBmsDat['statDischargedAmpHours'] = b1.extBmsDat['statDischargedAmpHours'] + b2.extBmsDat['statDischargedAmpHours']
bs.extBmsDat['statChargedAmpHours'] = b1.extBmsDat['statChargedAmpHours'] + b2.extBmsDat['statChargedAmpHours']
bs.extBmsDat['statDischargedEnergy'] = b1.extBmsDat['statDischargedEnergy'] + b2.extBmsDat['statDischargedEnergy']
bs.extBmsDat['statChargedEnergy'] = b1.extBmsDat['statChargedEnergy'] + b2.extBmsDat['statChargedEnergy']
bs.extBmsDat['statMinVoltage'] = self.minVal(b1.extBmsDat['statMinVoltage'], b2.extBmsDat['statMinVoltage'])
bs.extBmsDat['statMaxVoltage'] = self.maxVal(b1.extBmsDat['statMaxVoltage'], b2.extBmsDat['statMaxVoltage'])
bs.extBmsDat['statMinCellVoltage'] = self.minVal(b1.extBmsDat['statMinCellVoltage'], b2.extBmsDat['statMinCellVoltage'])
bs.extBmsDat['statMaxCellVoltage'] = self.maxVal(b1.extBmsDat['statMaxCellVoltage'], b2.extBmsDat['statMaxCellVoltage'])
bs.extBmsDat['countAlarmUndervolt'] = b1.extBmsDat['countAlarmUndervolt'] + b2.extBmsDat['countAlarmUndervolt']
bs.extBmsDat['countAlarmOvervolt'] = b1.extBmsDat['countAlarmOvervolt'] + b2.extBmsDat['countAlarmOvervolt']
bs.extBmsDat['countAlarmChargeOvercurrent'] = b1.extBmsDat['countAlarmChargeOvercurrent'] + b2.extBmsDat['countAlarmChargeOvercurrent']
bs.extBmsDat['countAlarmDischargeOvercurrent'] = b1.extBmsDat['countAlarmDischargeOvercurrent'] + b2.extBmsDat['countAlarmDischargeOvercurrent']
bs.extBmsDat['countAlarmChargeOvertemp'] = b1.extBmsDat['countAlarmChargeOvertemp'] + b2.extBmsDat['countAlarmChargeOvertemp']
bs.extBmsDat['countAlarmChargeUndertemp'] = b1.extBmsDat['countAlarmChargeUndertemp'] + b2.extBmsDat['countAlarmChargeUndertemp']
bs.extBmsDat['countAlarmDischargeOvertemp'] = b1.extBmsDat['countAlarmDischargeOvertemp'] + b2.extBmsDat['countAlarmDischargeOvertemp']
bs.extBmsDat['countAlarmDischargeUndertemp'] = b1.extBmsDat['countAlarmDischargeUndertemp'] + b2.extBmsDat['countAlarmDischargeUndertemp']
bs.extBmsDat['countAlarmSingleUndervolt'] = b1.extBmsDat['countAlarmSingleUndervolt'] + b2.extBmsDat['countAlarmSingleUndervolt']
bs.extBmsDat['countAlarmSingleOvervolt'] = b1.extBmsDat['countAlarmSingleOvervolt'] + b2.extBmsDat['countAlarmSingleOvervolt']
bs.extBmsDat['countAlarmShortCircuit'] = b1.extBmsDat['countAlarmShortCircuit'] + b2.extBmsDat['countAlarmShortCircuit']
bs.extBmsDat['countAlarmFrontDetectionIC'] = b1.extBmsDat['countAlarmFrontDetectionIC'] + b2.extBmsDat['countAlarmFrontDetectionIC']
bs.extBmsDat['statChargeCycles'] = b1.extBmsDat['statChargeCycles'] + b2.extBmsDat['statChargeCycles']
bs.extBmsDat['residualCapacity'] = b1.extBmsDat['residualCapacity'] + b2.extBmsDat['residualCapacity']
bs.extBmsDat['nominalCapacity'] = b1.extBmsDat['nominalCapacity'] + b2.extBmsDat['nominalCapacity']
bs.extBmsDat['cycleTimes'] = self.maxVal(b1.extBmsDat['cycleTimes'], b2.extBmsDat['cycleTimes'])
bs.extBmsDat['protectionState'] = b1.extBmsDat['protectionState'] | b2.extBmsDat['protectionState']
bs.extBmsDat['balancedState'] = b1.extBmsDat['balancedState'] | b2.extBmsDat['balancedState']
bs.extBmsDat['mosfetState'] = b1.extBmsDat['mosfetState'] & b2.extBmsDat['mosfetState']
bs.alarm['singleOverVolt'] = b1.alarm['singleOverVolt'] | b2.alarm['singleOverVolt']
bs.alarm['singleUnderVolt'] = b1.alarm['singleUnderVolt'] | b2.alarm['singleUnderVolt']
bs.alarm['groupOverVolt'] = b1.alarm['groupOverVolt'] | b2.alarm['groupOverVolt']
bs.alarm['groupUnderVolt'] = b1.alarm['groupUnderVolt'] | b2.alarm['groupUnderVolt']
bs.alarm['chargeOverTemp'] = b1.alarm['chargeOverTemp'] | b2.alarm['chargeOverTemp']
bs.alarm['chargeUnderTemp'] = b1.alarm['chargeUnderTemp'] | b2.alarm['chargeUnderTemp']
bs.alarm['dischargeOverTemp'] = b1.alarm['dischargeOverTemp'] | b2.alarm['dischargeOverTemp']
bs.alarm['dischargeUnderTemp'] = b1.alarm['dischargeUnderTemp'] | b2.alarm['dischargeUnderTemp']
bs.alarm['chargeOverCurrent'] = b1.alarm['chargeOverCurrent'] | b2.alarm['chargeOverCurrent']
bs.alarm['dischargeOverCurrent'] = b1.alarm['dischargeOverCurrent'] | b2.alarm['dischargeOverCurrent']
bs.alarm['shortCircuit'] = b1.alarm['shortCircuit'] | b2.alarm['shortCircuit']
bs.alarm['frontDetectionIC'] = b1.alarm['frontDetectionIC'] | b2.alarm['frontDetectionIC']
bs.alarm['softwareLockMOS'] = b1.alarm['softwareLockMOS'] | b2.alarm['softwareLockMOS']
return bs # virtual summary battery
def extractExtBmsDat(self, bat, buff, offset):
ob = offset
obs = ob + 56
obsc = obs + 32
obr = obsc + 28
bat.extBmsDat['voltage'] = struct.unpack('<f', buff[ob + 0: ob + 4])[0]
bat.extBmsDat['voltageMin'] = struct.unpack('<f', buff[ob + 4: ob + 8])[0]
bat.extBmsDat['voltageMax'] = struct.unpack('<f', buff[ob + 8: ob + 12])[0]
bat.extBmsDat['current'] = struct.unpack('<f', buff[ob + 12: ob + 16])[0]
bat.extBmsDat['currentMin'] = struct.unpack('<f', buff[ob + 16: ob + 20])[0]
bat.extBmsDat['currentMax'] = struct.unpack('<f', buff[ob + 20: ob + 24])[0]
bat.extBmsDat['power'] = struct.unpack('<f', buff[ob + 24: ob + 28])[0]
bat.extBmsDat['temp1'] = struct.unpack('<f', buff[ob + 28: ob + 32])[0]
bat.extBmsDat['temp2'] = struct.unpack('<f', buff[ob + 32: ob + 36])[0]
bat.extBmsDat['consumedAmphours'] = struct.unpack('<f', buff[ob + 36: ob + 40])[0] / 1000.0
bat.extBmsDat['chargedAmphours'] = struct.unpack('<f', buff[ob + 40: ob + 44])[0] / 1000.0
bat.extBmsDat['soc'] = struct.unpack('<f', buff[ob + 44: ob + 48])[0]
bat.extBmsDat['sysMinCellVoltage'] = struct.unpack('<f', buff[ob + 48: ob + 52])[0]
bat.extBmsDat['sysMaxCellVoltage'] = struct.unpack('<f', buff[ob + 52: ob + 56])[0]
bat.extBmsDat['statDischargedAmpHours'] = struct.unpack('<I', buff[obs + 0: obs + 4])[0] / 1000.0
bat.extBmsDat['statChargedAmpHours'] = struct.unpack('<I', buff[obs + 4: obs + 8])[0] / 1000.0
bat.extBmsDat['statDischargedEnergy'] = struct.unpack('<I', buff[obs + 8: obs + 12])[0]
bat.extBmsDat['statChargedEnergy'] = struct.unpack('<I', buff[obs + 12: obs + 16])[0]
bat.extBmsDat['statMinVoltage'] = struct.unpack('<f', buff[obs + 16: obs + 20])[0]
bat.extBmsDat['statMaxVoltage'] = struct.unpack('<f', buff[obs + 20: obs + 24])[0]
bat.extBmsDat['statMinCellVoltage'] = struct.unpack('<f', buff[obs + 24: obs + 28])[0]
bat.extBmsDat['statMaxCellVoltage'] = struct.unpack('<f', buff[obs + 28: obs + 32])[0]
bat.extBmsDat['countAlarmUndervolt'] = struct.unpack('<H', buff[obsc + 0: obsc + 2])[0]
bat.extBmsDat['countAlarmOvervolt'] = struct.unpack('<H', buff[obsc + 2: obsc + 4])[0]
bat.extBmsDat['countAlarmChargeOvercurrent'] = struct.unpack('<H', buff[obsc + 4: obsc + 6])[0]
bat.extBmsDat['countAlarmDischargeOvercurrent'] = struct.unpack('<H', buff[obsc + 6: obsc + 8])[0]
bat.extBmsDat['countAlarmChargeOvertemp'] = struct.unpack('<H', buff[obsc + 8: obsc + 10])[0]
bat.extBmsDat['countAlarmChargeUndertemp'] = struct.unpack('<H', buff[obsc + 10: obsc + 12])[0]
bat.extBmsDat['countAlarmDischargeOvertemp'] = struct.unpack('<H', buff[obsc + 12: obsc + 14])[0]
bat.extBmsDat['countAlarmDischargeUndertemp'] = struct.unpack('<H', buff[obsc + 14: obsc + 16])[0]
bat.extBmsDat['countAlarmSingleUndervolt'] = struct.unpack('<H', buff[obsc + 16: obsc + 18])[0]
bat.extBmsDat['countAlarmSingleOvervolt'] = struct.unpack('<H', buff[obsc + 18: obsc + 20])[0]
bat.extBmsDat['countAlarmShortCircuit'] = struct.unpack('<H', buff[obsc + 20: obsc + 22])[0]
bat.extBmsDat['countAlarmFrontDetectionIC'] = struct.unpack('<H', buff[obsc + 22: obsc + 24])[0]
bat.extBmsDat['statChargeCycles'] = struct.unpack('<I', buff[obsc + 24: obsc + 28])[0]
bat.extBmsDat['residualCapacity'] = self.__twobytestofloat(buff[obr + 0], buff[obr + 1]) / 100.0
bat.extBmsDat['nominalCapacity'] = self.__twobytestofloat(buff[obr + 2], buff[obr + 3]) / 100.0
bat.extBmsDat['cycleTimes'] = struct.unpack('<H', buff[obr + 4: obr + 6])[0]
bat.extBmsDat['protectionState'] = (buff[obr + 7] << 8) | buff[obr + 6] #struct.unpack('<H', buff[obr + 6: obr + 8])[0]
bat.extBmsDat['balancedState'] = buff[obr + 8]
bat.extBmsDat['mosfetState'] = buff[obr + 9]
bat.alarm['singleOverVolt'] = (bat.extBmsDat['protectionState'] & 0x0001)
bat.alarm['singleUnderVolt'] = (bat.extBmsDat['protectionState'] & 0x0002) >> 1
bat.alarm['groupOverVolt'] = (bat.extBmsDat['protectionState'] & 0x0004) >> 2
bat.alarm['groupUnderVolt'] = (bat.extBmsDat['protectionState'] & 0x0008) >> 3
bat.alarm['chargeOverTemp'] = (bat.extBmsDat['protectionState'] & 0x0010) >> 4 #in case of an alarm this bit toggles with the time that is set as delay for that alarm in BMS. Seems to bee a bug in BMS (alarm comming -> charge mos switches off -> current = 0 -> alarm going -> delay -> alarm comming...)
bat.alarm['chargeUnderTemp'] = (bat.extBmsDat['protectionState'] & 0x0020) >> 5 #in case of an alarm this bit toggles with the time that is set as delay for that alarm in BMS. Seems to bee a bug in BMS (alarm comming -> charge mos switches off -> current = 0 -> alarm going -> delay -> alarm comming...)
bat.alarm['dischargeOverTemp'] = (bat.extBmsDat['protectionState'] & 0x0040) >> 6
bat.alarm['dischargeUnderTemp'] = (bat.extBmsDat['protectionState'] & 0x0080) >> 7
bat.alarm['chargeOverCurrent'] = (bat.extBmsDat['protectionState'] & 0x0100) >> 8
bat.alarm['dischargeOverCurrent'] = (bat.extBmsDat['protectionState'] & 0x0200) >> 9
bat.alarm['shortCircuit'] = (bat.extBmsDat['protectionState'] & 0x0400) >> 10
bat.alarm['frontDetectionIC'] = (bat.extBmsDat['protectionState'] & 0x0800) >> 11
bat.alarm['softwareLockMOS'] = (bat.extBmsDat['protectionState'] & 0x1000) >> 12
return obr + 10 # offset for next data structure
def extractClima(self, cl, buff, offset):
oc = offset
cl['indoorTemp'] = struct.unpack('<f', buff[oc + 0: oc + 4])[0]
cl['indoorTempMin'] = struct.unpack('<f', buff[oc + 4: oc + 8])[0]
cl['indoorTempMax'] = struct.unpack('<f', buff[oc + 8: oc + 12])[0]
cl['indoorHumi'] = struct.unpack('<f', buff[oc + 12: oc + 16])[0]
cl['indoorHumiMin'] = struct.unpack('<f', buff[oc + 16: oc + 20])[0]
cl['indoorHumiMax'] = struct.unpack('<f', buff[oc + 20: oc + 24])[0]
cl['outdoorTemp'] = struct.unpack('<f', buff[oc + 24: oc + 28])[0]
cl['outdoorTempMin'] = struct.unpack('<f', buff[oc + 28: oc + 32])[0]
cl['outdoorTempMax'] = struct.unpack('<f', buff[oc + 32: oc + 36])[0]
cl['outdoorHumi'] = struct.unpack('<f', buff[oc + 36: oc + 40])[0]
cl['outdoorHumiMin'] = struct.unpack('<f', buff[oc + 40: oc + 44])[0]
cl['outdoorHumiMax'] = struct.unpack('<f', buff[oc + 44: oc + 48])[0]
cl['spare1T'] = struct.unpack('<f', buff[oc + 48: oc + 52])[0]
cl['spare1TMin'] = struct.unpack('<f', buff[oc + 52: oc + 56])[0]
cl['spare1TMax'] = struct.unpack('<f', buff[oc + 56: oc + 60])[0]
cl['spare1H'] = struct.unpack('<f', buff[oc + 60: oc + 64])[0]
cl['spare1HMin'] = struct.unpack('<f', buff[oc + 64: oc + 68])[0]
cl['spare1HMax'] = struct.unpack('<f', buff[oc + 68: oc + 72])[0]
cl['spare2T'] = struct.unpack('<f', buff[oc + 72: oc + 76])[0]
cl['spare2TMin'] = struct.unpack('<f', buff[oc + 76: oc + 80])[0]
cl['spare2TMax'] = struct.unpack('<f', buff[oc + 80: oc + 84])[0]
cl['spare2H'] = struct.unpack('<f', buff[oc + 84: oc + 88])[0]
cl['spare2HMin'] = struct.unpack('<f', buff[oc + 88: oc + 92])[0]
cl['spare2HMax'] = struct.unpack('<f', buff[oc + 92: oc + 96])[0]
return oc + 96 # offset for next data structure
def extractTank(self, tanks, buff, offset):
ot = offset
#tanks['t1'] = struct.unpack('<f', buff[ot + 0: ot + 4])[0]
#tanks['t2'] = struct.unpack('<f', buff[ot + 4: ot + 8])[0]
#tanks['t3'] = struct.unpack('<f', buff[ot + 8: ot + 12])[0]
tanks['t1'] = buff[ot + 0]
tanks['t2'] = buff[ot + 1]
tanks['t3'] = buff[ot + 2]
#print("TankLevelOffset: " + str(ot))
#print("TankLevel: " + str(tanks['t1']) + " " + str(tanks['t2'])+ " " + str(tanks['t3']))
return ot + 3 # offset for next data structure
def reqData(self):
comOk = False
bmsRaw = bytearray(self.__REC_BASIC_TEL_LEN)
try:
self._port.reset_input_buffer()
self._port.reset_output_buffer()
self.txCount = self._port.write(self.__hunkReadBasic)
time.sleep(0.1) # wait 100ms until bms hast transmitted all bytes
self.rxCount = self.__receive(self._port, bmsRaw, self.__REC_BASIC_TEL_LEN)
if DEBUG_SERIAL:
print("RxCount: " + str(self.rxCount) + " Expected: " + str(self.__REC_BASIC_TEL_LEN))
if self.rxCount == self.__REC_BASIC_TEL_LEN:
if self.__checkChecksum(bmsRaw, self.__REC_BASIC_TEL_LEN):
# evaluate answer and convert data from bmsRaw to internal variables
offset = 4
offset = self.extractExtBmsDat(self.b1, bmsRaw, offset)
offset = self.extractExtBmsDat(self.b2, bmsRaw, offset)
offset = self.extractClima(self.clima, bmsRaw, offset)
offset = self.extractTank(self.tankLevelRel, bmsRaw, offset)
if DEBUG_SERIAL:
print("Offset last: " + str(offset))
print("1-Volt " + str(self.b1.extBmsDat['voltage']) +
" 1-Curr " + str(self.b1.extBmsDat['current']) +
" 1-statMinCellVoltage " + str(self.b1.extBmsDat['statMinCellVoltage']) +
" 1-statMaxCellVoltage " + str(self.b1.extBmsDat['statMaxCellVoltage']) +
" 1-mosfetState " + str(self.b1.extBmsDat['mosfetState']) +
" 1-protectionState " + str(self.b1.extBmsDat['protectionState']) +
" 2-Volt " + str(self.b2.extBmsDat['voltage']) +
" 2-Curr " + str(self.b2.extBmsDat['current']) +
" 2-statMinCellVoltage " + str(self.b2.extBmsDat['statMinCellVoltage']) +
" 2-statMaxCellVoltage " + str(self.b2.extBmsDat['statMaxCellVoltage']) +
" 2-mosfetState " + str(self.b2.extBmsDat['mosfetState']) +
" 2-protectionState " + str(self.b2.extBmsDat['protectionState']) +
" i-Temp" + str(self.clima['indoorTemp']) +
" i-Humi" + str(self.clima['indoorHumi'])
)
comOk = True
except Exception as e:
self.__quitWithMessage(e)
if DEBUG_SIMULATE_BMS_VALUES:
self.data['basic']['totalVoltage'] += 0.1
comOk = True
return comOk
################################################################################################
from gi.repository import GLib
import platform
import argparse
import logging
import sys
import os
#victron energy own packages
sys.path.insert(1, os.path.join(os.path.dirname(__file__), './ext/velib_python'))
from vedbus import VeDbusService
class DbusBattery:
def __init__(self, servicename, busCon, deviceinstance, productname, connection):
self._dbusservice = VeDbusService(servicename, busCon)
if DEBUG_DBUS:
logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance))
# Most simple and short way to add an object with an initial value of 5.
#dbusservice.add_path('/Position', value=5)
# Most advanced wayt to add a path
#dbusservice.add_path('/RPM', value=100, description='RPM setpoint', writeable=True, onchangecallback=validate_new_value, gettextcallback=get_text_for_rpm)
# 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', 'Process42 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', '42')
self._dbusservice.add_path('/ProductName', productname)
self._dbusservice.add_path('/FirmwareVersion', HUNKCONTROL_VERSION)
self._dbusservice.add_path('/HardwareVersion', 1)
self._dbusservice.add_path('/Connected', 1)
self._dbusservice.add_path('/CustomName', productname)
#Create battery specific objects
self._dbusservice.add_path('/Dc/0/Voltage',0 , writeable=False) # V DC
self._dbusservice.add_path('/Dc/0/Current',0 , writeable=False) # A DC positive when charged, negative when discharged
self._dbusservice.add_path('/Dc/0/Power',0 , writeable=False) # W positive when charged, negative when discharged
self._dbusservice.add_path('/Dc/0/Temperature',0 , writeable=False) # degree C Battery temperature (BMV-702 configured to read temperature only)
# '/Dc/0/MidVoltage' # V DC Mid voltage (BMV-702 configured to read midpoint voltage only) //defined as voltage in the middle of the cell string
# '/Dc/0/MidVoltageDeviation' # Percentage deviation
self._dbusservice.add_path('/ConsumedAmphours',0 , writeable=False) # Ah (BMV, Lynx BMS).
self._dbusservice.add_path('/Soc',0 , writeable=False) # 0 to 100 % (BMV, BYD, Lynx BMS)
self._dbusservice.add_path('/Alarms/Alarm',0 , writeable=False) # BMV, BYD, Lynx BMS
self._dbusservice.add_path('/Alarms/LowVoltage',0 , writeable=False)
self._dbusservice.add_path('/Alarms/HighVoltage',0 , writeable=False)
self._dbusservice.add_path('/Alarms/HighChargeCurrent',0 , writeable=False)
self._dbusservice.add_path('/Alarms/HighDischargeCurrent',0 , writeable=False)
self._dbusservice.add_path('/Alarms/CellImbalance',0 , writeable=False)
self._dbusservice.add_path('/Alarms/InternalFailure',0 , writeable=False)
self._dbusservice.add_path('/Alarms/HighChargeTemperature',0 , writeable=False)
self._dbusservice.add_path('/Alarms/LowChargeTemperature',0 , writeable=False)
self._dbusservice.add_path('/Alarms/LowCellVoltage',0 , writeable=False)
self._dbusservice.add_path('/Alarms/HighCellVoltage',0 , writeable=False) # not documented by victron
self._dbusservice.add_path('/Alarms/LowTemperature',0 , writeable=False)
self._dbusservice.add_path('/Alarms/HighTemperature',0 , writeable=False)
# '/Alarms/MidVoltage'
self._dbusservice.add_path('/History/ChargeCycles',0 , writeable=False)
self._dbusservice.add_path('/History/TotalAhDrawn',0 , writeable=False)
self._dbusservice.add_path('/History/MinimumVoltage',0 , writeable=False)
self._dbusservice.add_path('/History/MaximumVoltage',0 , writeable=False)
self._dbusservice.add_path('/History/LowVoltageAlarms',0 , writeable=False)
self._dbusservice.add_path('/History/HighVoltageAlarms',0 , writeable=False)
self._dbusservice.add_path('/History/DischargedEnergy',0 , writeable=False)
self._dbusservice.add_path('/History/ChargedEnergy',0 , writeable=False)
self._dbusservice.add_path('/History/MinimumCellVoltage',0 , writeable=False)
self._dbusservice.add_path('/History/MaximumCellVoltage',0 , writeable=False)
self._dbusservice.add_path('/Balancing',0 , writeable=False)
self._dbusservice.add_path('/System/NrOfBatteries',0 , writeable=False)
self._dbusservice.add_path('/System/BatteriesParallel',0 , writeable=False)
self._dbusservice.add_path('/System/BatteriesSeries',0 , writeable=False)
self._dbusservice.add_path('/System/NrOfCellsPerBattery',0 , writeable=False)
self._dbusservice.add_path('/System/MinCellVoltage',0 , writeable=False)
self._dbusservice.add_path('/System/MaxCellVoltage',0 , writeable=False)
#from here on listed paths are no victron paths
self._dbusservice.add_path('/smartBMS/AquisitionCycle',0 , writeable=False) # to see if script is running
self._dbusservice.add_path('/smartBMS/voltageMin', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/voltageMax', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/currentMin', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/currentMax', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/temp1', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/temp2', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/chargedAmphours', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/statChargedAmpHours', 0, writeable=False)
# following three values are the same like in /History/... They are doubled published here because History values are not displayed in dbus-spy
self._dbusservice.add_path('/smartBMS/statDischargedAmpHours', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/statChargedEnergy', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/statDischargedEnergy', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmUndervolt', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmOvervolt', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmChargeOvercurrent', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmDischargeOvercurrent', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmChargeOvertemp', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmChargeUndertemp', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmDischargeOvertemp', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmDischargeUndertemp', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmSingleUndervolt', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmSingleOvervolt', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmShortCircuit', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/countAlarmFrontDetectionIC', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/nominalCapacity', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/residualCapacity', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/cycleTimes', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/protectionState', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/balancedState', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/mosfetState', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/alarm/shortCircuit', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/alarm/frontDetectionIC', 0, writeable=False)
self._dbusservice.add_path('/smartBMS/alarm/softwareLockMOS', 0, writeable=False)
def update(self, batDat):
self._calcVictronVal(batDat)
return True
def _calcVictronVal(self, batDat):
self._dbusservice['/Dc/0/Voltage'] = round(batDat.extBmsDat['voltage'], 2)
self._dbusservice['/Dc/0/Current'] = round(batDat.extBmsDat['current'], 2)
self._dbusservice['/Dc/0/Power'] = round(batDat.extBmsDat['power'], 2)
self._dbusservice['/Dc/0/Temperature'] = round( (batDat.extBmsDat['temp1'] + batDat.extBmsDat['temp2']) / 2, 2)
self._dbusservice['/ConsumedAmphours'] = round( batDat.extBmsDat['consumedAmphours'], 3) #it is more an estimation. Accuracy can by verry bad due to low sample time
self._dbusservice['/Soc'] = round(batDat.extBmsDat['soc'], 2)
#on victron dbus For all alarms: 0=OK; 1=Warning; 2=Alarm
self._dbusservice['/Alarms/Alarm'] = 2 if (batDat.extBmsDat['protectionState'] & 0x7FF) else 0 # alarm if any of the first 11 bits is set. Bit 12 (softwareLockMOS) shall not generate a summary alarm
self._dbusservice['/Alarms/LowVoltage'] = batDat.alarm['groupUnderVolt'] * 2
self._dbusservice['/Alarms/HighVoltage'] = batDat.alarm['groupOverVolt'] * 2
self._dbusservice['/Alarms/HighChargeCurrent'] = batDat.alarm['chargeOverCurrent'] * 2
self._dbusservice['/Alarms/HighDischargeCurrent'] = batDat.alarm['dischargeOverCurrent'] * 2
#self._dbusservice['/Alarms/CellImbalance'] =
#self._dbusservice['/Alarms/InternalFailure'] =
self._dbusservice['/Alarms/HighChargeTemperature'] = batDat.alarm['chargeOverTemp'] * 2
self._dbusservice['/Alarms/LowChargeTemperature'] = batDat.alarm['chargeUnderTemp'] * 2
self._dbusservice['/Alarms/LowCellVoltage'] = batDat.alarm['singleUnderVolt'] * 2
self._dbusservice['/Alarms/HighCellVoltage'] = batDat.alarm['singleOverVolt'] * 2 # not documented by victron
self._dbusservice['/Alarms/LowTemperature'] = batDat.alarm['dischargeUnderTemp'] * 2
self._dbusservice['/Alarms/HighTemperature'] = batDat.alarm['dischargeOverTemp'] * 2
if DEBUG_ALARMS:
print("AlarmSummary: " + str(self._dbusservice['/Alarms/Alarm']) +
" siOvVolt " + str(self._dbusservice['/Alarms/HighCellVoltage']) +
" siUnVolt " + str(self._dbusservice['/Alarms/LowCellVoltage']) +
" grOvVolt " + str(self._dbusservice['/Alarms/HighVoltage']) +
" grUnVolt " + str(self._dbusservice['/Alarms/LowVoltage']) +
" chaOvTemp " + str(self._dbusservice['/Alarms/HighChargeTemperature']) +
" chaUnTemp " + str(self._dbusservice['/Alarms/LowChargeTemperature']) +
" dischaOvT " + str(self._dbusservice['/Alarms/HighTemperature']) +
" dischaUnT " + str(self._dbusservice['/Alarms/LowTemperature']) +
" chaOvCurr " + str(self._dbusservice['/Alarms/HighChargeCurrent']) +
" dischaOvCurr " + str(self._dbusservice['/Alarms/HighDischargeCurrent']) +
" shortCirc " + str(batDat.alarm['shortCircuit']) +
" frontDetectIC " + str(batDat.alarm['frontDetectionIC'])
)
print("AlamrCounter: " + str(self._dbusservice['/Alarms/Alarm']) +
" siOvVolt " + str(self._dbusservice['/smartBMS/countAlarmSingleOvervolt']) +
" siUnVolt " + str(self._dbusservice['/smartBMS/countAlarmSingleUndervolt']) +
" grOvVolt " + str(self._dbusservice['/smartBMS/countAlarmOvervolt']) +
" grUnVolt " + str(self._dbusservice['/smartBMS/countAlarmUndervolt']) +
" chaOvTemp " + str(self._dbusservice['/smartBMS/countAlarmChargeOvertemp']) +
" chaUnTemp " + str(self._dbusservice['/smartBMS/countAlarmChargeUndertemp']) +
" dischaOvT " + str(self._dbusservice['/smartBMS/countAlarmDischargeOvertemp']) +
" dischaUnT " + str(self._dbusservice['/smartBMS/countAlarmDischargeUndertemp']) +
" chaOvCurr " + str(self._dbusservice['/smartBMS/countAlarmChargeOvercurrent']) +
" dischaOvCurr " + str(self._dbusservice['/smartBMS/countAlarmDischargeOvercurrent']) +
" shortCirc " + str(self._dbusservice['/smartBMS/countAlarmShortCircuit']) +
" frontDetectIC " + str(self._dbusservice['/smartBMS/countAlarmFrontDetectionIC'])
)
self._dbusservice['/History/ChargeCycles'] = batDat.extBmsDat['statChargeCycles']
self._dbusservice['/History/TotalAhDrawn'] = round(batDat.extBmsDat['statDischargedAmpHours'], 3)
self._dbusservice['/History/MinimumVoltage'] = round(batDat.extBmsDat['statMinVoltage'], 3)
self._dbusservice['/History/MaximumVoltage'] = round(batDat.extBmsDat['statMaxVoltage'], 3)
self._dbusservice['/History/LowVoltageAlarms'] = batDat.extBmsDat['countAlarmUndervolt']
self._dbusservice['/History/HighVoltageAlarms'] = batDat.extBmsDat['countAlarmOvervolt']
self._dbusservice['/History/DischargedEnergy'] = round(batDat.extBmsDat['statDischargedEnergy'] / 1000.0 , 3) #unit is kWh
self._dbusservice['/History/ChargedEnergy'] = round(batDat.extBmsDat['statChargedEnergy'] / 1000.0, 3) #unit is kWh
self._dbusservice['/History/MinimumCellVoltage'] = round(batDat.extBmsDat['statMinCellVoltage'], 3)
self._dbusservice['/History/MaximumCellVoltage'] = round(batDat.extBmsDat['statMaxCellVoltage'], 3)
self._dbusservice['/System/MinCellVoltage'] = round(batDat.extBmsDat['sysMinCellVoltage'], 3)
self._dbusservice['/System/MaxCellVoltage'] = round(batDat.extBmsDat['sysMaxCellVoltage'], 3)
self._dbusservice['/Balancing'] = batDat.extBmsDat['balancedState']
#from here on listed paths are no victron paths
self._dbusservice['/smartBMS/AquisitionCycle'] += 1
self._dbusservice['/smartBMS/voltageMin'] = batDat.extBmsDat['voltageMin']
self._dbusservice['/smartBMS/voltageMax'] = batDat.extBmsDat['voltageMax']
self._dbusservice['/smartBMS/currentMin'] = batDat.extBmsDat['currentMin']
self._dbusservice['/smartBMS/currentMax'] = batDat.extBmsDat['currentMax']
self._dbusservice['/smartBMS/temp1'] = batDat.extBmsDat['temp1']
self._dbusservice['/smartBMS/temp2'] = batDat.extBmsDat['temp2']
self._dbusservice['/smartBMS/chargedAmphours'] = batDat.extBmsDat['chargedAmphours']
self._dbusservice['/smartBMS/statChargedAmpHours'] = batDat.extBmsDat['statChargedAmpHours']
#following three values are the same like in /History/... They are doubled published here because History values are not displayed in dbus-spy
self._dbusservice['/smartBMS/statDischargedAmpHours'] = self._dbusservice['/History/TotalAhDrawn']
self._dbusservice['/smartBMS/statChargedEnergy'] = self._dbusservice['/History/ChargedEnergy']
self._dbusservice['/smartBMS/statDischargedEnergy'] = self._dbusservice['/History/DischargedEnergy']
self._dbusservice['/smartBMS/countAlarmUndervolt'] = batDat.extBmsDat['countAlarmUndervolt']
self._dbusservice['/smartBMS/countAlarmOvervolt'] = batDat.extBmsDat['countAlarmOvervolt']
self._dbusservice['/smartBMS/countAlarmChargeOvercurrent'] = batDat.extBmsDat['countAlarmChargeOvercurrent']
self._dbusservice['/smartBMS/countAlarmDischargeOvercurrent'] = batDat.extBmsDat['countAlarmDischargeOvercurrent']
self._dbusservice['/smartBMS/countAlarmChargeOvertemp'] = batDat.extBmsDat['countAlarmChargeOvertemp']
self._dbusservice['/smartBMS/countAlarmChargeUndertemp'] = batDat.extBmsDat['countAlarmChargeUndertemp']
self._dbusservice['/smartBMS/countAlarmDischargeOvertemp'] = batDat.extBmsDat['countAlarmDischargeOvertemp']
self._dbusservice['/smartBMS/countAlarmDischargeUndertemp'] = batDat.extBmsDat['countAlarmDischargeUndertemp']
self._dbusservice['/smartBMS/countAlarmSingleUndervolt'] = batDat.extBmsDat['countAlarmSingleUndervolt']
self._dbusservice['/smartBMS/countAlarmSingleOvervolt'] = batDat.extBmsDat['countAlarmSingleOvervolt']
self._dbusservice['/smartBMS/countAlarmShortCircuit'] = batDat.extBmsDat['countAlarmShortCircuit']
self._dbusservice['/smartBMS/countAlarmFrontDetectionIC'] = batDat.extBmsDat['countAlarmFrontDetectionIC']
self._dbusservice['/smartBMS/nominalCapacity'] = batDat.extBmsDat['nominalCapacity']
self._dbusservice['/smartBMS/residualCapacity'] = batDat.extBmsDat['residualCapacity']
self._dbusservice['/smartBMS/cycleTimes'] = batDat.extBmsDat['cycleTimes']
self._dbusservice['/smartBMS/protectionState'] = ''.join(['{0:016b}'.format(batDat.extBmsDat['protectionState'])])
self._dbusservice['/smartBMS/balancedState'] = ''.join(['{0:08b}'.format(batDat.extBmsDat['balancedState'])])
self._dbusservice['/smartBMS/mosfetState'] = ''.join(['{0:08b}'.format(batDat.extBmsDat['mosfetState'])]) #charge MOSFET on -> bit0 = true; discharge MOSFET on -> bit1 = true
self._dbusservice['/smartBMS/alarm/shortCircuit'] = batDat.alarm['shortCircuit']
self._dbusservice['/smartBMS/alarm/frontDetectionIC'] = batDat.alarm['frontDetectionIC']
self._dbusservice['/smartBMS/alarm/softwareLockMOS'] = batDat.alarm['softwareLockMOS']
def _handlechangedvalue(self, path, value):
if DEBUG_DBUS:
logging.debug("someone else updated %s to %s" % (path, value))
return True # accept the change
class DbusTemperature:
def __init__(self, servicename, busCon, deviceinstance, productname, connection):
self._dbusservice = VeDbusService(servicename, busCon)
if DEBUG_DBUS:
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', 'Process42 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', '42')
self._dbusservice.add_path('/ProductName', productname)
self._dbusservice.add_path('/FirmwareVersion', 1)
self._dbusservice.add_path('/HardwareVersion', 1)
self._dbusservice.add_path('/Connected', 1)
self._dbusservice.add_path('/CustomName', productname, writeable=True)
#Create battery specific objects
self._dbusservice.add_path('/Temperature',0 , writeable=False) # degrees Celsius
self._dbusservice.add_path('/Humidity', 0, writeable=False) # percent
self._dbusservice.add_path('/Status',0 , writeable=False) # 0=Ok; 1=Disconnected; 2=Short circuited; 3=Reverse polarity; 4=Unknown
self._dbusservice.add_path('/Scale',0 , writeable=False) # <- normally not necessary! devices should be calibrated.
self._dbusservice.add_path('/Offset', 0, writeable=False) # <- normally not necessary! devices should be calibrated.
#self._dbusservice.add_path('/TemperatureType', 2, writeable=False) # 0=battery; 1=fridge; 2=generic
self._dbusservice.add_path('/TemperatureMin', 2, writeable=False) # no official victron dbus value
self._dbusservice.add_path('/TemperatureMax', 2, writeable=False) # no official victron dbus value
self._dbusservice.add_path('/HumidityMin', 2, writeable=False) # no official victron dbus value
self._dbusservice.add_path('/HumidityMax', 2, writeable=False) # no official victron dbus value
def update(self, temperature, temperatureMin, temperatureMax, humi, humiMin, humiMax):
self._dbusservice['/Temperature'] = round(temperature, 1)
self._dbusservice['/TemperatureMin'] = round(temperatureMin, 1)
self._dbusservice['/TemperatureMax'] = round(temperatureMax, 1)
self._dbusservice['/Humidity'] = round(humi, 0)
self._dbusservice['/HumidityMin'] = round(humiMin, 0)
self._dbusservice['/HumidityMax'] = round(humiMax, 0)
return True
def _handlechangedvalue(self, path, value):
if DEBUG_DBUS:
logging.debug("someone else updated %s to %s" % (path, value))
return True # accept the change
class DbusTank:
def __init__(self, servicename, busCon, deviceinstance, productname, connection):
self._dbusservice = VeDbusService(servicename, busCon)
if DEBUG_DBUS:
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', 'Process42 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', '42')
self._dbusservice.add_path('/ProductName', productname)
self._dbusservice.add_path('/FirmwareVersion', 1)
self._dbusservice.add_path('/HardwareVersion', 1)
self._dbusservice.add_path('/Connected', 1)
self._dbusservice.add_path('/CustomName', productname, writeable=True)
#Create battery specific objects
self._dbusservice.add_path('/Level',0 , writeable=False) # 0 to 100%
self._dbusservice.add_path('/Remaining',0 , writeable=False) # volume in m3
self._dbusservice.add_path('/Status',0 , writeable=False) # 0=Ok; 1=Disconnected; 2=Short circuited; 3=Unknown
self._dbusservice.add_path('/Capacity', 0, writeable=False) # volume in m3
self._dbusservice.add_path('/FluidType', 1, writeable=False) # 0=Fuel; 1=Fresh water; 2=Waste water; 3=Live well; 4=Oil; 5=Black water (sewage)
self._dbusservice.add_path('/Standard', 0, writeable=False) # 0=European; 1=USA
def update(self, level, maxVolume):
self._dbusservice['/Level'] = round(level, 2)
self._dbusservice['/Capacity'] = round(maxVolume, 3)
self._dbusservice['/Remaining'] = round( (level * maxVolume) / 100, 3)
return True
def _handlechangedvalue(self, path, value):
if DEBUG_DBUS:
logging.debug("someone else updated %s to %s" % (path, value))
return True # accept the change
def main():
#logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.WARNING)
parser = argparse.ArgumentParser()
parser.add_argument("ttyPort")
args = parser.parse_args()
ttyPort = args.ttyPort
if DEBUG_DBUS:
print('Start comunication on Port: ' + ttyPort)
serialPort = serial.Serial('/dev/'+ttyPort,
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0)
hunkobj = hunkController(serialPort) #create hunk handling object for given port
if not hunkobj.checkCom(): #request some data from Hunk Controller to see if its available
if DEBUG_DBUS:
print('Hunk Controller does not answer on serial port')
quit() # exit programm
if DEBUG_DBUS:
print('Hunk Controller answered')
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)
if DEBUG_DBUS:
logging.info('Connected to dbus, and switching over to GLib.MainLoop() (= event based)')
bus0 = dbusconnection()
bat0 = DbusBattery(
servicename='com.victronenergy.battery.b0' + ttyPort,
busCon=bus0,
deviceinstance=0,
productname='7. SmartBMS Summe',
connection='serial')
bus1 = dbusconnection()
bat1 = DbusBattery(
servicename= 'com.victronenergy.battery.b1' + ttyPort,
busCon= bus1,
deviceinstance= 1,
productname= '9. SmartBMS-1(unten)',
connection= 'serial')
bus2 = dbusconnection()
bat2 = DbusBattery(
servicename='com.victronenergy.battery.b2' + ttyPort,
busCon=bus2,
deviceinstance=2,
productname='8. SmartBMS-2(oben)',
connection='serial')
bus3 = dbusconnection()
temp1 = DbusTemperature(
servicename='com.victronenergy.temperature.tempIndooor' + ttyPort,
busCon=bus3,
deviceinstance=111,
productname='1. Temperatur/Feuchte innen',
connection='serial')
bus5 = dbusconnection()
temp2 = DbusTemperature(
servicename='com.victronenergy.temperature.tempOutdooor' + ttyPort,
busCon=bus5,
deviceinstance=121,
productname='2. Temperatur/Feuchte außen',
connection='serial')
bus6 = dbusconnection()
tank1 = DbusTank(
servicename='com.victronenergy.tank.bed' + ttyPort,
busCon=bus6,
deviceinstance=151,
productname='Fr.Wasser Bett',
connection='serial')
bus7 = dbusconnection()
tank2 = DbusTank(
servicename='com.victronenergy.tank.bank' + ttyPort,
busCon=bus7,
deviceinstance=152,
productname='Fr.Wasser Bank',
connection='serial')
hunkobj.linkObjects(bat0, bat1, bat2, temp1, temp2, tank1, tank2)
mainloop = GLib.MainLoop()
mainloop.run()
if __name__ == "__main__":
main()