Version checked: main (VERSION = "4.3.20260611-beta")
File: dbus-aggregate-batteries.py, method that reads V/I/P from each battery service
Summary
For serial / non-CAN batteries the aggregation loop reads /Dc/0/Voltage, /Dc/0/Current,
/Dc/0/Power and adds them directly, with no None check. When one battery in the bank drops
its connection for a moment, its dbus-serialbattery instance publishes None on those paths.
The aggregator then executes int += None → TypeError and enters an error/retry loop. After
READ_TRIALS it calls sys.exit(1), which removes the aggregate battery service from D-Bus.
On a Victron ESS / VE.Bus system a disappearing battery service triggers a "Low Battery" VE.Bus
alarm and can interrupt inverter operation — a momentary BMS comms hiccup becomes a system alarm.
The CAN path (settings.CAN_batteries) got a ValueError guard, but the serial path is unguarded.
Affected code (current main)
Non-CAN read, no None guard:
# ~line 909, the `else:` (non-CAN) branch
Voltage += self._dbusMon.dbusmon.get_value(self._batteries_dict[i], "/Dc/0/Voltage")
Current += self._dbusMon.dbusmon.get_value(self._batteries_dict[i], "/Dc/0/Current")
Power += self._dbusMon.dbusmon.get_value(self._batteries_dict[i], "/Dc/0/Power")
(Same pattern follows for /InstalledCapacity, /Capacity, /Soc, /Dc/0/Temperature.)
Exit on read failure:
# ~line 1093
logging.error("DBus read failed. Exiting...")
tt.sleep(settings.TIME_BEFORE_RESTART)
sys.exit(1)
Averaging by fixed battery count:
# ~line 1105
Voltage = Voltage / settings.NR_OF_BATTERIES
Temperature = Temperature / settings.NR_OF_BATTERIES
VoltagesSum = sum(VoltagesSum_dict.values()) / settings.NR_OF_BATTERIES
Steps to reproduce
NR_OF_BATTERIES >= 2, serial (non-CAN) BMS instances.
- Briefly disconnect one BMS (pull RS485, or induce a comms error) so its
dbus-serialbattery
publishes None for V/I/P.
- Aggregator throws
TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType',
loops, then sys.exit(1) → com.victronenergy.battery.aggregate disappears from D-Bus
→ VE.Bus "Low Battery" alarm on ESS systems.
Impact
- A single, recoverable BMS disconnect takes down the whole aggregated battery service.
- On ESS this raises a VE.Bus Low Battery alarm and can disturb the inverter, instead of simply
riding through with the remaining batteries.
- While one battery is missing, averaging by
NR_OF_BATTERIES (not the number actually reporting)
also publishes a wrong aggregate voltage/temperature.
Proposed fix (behaviour)
Three independent, small changes make the serial path resilient:
-
None-guard + skip. If any mandatory value is None, skip that battery for the cycle
(don't add it) and remember when it first went missing:
_voltage = get_value(.../Dc/0/Voltage)
_current = get_value(.../Dc/0/Current)
_power = get_value(.../Dc/0/Power)
if _voltage is None or _current is None or _power is None:
# record first-missing timestamp, optionally block charge/discharge after a timeout
continue
batteries_online_count += 1
Voltage += _voltage; Current += _current; Power += _power
-
Don't sys.exit() on read failure. Removing the service from D-Bus is what triggers the
VE.Bus alarm. Keep the service alive holding the last published values; only block
charge/discharge after a safety timeout (e.g. 180 s) if data never recovers.
-
Average by online count. Divide by the number of batteries that actually reported this
cycle, falling back to NR_OF_BATTERIES only when none did:
divisor = batteries_online_count if batteries_online_count > 0 else settings.NR_OF_BATTERIES
Voltage = Voltage / divisor
With these changes a transient disconnect of one battery no longer crashes the aggregator or
raises a VE.Bus alarm; the system keeps running on the remaining batteries and recovers
automatically when the battery returns.
Version checked:
main(VERSION = "4.3.20260611-beta")File:
dbus-aggregate-batteries.py, method that reads V/I/P from each battery serviceSummary
For serial / non-CAN batteries the aggregation loop reads
/Dc/0/Voltage,/Dc/0/Current,/Dc/0/Powerand adds them directly, with noNonecheck. When one battery in the bank dropsits connection for a moment, its
dbus-serialbatteryinstance publishesNoneon those paths.The aggregator then executes
int += None→TypeErrorand enters an error/retry loop. AfterREAD_TRIALSit callssys.exit(1), which removes the aggregate battery service from D-Bus.On a Victron ESS / VE.Bus system a disappearing battery service triggers a "Low Battery" VE.Bus
alarm and can interrupt inverter operation — a momentary BMS comms hiccup becomes a system alarm.
The CAN path (
settings.CAN_batteries) got aValueErrorguard, but the serial path is unguarded.Affected code (current
main)Non-CAN read, no
Noneguard:(Same pattern follows for
/InstalledCapacity,/Capacity,/Soc,/Dc/0/Temperature.)Exit on read failure:
Averaging by fixed battery count:
Steps to reproduce
NR_OF_BATTERIES >= 2, serial (non-CAN) BMS instances.dbus-serialbatterypublishes
Nonefor V/I/P.TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType',loops, then
sys.exit(1)→com.victronenergy.battery.aggregatedisappears from D-Bus→ VE.Bus "Low Battery" alarm on ESS systems.
Impact
riding through with the remaining batteries.
NR_OF_BATTERIES(not the number actually reporting)also publishes a wrong aggregate voltage/temperature.
Proposed fix (behaviour)
Three independent, small changes make the serial path resilient:
None-guard + skip. If any mandatory value isNone, skip that battery for the cycle(don't add it) and remember when it first went missing:
Don't
sys.exit()on read failure. Removing the service from D-Bus is what triggers theVE.Bus alarm. Keep the service alive holding the last published values; only block
charge/discharge after a safety timeout (e.g. 180 s) if data never recovers.
Average by online count. Divide by the number of batteries that actually reported this
cycle, falling back to
NR_OF_BATTERIESonly when none did:With these changes a transient disconnect of one battery no longer crashes the aggregator or
raises a VE.Bus alarm; the system keeps running on the remaining batteries and recovers
automatically when the battery returns.