Skip to content

Aggregation crashes / exits when one serial battery is temporarily disconnected (non-CAN path) #162

Description

@JoanMCD

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 += NoneTypeError 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

  1. NR_OF_BATTERIES >= 2, serial (non-CAN) BMS instances.
  2. Briefly disconnect one BMS (pull RS485, or induce a comms error) so its dbus-serialbattery
    publishes None for V/I/P.
  3. 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:

  1. 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
  2. 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.

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions