Skip to content

Commit

Permalink
Changes 2023.05.01
Browse files Browse the repository at this point in the history
* Added: Recalculation interval in linear mode for CVL, CCL and DCL by @mr-manuel
* Changed: Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced by @mr-manuel
  • Loading branch information
mr-manuel committed May 1, 2023
2 parents 4d4d256 + 52fdc4a commit d820050
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 115 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[flake8]
max-line-length = 120
per-file-ignores =
./etc/dbus-serialbattery/utils.py: E501
exclude =
./etc/dbus-serialbattery/bms/battery_template.py,
./etc/dbus-serialbattery/bms/mnb_test_max17853.py,
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
* Added: JKBMS BLE - MOS temperature by @mr-manuel
* Added: JKBMS BLE - Show if balancing is active and which cells are balancing by @mr-manuel
* Added: Post install notes by @mr-manuel
* Added: Recalculation interval in linear mode for CVL, CCL and DCL by @mr-manuel
* Added: Script to install directly from repository by @mr-manuel
* Added: Show charge mode (absorption, bulk, ...) in Parameters page by @mr-manuel
* Added: Show charge/discharge limitation reason by @mr-manuel
* Added: Show MOSFET temperature for JKBMS https://github.com/Louisvdw/dbus-serialbattery/pull/440 by @baphomett
* Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected by @mr-manuel
* Added: Show TimeToGo in GUI only, if enabled by @mr-manuel
* Added: Support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 by @peterohman
Expand Down Expand Up @@ -56,6 +58,7 @@
* Changed: Moved Bluetooth part to `installble.sh` by @mr-manuel
* Changed: Moved BMS scripts to subfolder by @mr-manuel
* Changed: Optimized installation scripts by @mr-manuel
* Changed: Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced by @mr-manuel
* Changed: Removed wildcard imports from several BMS drivers and fixed black lint errors by @mr-manuel
* Changed: Serial-Starter file is now created from `reinstalllocal.sh`. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/520 by @mr-manuel
* Changed: Separate Time-To-Go and Time-To-SoC activation by @mr-manuel
Expand Down
156 changes: 109 additions & 47 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ def __init__(self, port, baud, address):
self.charge_mode = None
self.charge_limitation = None
self.discharge_limitation = None
self.control_voltage_last_set = 0
self.linear_cvl_last_set = 0
self.linear_ccl_last_set = 0
self.linear_dcl_last_set = 0
self.max_voltage_start_time = None
self.control_current = None
self.control_previous_total = None
Expand Down Expand Up @@ -181,41 +183,45 @@ def manage_charge_voltage_linear(self) -> None:
if voltage:
voltageSum += voltage

# calculate penalty sum to prevent single cell overcharge
if voltage >= utils.PENALTY_AT_CELL_VOLTAGE[0]:
# calculate penalty sum to prevent single cell overcharge by using current cell voltage
if voltage > utils.MAX_CELL_VOLTAGE:
# foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
foundHighCellVoltage = True
penaltySum += utils.calcLinearRelationship(
voltage,
utils.PENALTY_AT_CELL_VOLTAGE,
utils.PENALTY_BATTERY_VOLTAGE,
)
penaltySum += voltage - utils.MAX_CELL_VOLTAGE - 0.010

voltageSum = round(voltageSum, 3)
voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage()

if self.max_voltage_start_time is None:
if (
utils.MAX_CELL_VOLTAGE * self.cell_count <= voltageSum
and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL
and self.allow_max_voltage
):
self.max_voltage_start_time = time()
elif (
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
# utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
and not self.allow_max_voltage
):
self.allow_max_voltage = True
else:
tDiff = time() - self.max_voltage_start_time
if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
# if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
# keep max voltage for 300 more seconds
if 300 < tDiff:
self.allow_max_voltage = False
self.max_voltage_start_time = None

# INFO: battery will only switch to Absorption, if all cells are balanced.
# Reach MAX_CELL_VOLTAGE * cell count if they are all balanced.
if foundHighCellVoltage and self.allow_max_voltage:
# set CVL only once every PENALTY_RECALCULATE_EVERY seconds
control_voltage_time = int(time() / utils.PENALTY_RECALCULATE_EVERY)
if control_voltage_time != self.control_voltage_last_set:
# set CVL only once every LINEAR_RECALCULATION_EVERY seconds
if (
int(time()) - self.linear_cvl_last_set
>= utils.LINEAR_RECALCULATION_EVERY
):
self.linear_cvl_last_set = int(time())

# Keep penalty above min battery voltage
self.control_voltage = round(
max(
Expand All @@ -224,26 +230,40 @@ def manage_charge_voltage_linear(self) -> None:
),
3,
)
self.control_voltage_last_set = control_voltage_time

self.charge_mode = (
"Bulk dynamic (linear mode)"
"Bulk dynamic (vS: "
+ str(round(voltageSum, 2))
+ " - pS: "
+ str(round(penaltySum, 2))
+ ")"
if self.max_voltage_start_time is None
else "Absorption dynamic (linear mode)"
else "Absorption dynamic (vS: "
+ str(round(voltageSum, 2))
+ " - pS: "
+ str(round(penaltySum, 2))
+ ")"
)

elif self.allow_max_voltage:
self.control_voltage = round((utils.MAX_CELL_VOLTAGE * self.cell_count), 3)
self.charge_mode = (
"Bulk (linear mode)"
if self.max_voltage_start_time is None
else "Absorption (linear mode)"
"Bulk" if self.max_voltage_start_time is None else "Absorption"
)

else:
self.control_voltage = round(
(utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3
)
self.charge_mode = "Float (linear mode)"
self.charge_mode = "Float"

if (
self.allow_max_voltage
and self.get_balancing()
and voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
):
self.charge_mode += " + Balancing"
self.charge_mode += " (LM)"

def manage_charge_voltage_step(self) -> None:
"""
Expand Down Expand Up @@ -293,14 +313,14 @@ def manage_charge_voltage_step(self) -> None:
if self.allow_max_voltage:
self.control_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
self.charge_mode = (
"Bulk (step mode)"
if self.max_voltage_start_time is None
else "Absorption (step mode)"
"Bulk" if self.max_voltage_start_time is None else "Absorption"
)

else:
self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count
self.charge_mode = "Float (step mode)"
self.charge_mode = "Float"

self.charge_mode += " (Step Mode)"

def manage_charge_current(self) -> None:
# Manage Charge Current Limitations
Expand All @@ -320,10 +340,10 @@ def manage_charge_current(self) -> None:
if self.max_battery_charge_current != tmp:
if tmp in charge_limits_new:
charge_limits_new.update(
{tmp: charge_limits_new[tmp] + ", Cell voltage"}
{tmp: charge_limits_new[tmp] + ", Cell Voltage"}
)
else:
charge_limits_new.update({tmp: "Cell voltage"})
charge_limits_new.update({tmp: "Cell Voltage"})

if utils.CCCM_T_ENABLE:
tmp = self.calcMaxChargeCurrentReferringToTemperature()
Expand Down Expand Up @@ -353,22 +373,44 @@ def manage_charge_current(self) -> None:
else:
charge_limits_new.update({tmp: "SoC"})

self.control_charge_current = round(
min(charge_limits), 3
) # gets changed after finished testing

self.charge_limitation = (
charge_limits_new[min(charge_limits_new)]
+ " ("
+ str(round(min(charge_limits_new), 3))
+ ")"
# do not set CCL immediately, but only
# - after LINEAR_RECALCULATION_EVERY passed
# - if CCL changes to 0
# - if CCL changes more than LINEAR_RECALCULATION_ON_PERC_CHANGE
ccl = round(min(charge_limits), 3) # gets changed after finished testing
diff = (
abs(self.control_charge_current - ccl)
if self.control_charge_current is not None
else 0
)
if (
int(time()) - self.linear_ccl_last_set >= utils.LINEAR_RECALCULATION_EVERY
or ccl == 0
or (
diff
>= self.control_charge_current
* utils.LINEAR_RECALCULATION_ON_PERC_CHANGE
/ 100
)
):
self.linear_ccl_last_set = int(time())

self.control_charge_current = ccl

self.charge_limitation = (
charge_limits_new[min(charge_limits_new)]
+ " ("
+ str(round(min(charge_limits_new), 3))
+ ")"
)

if self.control_charge_current == 0:
self.control_allow_charge = False
else:
self.control_allow_charge = True

#####

# Manage Discharge Current Limitations
discharge_limits = [
self.max_battery_discharge_current
Expand All @@ -388,10 +430,10 @@ def manage_charge_current(self) -> None:
if self.max_battery_discharge_current != tmp:
if tmp in discharge_limits_new:
discharge_limits_new.update(
{tmp: discharge_limits_new[tmp] + ", Cell voltage"}
{tmp: discharge_limits_new[tmp] + ", Cell Voltage"}
)
else:
discharge_limits_new.update({tmp: "Cell voltage"})
discharge_limits_new.update({tmp: "Cell Voltage"})

if utils.DCCM_T_ENABLE:
tmp = self.calcMaxDischargeCurrentReferringToTemperature()
Expand Down Expand Up @@ -425,16 +467,36 @@ def manage_charge_current(self) -> None:
else:
discharge_limits_new.update({tmp: "SoC"})

self.control_discharge_current = round(
min(discharge_limits), 3
) # gets changed after finished testing

self.discharge_limitation = (
discharge_limits_new[min(discharge_limits_new)]
+ " ("
+ str(round(min(discharge_limits_new), 3))
+ ")"
# do not set DCL immediately, but only
# - after LINEAR_RECALCULATION_EVERY passed
# - if DCL changes to 0
# - if DCL changes more than LINEAR_RECALCULATION_ON_PERC_CHANGE
dcl = round(min(discharge_limits), 3) # gets changed after finished testing
diff = (
abs(self.control_discharge_current - dcl)
if self.control_discharge_current is not None
else 0
)
if (
int(time()) - self.linear_dcl_last_set >= utils.LINEAR_RECALCULATION_EVERY
or dcl == 0
or (
diff
>= self.control_discharge_current
* utils.LINEAR_RECALCULATION_ON_PERC_CHANGE
/ 100
)
):
self.linear_dcl_last_set = int(time())

self.control_discharge_current = dcl

self.discharge_limitation = (
discharge_limits_new[min(discharge_limits_new)]
+ " ("
+ str(round(min(discharge_limits_new), 3))
+ ")"
)

if self.control_discharge_current == 0:
self.control_allow_discharge = False
Expand Down
25 changes: 16 additions & 9 deletions etc/dbus-serialbattery/bms/daly.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ def read_cells_volts(self, ser):

# logger.warning("data " + bytes(cells_volts_data).hex())

while (
bufIdx <= len(cells_volts_data) - (4 + 8 + 1)
while bufIdx <= len(cells_volts_data) - (
4 + 8 + 1
): # we at least need 13 bytes to extract the identifiers + 8 bytes payload + checksum
b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx)
if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08:
Expand All @@ -313,7 +313,7 @@ def read_cells_volts(self, ser):
)
bufIdx += 13 # BBBBBhhhBB -> 13 byte
else:
bufIdx += 1 # step through buffer to find valid start
bufIdx += 1 # step through buffer to find valid start
logger.warning("bad cell voltages header")
return True

Expand Down Expand Up @@ -409,20 +409,22 @@ def read_serial_data_daly(self, ser, command):

if len(data) <= 12:
logger.debug("Too short reply to cmd " + bytes(command).hex())
return False;
return False

# search sentence start
try:
idx = data.index(0xA5)
except ValueError:
logger.debug("No Sentence Start found for reply to cmd " + bytes(command).hex())
logger.debug(
"No Sentence Start found for reply to cmd " + bytes(command).hex()
)
return False

if len(data[idx:]) <= 12:
logger.debug("Too short reply to cmd " + bytes(command).hex())
return False;
return False

if data[12+idx] != sum(data[idx:12+idx]) & 0xFF:
if data[12 + idx] != sum(data[idx : 12 + idx]) & 0xFF:
logger.debug("Bad checksum in reply to cmd " + bytes(command).hex())
return False

Expand All @@ -431,7 +433,12 @@ def read_serial_data_daly(self, ser, command):
if length == 8:
return data[4 + idx : length + 4 + idx]
else:
logger.debug(">>> ERROR: Incorrect Reply to CMD " + bytes(command).hex() + ": 0x" + bytes(data).hex())
logger.debug(
">>> ERROR: Incorrect Reply to CMD "
+ bytes(command).hex()
+ ": 0x"
+ bytes(data).hex()
)
return False

# Read data from previously openned serial port
Expand Down Expand Up @@ -510,4 +517,4 @@ def read_serialport_data(

except Exception as e:
logger.error(e)
return False
return False
Loading

0 comments on commit d820050

Please sign in to comment.