Why does Venus OS buffer or delay power measurements? Causes ESS swinging

Hello and happy new year!

i have the following Setup:

  • Venus OS v3.52 on RaspberryPi 2B
  • Multiplus 1 24/3000/70 with Firmware v552 Connected via VE-Bus interface MK3
  • VM-3P75CT with Firmware v1.08 connected via Modbus UDP (Ethernet Cable)

The Energy Meter is placed directly behind the electrical Billing Meter of my house, so both meters should display similar measurements. The whole system works as ESS. The Multiplus ist connected on Phase L3 and “Multiphase regulation” is set to “Total of all phases”. The Multiplus is connected via its AC input to the house and has no loads on its AC output. My PV-Inverter is connected directly to the houses circuit braker panel (parallel with Multiplus and all other loads in the house).

Most of the time everything is fine and the ESS keeps the momentary power at grid connection point around 0W. But sometimes, about once per day, the ESS starts swinging. It toggles between charging and discharging with full power (about 2000W) in a period of seconds and later minutes. It can only stop it by switching of the Multiplus
or disconnection the ethernet cable from Energy Meter.

I am very shure that there is some kind of buffer for the power values in Venus OS because even a minute after disconnecting the ethernet cable from the Energy Meter i could observe fast changing power values of the Energy Meter in Venus OS before it was finally displayed as disconnected. If the ESS algorithm is working with such “old” measurements it is unsurprisingly swinging.

In the attached picture you can see the “old” power value of the Energy Meter that is displayed by Venus OS, the “real” power value of the Energy Meter on the Victron Connect app and to proof it the red “0.02 kW” is the power measured by my Billing Meter.

In the attached screenshot you can see how the battery is charged at the beginning and then the ESS starts “swinging”. At the beginnig fast with little power and then slower with +/- 2kW while the in house consumtion is relativly constant around 200W. At the end i switched of the Multiplus.

My questions are: Has anybody else had a similar problem? How can this buffering be avoided?


Let me try a wild guess (read: no, I have not had this problem), based on the fact that the Pi 2B is 10 years old and therefore rather slow, while the VM-3P75CT is as fast as they get with 10 updates per second. So it might well be that the Pi is too slow to handle everything that’s going on when there is other background processes besides the ESS running.

If you’re familiar with the Linux command line, ssh to the device and look at “top” and “vmstat 5” while the issue is occuring, whether one of the CPU cores (or all of them) are at 100%. Maybe also “netstat -anu” to see if the Recv-Q on the relevant port is increasing (which would be the buffer in the OS).

If that is the case, the solution would be to get a newer RPi, model 4 definitely works with VenusOS, you’d have to check if model 5 is also supported.

1 Like

I don’t think that the RPI is “to slow”, but at the end it is responsible for forwarding the measurement to the inverter / vrm.

And apparently, there is an issue in doing so.

Please also check your dbus-round trip time, while this issue is happening (Adavanced Graphs):

Are you running 3rd party scripts? Some of the (older) still use legacy implementations of registering themself on dbus, causing the whole bus to get stuck for some time, while the script is registering itself.

A script failing and continiously restarting may therefore overload the dbus from time to time.

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()


Indeed, roundtrip time is very high.

minimum changes you should do is changing the registration way. Instead of:

do

self._dbusservice = VeDbusService(servicename, busCon, register=False)

and once you published all the mandatory paths, call

self._dbusservice.register()

repeat for all your services, this will significantly reduce the stuck-time caused, when other services try to read from your service(s) while they are “half-registered”.

But unless your script is restarting every ~ 3-5 Minutes, that may not be the cause for the high roundtrip times. (But eventually it crashes and gets restarted without noticing every now and then?)

Also check the connectivities of your devices. Seeing a lot of try / retry and synchronous operations. Mind the looper is “waiting” for synchronous operations to complete, hence can’t process other stuff while “waiting” for a slow wifi device or failing serial connection, etc.

Sorry for late response. I will implement your recomendations in the next days. Currently i can not test with the ESS because my Batteries in the camper are not heated and we freezing temperatures for more that a week now.