Skip to content

Commit

Permalink
Add bulk voltage
Browse files Browse the repository at this point in the history
Load to bulk voltage every x days to reset the SoC to 100% for some BMS
  • Loading branch information
mr-manuel committed Jun 29, 2023
1 parent bc267e1 commit e9d6e55
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel
* Added: Implement callback function for update by @seidler2547
* Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel
* Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel
* Added: Save custom name and make it restart persistant by @mr-manuel
* Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel
* Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel
Expand Down
86 changes: 73 additions & 13 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ def init_values(self):
self.cells: List[Cell] = []
self.control_charging = None
self.control_voltage = None
self.bulk_requested = False
self.bulk_last_reached = 0
self.bulk_battery_voltage = None
self.max_battery_voltage = None
self.min_battery_voltage = None
self.allow_max_voltage = True
Expand Down Expand Up @@ -239,8 +242,32 @@ def manage_charge_voltage(self) -> None:
self.charge_mode = "Keep always max voltage"

def prepare_voltage_management(self) -> None:
self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2)
self.max_battery_voltage = round(utils.MAX_CELL_VOLTAGE * self.cell_count, 2)
self.min_battery_voltage = round(utils.MIN_CELL_VOLTAGE * self.cell_count, 2)

bulk_last_reached_days_ago = (
0
if self.bulk_last_reached == 0
else (((int(time()) - self.bulk_last_reached) / 60 / 60 / 24))
)
# set bulk_requested to True, if the days are over
# it gets set to False once the bulk voltage was reached once
if (
utils.BULK_AFTER_DAYS is not False
and self.bulk_requested is not False
and self.allow_max_voltage
and (
self.bulk_last_reached == 0
or utils.BULK_AFTER_DAYS < bulk_last_reached_days_ago
)
):
logger.info(
f"set bulk_requested to True: first time or {utils.BULK_AFTER_DAYS}"
+ f" < {round(bulk_last_reached_days_ago, 2)}"
)
self.bulk_requested = True
self.max_battery_voltage = self.bulk_battery_voltage

def manage_charge_voltage_linear(self) -> None:
"""
Expand All @@ -252,21 +279,32 @@ def manage_charge_voltage_linear(self) -> None:
penaltySum = 0
tDiff = 0
current_time = int(time())

# meassurment and variation tolerance in volts
measurementToleranceVariation = 0.022
measurementToleranceVariation = 0.025

try:
# calculate battery sum
# calculate battery sum and check for cell overvoltage
for i in range(self.cell_count):
voltage = self.get_cell_voltage(i)
if voltage:
voltageSum += voltage

# calculate penalty sum to prevent single cell overcharge by using current cell voltage
if voltage > utils.MAX_CELL_VOLTAGE:
if (
self.max_battery_voltage != self.bulk_battery_voltage
and voltage > utils.MAX_CELL_VOLTAGE
):
# foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
foundHighCellVoltage = True
penaltySum += voltage - utils.MAX_CELL_VOLTAGE
elif (
self.max_battery_voltage == self.bulk_battery_voltage
and voltage > utils.BULK_CELL_VOLTAGE
):
# foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
foundHighCellVoltage = True
penaltySum += voltage - utils.BULK_CELL_VOLTAGE

voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage()

Expand Down Expand Up @@ -331,22 +369,30 @@ def manage_charge_voltage_linear(self) -> None:

self.charge_mode = (
"Bulk dynamic"
if self.max_voltage_start_time is None
# if self.max_voltage_start_time is None # remove this line after testing
if self.max_battery_voltage == self.bulk_battery_voltage
else "Absorption dynamic"
)

elif self.allow_max_voltage:
self.control_voltage = round(self.max_battery_voltage, 3)
self.charge_mode = (
# "Bulk" if self.max_voltage_start_time is None else "Absorption"
"Bulk"
if self.max_voltage_start_time is None
# if self.max_voltage_start_time is None # remove this line after testing
if self.max_battery_voltage == self.bulk_battery_voltage
else "Absorption"
)

else:
floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3)
chargeMode = "Float"
# reset bulk when going into float
if self.bulk_requested:
logger.info("set bulk_requested to False")
self.bulk_requested = False
# IDEA: Save "bulk_last_reached" in the dbus path com.victronenergy.settings
# to make it restart persistent
self.bulk_last_reached = current_time
if self.control_voltage:
if not self.charge_mode.startswith("Float"):
self.transition_start_time = current_time
Expand Down Expand Up @@ -382,7 +428,7 @@ def manage_charge_voltage_linear(self) -> None:
self.charge_mode += " (Linear Mode)"

# uncomment for enabling debugging infos in GUI
"""
# """
self.charge_mode_debug = (
f"max_battery_voltage: {round(self.max_battery_voltage, 2)}V"
)
Expand All @@ -408,7 +454,15 @@ def manage_charge_voltage_linear(self) -> None:
self.charge_mode_debug += (
f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}"
)
"""
self.charge_mode_debug += "\nbulk_last_reached: " + str(
"Never"
if self.bulk_last_reached == 0
else str(
round((current_time - self.bulk_last_reached) / 60 / 60 / 24, 2)
)
+ " days ago"
)
# """

except TypeError:
self.control_voltage = None
Expand All @@ -433,6 +487,7 @@ def manage_charge_voltage_step(self) -> None:
"""
voltageSum = 0
tDiff = 0
current_time = int(time())

try:
# calculate battery sum
Expand All @@ -448,7 +503,7 @@ def manage_charge_voltage_step(self) -> None:
and self.allow_max_voltage
):
# example 2
self.max_voltage_start_time = time()
self.max_voltage_start_time = current_time

# check if reset soc is greater than battery soc
# this prevents flapping between max and float voltage
Expand All @@ -464,7 +519,7 @@ def manage_charge_voltage_step(self) -> None:

# timer started
else:
tDiff = time() - self.max_voltage_start_time
tDiff = current_time - self.max_voltage_start_time
if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
self.allow_max_voltage = False
self.max_voltage_start_time = None
Expand All @@ -481,6 +536,11 @@ def manage_charge_voltage_step(self) -> None:
else:
self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count
self.charge_mode = "Float"
# reset bulk when going into float
if self.bulk_requested:
logger.info("set bulk_requested to False")
self.bulk_requested = False
self.bulk_last_reached = current_time

self.charge_mode += " (Step Mode)"

Expand Down
21 changes: 18 additions & 3 deletions etc/dbus-serialbattery/config.default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@ MAX_BATTERY_DISCHARGE_CURRENT = 60.0
; Description: Cell min/max voltages which are used to calculate the min/max battery voltage
; Example: 16 cells * 3.45V/cell = 55.2V max charge voltage. 16 cells * 2.90V = 46.4V min discharge voltage
MIN_CELL_VOLTAGE = 2.900
; Max voltage can seen as absorption voltage
; Max voltage (can seen as absorption voltage)
MAX_CELL_VOLTAGE = 3.450
; Float voltage (can be seen as resting voltage)
FLOAT_CELL_VOLTAGE = 3.375

; Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS)
; Has to be higher as the MAX_CELL_VOLTAGE
BULK_CELL_VOLTAGE = 3.650
; Specify after how many days the bulk voltage should be reached again
; The timer is reset when the bulk voltage is reached
; Leave empty if you don't want to use this
; Example: Value is set to 15
; day 1: bulk reached once
; day 16: bulk reached twice
; day 31: bulk not reached since it's very cloudy
; day 34: bulk reached since the sun came out
; day 49: bulk reached again, since last time it took 3 days to reach bulk voltage
BULK_AFTER_DAYS =

; --------- Bluetooth BMS ---------
; Description: List the Bluetooth BMS here that you want to install
; -- Available Bluetooth BMS:
Expand Down Expand Up @@ -135,7 +150,7 @@ CCCM_SOC_ENABLE = True
; Discharge current control management enable (True/False).
DCCM_SOC_ENABLE = True

; Charge current soc limits
; Charge current SoC limits
CC_SOC_LIMIT1 = 98
CC_SOC_LIMIT2 = 95
CC_SOC_LIMIT3 = 91
Expand All @@ -145,7 +160,7 @@ CC_CURRENT_LIMIT1_FRACTION = 0.1
CC_CURRENT_LIMIT2_FRACTION = 0.3
CC_CURRENT_LIMIT3_FRACTION = 0.5

; Discharge current soc limits
; Discharge current SoC limits
DC_SOC_LIMIT1 = 10
DC_SOC_LIMIT2 = 20
DC_SOC_LIMIT3 = 30
Expand Down
33 changes: 32 additions & 1 deletion etc/dbus-serialbattery/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _get_list_from_config(


# Constants - Need to dynamically get them in future
DRIVER_VERSION = "1.0.20230625dev"
DRIVER_VERSION = "1.0.20230629dev"
zero_char = chr(48)
degree_sign = "\N{DEGREE SIGN}"

Expand Down Expand Up @@ -66,6 +66,29 @@ def _get_list_from_config(
">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration."
)

# Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS)
# Has to be higher as the MAX_CELL_VOLTAGE
BULK_CELL_VOLTAGE = float(config["DEFAULT"]["BULK_CELL_VOLTAGE"])
if BULK_CELL_VOLTAGE < MAX_CELL_VOLTAGE:
BULK_CELL_VOLTAGE = MAX_CELL_VOLTAGE
logger.error(
">>> ERROR: BULK_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration."
)
# Specify after how many days the bulk voltage should be reached again
# The timer is reset when the bulk voltage is reached
# Leave empty if you don't want to use this
# Example: Value is set to 15
# day 1: bulk reached once
# day 16: bulk reached twice
# day 31: bulk not reached since it's very cloudy
# day 34: bulk reached since the sun came out
# day 49: bulk reached again, since last time it took 3 days to reach bulk voltage
BULK_AFTER_DAYS = (
int(config["DEFAULT"]["BULK_AFTER_DAYS"])
if config["DEFAULT"]["BULK_AFTER_DAYS"] != ""
else False
)

# --------- BMS disconnect behaviour ---------
# Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the
# BMS on purpose, then you have to restart the driver/system to reset the block.
Expand Down Expand Up @@ -157,6 +180,10 @@ def _get_list_from_config(
CELL_VOLTAGES_WHILE_CHARGING = _get_list_from_config(
"DEFAULT", "CELL_VOLTAGES_WHILE_CHARGING", lambda v: float(v)
)
if CELL_VOLTAGES_WHILE_CHARGING[0] < MAX_CELL_VOLTAGE:
logger.error(
">>> ERROR: Maximum value of CELL_VOLTAGES_WHILE_CHARGING is set to a value lower than MAX_CELL_VOLTAGE. Please check the configuration."
)
MAX_CHARGE_CURRENT_CV = _get_list_from_config(
"DEFAULT",
"MAX_CHARGE_CURRENT_CV_FRACTION",
Expand All @@ -166,6 +193,10 @@ def _get_list_from_config(
CELL_VOLTAGES_WHILE_DISCHARGING = _get_list_from_config(
"DEFAULT", "CELL_VOLTAGES_WHILE_DISCHARGING", lambda v: float(v)
)
if CELL_VOLTAGES_WHILE_DISCHARGING[0] > MIN_CELL_VOLTAGE:
logger.error(
">>> ERROR: Minimum value of CELL_VOLTAGES_WHILE_DISCHARGING is set to a value greater than MIN_CELL_VOLTAGE. Please check the configuration."
)
MAX_DISCHARGE_CURRENT_CV = _get_list_from_config(
"DEFAULT",
"MAX_DISCHARGE_CURRENT_CV_FRACTION",
Expand Down

0 comments on commit e9d6e55

Please sign in to comment.