Skip to content

Commit

Permalink
Merge pull request #749 from mr-manuel/dev
Browse files Browse the repository at this point in the history
Changes 2023.07.11
  • Loading branch information
mr-manuel committed Jul 14, 2023
2 parents bc267e1 + ca3a366 commit a24d6ef
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 22 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
96 changes: 83 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,38 @@ 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
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 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 (0) or {utils.BULK_AFTER_DAYS}"
+ f" < {round(bulk_last_reached_days_ago, 2)}"
)
self.bulk_requested = True

self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2)

if self.bulk_requested:
self.max_battery_voltage = self.bulk_battery_voltage
else:
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)

def manage_charge_voltage_linear(self) -> None:
"""
Expand All @@ -252,21 +285,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.5

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 +375,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 +434,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 +460,19 @@ def manage_charge_voltage_linear(self) -> None:
self.charge_mode_debug += (
f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}"
)
"""
bulk_days_ago = round(
(current_time - self.bulk_last_reached) / 60 / 60 / 24, 2
)
bulk_in_days = round(utils.BULK_AFTER_DAYS - bulk_days_ago, 2)
self.charge_mode_debug += "\nbulk_last_reached: " + str(
"Never"
if self.bulk_last_reached == 0
else str(bulk_days_ago)
+ " days ago - next in "
+ str(bulk_in_days)
+ "days"
)
# """

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

try:
# calculate battery sum
Expand All @@ -448,7 +513,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 +529,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 +546,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
6 changes: 5 additions & 1 deletion etc/dbus-serialbattery/bms/jkbms.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ def read_status_data(self):
unpack_from(">H", self.get_data(status_data, b"\x99", offset, 2))[0]
)

# the JKBMS resets to 95% SoC, if all cell voltages are above or equal to 3.500 V
# the JKBMS resets to
# 95% SoC, if all cell voltages are above or equal to OVPR (Over Voltage Protection Recovery)
# 100% Soc, if all cell voltages are above or equal to OVP (Over Voltage Protection)
offset = cellbyte_count + 18
self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0]

Expand Down Expand Up @@ -279,6 +281,8 @@ def to_protection_bits(self, byte_data):
# MOSFET temperature alarm
self.protection.temp_high_internal = 2 if is_bit_set(tmp[pos - 1]) else 0
# charge over voltage alarm
# TODO: check if "self.bulk_requested is False" works,
# else use "self.bulk_last_reached < int(time()) - (60 * 60)"
self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0
# discharge under voltage alarm
self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0
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
18 changes: 14 additions & 4 deletions etc/dbus-serialbattery/dbushelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import os
import platform
import dbus
import dbus # pyright: ignore[reportMissingImports]
import traceback
from time import time

Expand All @@ -14,8 +14,10 @@
"/opt/victronenergy/dbus-systemcalc-py/ext/velib_python",
),
)
from vedbus import VeDbusService # noqa: E402
from settingsdevice import SettingsDevice # noqa: E402
from vedbus import VeDbusService # noqa: E402 # pyright: ignore[reportMissingImports]
from settingsdevice import ( # noqa: E402 # pyright: ignore[reportMissingImports]
SettingsDevice,
)
from utils import logger, publish_config_variables # noqa: E402
import utils # noqa: E402

Expand Down Expand Up @@ -500,7 +502,15 @@ def publish_dbus(self):
self._dbusservice[
"/Alarms/LowCellVoltage"
] = self.battery.protection.voltage_cell_low
self._dbusservice["/Alarms/HighVoltage"] = self.battery.protection.voltage_high
# disable high voltage warning temporarly, if loading to bulk voltage and bulk voltage reached is 30 minutes ago
self._dbusservice["/Alarms/HighVoltage"] = (
self.battery.protection.voltage_high
if (
self.battery.bulk_requested is False
and self.battery.bulk_last_reached < int(time()) - (60 * 30)
)
else 0
)
self._dbusservice["/Alarms/LowSoc"] = self.battery.protection.soc_low
self._dbusservice[
"/Alarms/HighChargeCurrent"
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.20230711dev"
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 a24d6ef

Please sign in to comment.