Skip to content

Commit

Permalink
Merge branch 'dev' into ogdev
Browse files Browse the repository at this point in the history
  • Loading branch information
ogurevich committed Jun 26, 2023
2 parents 4fc9e43 + 54d013c commit 465413e
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 155 deletions.
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@
* 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: 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
* Changed: Fix daly readsentence by @transistorgit
* Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel
* Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel
* Changed: Fixed that other devices are recognized as ANT BMS by @mr-manuel
* Changed: Fixed that other devices are recognized as Sinowealth BMS by @mr-manuel
* Changed: Fixed typo in `config.ini` sample by @hoschult
* Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel
* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel
* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel
* Changed: Improved battery error handling on connection loss by @mr-manuel
* Changed: Improved battery voltage handling in linear absorption mode by @ogurevich
* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel
* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel
* Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS
* Removed: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel


## v1.0.20230531
Expand Down
211 changes: 130 additions & 81 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ def init_values(self):
self.min_battery_voltage = None
self.allow_max_voltage = True
self.max_voltage_start_time = None
self.transition_start_time = None
self.control_voltage_at_transition_start = None
self.charge_mode = None
self.charge_limitation = None
self.discharge_limitation = None
Expand Down Expand Up @@ -141,7 +143,7 @@ def unique_identifier(self) -> str:
On +/- 5 Ah you can identify 11 batteries
"""
string = (
"".join(filter(str.isalnum, self.hardware_version)) + "_"
"".join(filter(str.isalnum, str(self.hardware_version))) + "_"
if self.hardware_version is not None and self.hardware_version != ""
else ""
)
Expand Down Expand Up @@ -250,48 +252,46 @@ def manage_charge_voltage_linear(self) -> None:
tDiff = 0

try:
if utils.CVCM_ENABLE:
# calculate battery sum
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:
# foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
foundHighCellVoltage = True
penaltySum += voltage - utils.MAX_CELL_VOLTAGE

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

if self.max_voltage_start_time is None:
# start timer, if max voltage is reached and cells are balanced
if (
self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum
and voltageDiff
<= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL
and self.allow_max_voltage
):
self.max_voltage_start_time = int(time())

# allow max voltage again, if cells are unbalanced or SoC threshold is reached
elif (
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
) and not self.allow_max_voltage:
self.allow_max_voltage = True
else:
tDiff = int(time()) - self.max_voltage_start_time
# 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
# we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode
# regardless of whether we were in absorption mode or not
if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP:
self.max_voltage_start_time = None
# calculate battery sum
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:
# foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
foundHighCellVoltage = True
penaltySum += voltage - utils.MAX_CELL_VOLTAGE

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

if self.max_voltage_start_time is None:
# start timer, if max voltage is reached and cells are balanced
if (
self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum
and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL
and self.allow_max_voltage
):
self.max_voltage_start_time = int(time())

# allow max voltage again, if cells are unbalanced or SoC threshold is reached
elif (
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
) and not self.allow_max_voltage:
self.allow_max_voltage = True
else:
tDiff = int(time()) - self.max_voltage_start_time
# 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
# we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode
# regardless of whether we were in absorption mode or not
if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP:
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.
Expand Down Expand Up @@ -343,10 +343,32 @@ def manage_charge_voltage_linear(self) -> None:
)

else:
self.control_voltage = round(
(utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3
)
self.charge_mode = "Float"
floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3)
chargeMode = "Float"
if self.control_voltage:
if not self.charge_mode.startswith("Float"):
self.transition_start_time = int(time())
self.initial_control_voltage = self.control_voltage
chargeMode = "Float Transition"
elif self.charge_mode.startswith("Float Transition"):
elapsed_time = int(time()) - self.transition_start_time
# Voltage drop per second
VOLTAGE_DROP_PER_SECOND = 0.01 / 10
voltage_drop = min(
VOLTAGE_DROP_PER_SECOND * elapsed_time,
self.initial_control_voltage - floatVoltage,
)
self.control_voltage = (
self.initial_control_voltage - voltage_drop
)
if self.control_voltage <= floatVoltage:
self.control_voltage = floatVoltage
chargeMode = "Float"
else:
chargeMode = "Float Transition"
else:
self.control_voltage = floatVoltage
self.charge_mode = chargeMode

if (
self.allow_max_voltage
Expand All @@ -370,43 +392,42 @@ def manage_charge_voltage_step(self) -> None:
tDiff = 0

try:
if utils.CVCM_ENABLE:
# calculate battery sum
for i in range(self.cell_count):
voltage = self.get_cell_voltage(i)
if voltage:
voltageSum += voltage

if self.max_voltage_start_time is None:
# check if max voltage is reached and start timer to keep max voltage
if (
self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum
and self.allow_max_voltage
):
# example 2
self.max_voltage_start_time = time()

# check if reset soc is greater than battery soc
# this prevents flapping between max and float voltage
elif (
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
and not self.allow_max_voltage
):
self.allow_max_voltage = True

# do nothing
else:
pass
# calculate battery sum
for i in range(self.cell_count):
voltage = self.get_cell_voltage(i)
if voltage:
voltageSum += voltage

if self.max_voltage_start_time is None:
# check if max voltage is reached and start timer to keep max voltage
if (
self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum
and self.allow_max_voltage
):
# example 2
self.max_voltage_start_time = time()

# check if reset soc is greater than battery soc
# this prevents flapping between max and float voltage
elif (
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
and not self.allow_max_voltage
):
self.allow_max_voltage = True

# timer started
# do nothing
else:
tDiff = time() - self.max_voltage_start_time
if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
self.allow_max_voltage = False
self.max_voltage_start_time = None
pass

else:
pass
# timer started
else:
tDiff = time() - self.max_voltage_start_time
if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
self.allow_max_voltage = False
self.max_voltage_start_time = None

else:
pass

if self.allow_max_voltage:
self.control_voltage = self.max_battery_voltage
Expand Down Expand Up @@ -981,6 +1002,34 @@ def get_mos_temp(self) -> Union[float, None]:
else:
return None

def validate_data(self) -> bool:
"""
Used to validate the data received from the BMS.
If the data is in the thresholds return True,
else return False since it's very probably not a BMS
"""
if self.capacity is not None and (self.capacity < 0 or self.capacity > 1000):
logger.debug(
"Capacity outside of thresholds (from 0 to 1000): " + str(self.capacity)
)
return False
if self.current is not None and abs(self.current) > 1000:
logger.debug(
"Current outside of thresholds (from -1000 to 1000): "
+ str(self.current)
)
return False
if self.voltage is not None and (self.voltage < 0 or self.voltage > 100):
logger.debug(
"Voltage outside of thresholds (form 0 to 100): " + str(self.voltage)
)
return False
if self.soc is not None and (self.soc < 0 or self.soc > 100):
logger.debug("SoC outside of thresholds (from 0 to 100): " + str(self.soc))
return False

return True

def log_cell_data(self) -> bool:
if logger.getEffectiveLevel() > logging.INFO and len(self.cells) == 0:
return False
Expand Down
9 changes: 0 additions & 9 deletions etc/dbus-serialbattery/bms/ant.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,9 @@ def read_status_data(self):

voltage = unpack_from(">H", status_data, 4)
self.voltage = voltage[0] * 0.1
# check if data is in the thresholds, if not it's very likely that it's not an ANT BMS
if self.voltage < 0 and self.voltage > 100:
return False

current, self.soc = unpack_from(">lB", status_data, 70)
self.current = 0.0 if current == 0 else current / -10
# check if data is in the thresholds, if not it's very likely that it's not an ANT BMS
if self.soc < 0 or self.soc > 100 or abs(self.current) > 1000:
return False

self.cell_count = unpack_from(">b", status_data, 123)[0]
self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
Expand All @@ -87,9 +81,6 @@ def read_status_data(self):

capacity = unpack_from(">L", status_data, 75)
self.capacity = capacity[0] / 1000000
# check if data is in the thresholds, if not it's very likely that it's not an ANT BMS
if self.capacity < 0 or self.capacity > 1000:
return False

capacity_remain = unpack_from(">L", status_data, 79)
self.capacity_remain = capacity_remain[0] / 1000000
Expand Down
5 changes: 3 additions & 2 deletions etc/dbus-serialbattery/bms/battery_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ def test_connection(self):
try:
result = self.read_status_data()
# get first data to show in startup log, only if result is true
if result:
self.refresh_data()
result = result and self.refresh_data()
except Exception as err:
logger.error(f"Unexpected {err=}, {type(err)=}")
result = False
Expand Down Expand Up @@ -87,6 +86,8 @@ def read_status_data(self):
self.cycles,
) = unpack_from(">bb??bhx", status_data)

# Integrate a check to be sure, that the received data is from the BMS type you are making this driver for

self.hardware_version = "TemplateBMS " + str(self.cell_count) + " cells"
logger.info(self.hardware_version)
return True
Expand Down
10 changes: 9 additions & 1 deletion etc/dbus-serialbattery/bms/daly.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,10 @@ def write_soc_and_datetime(self, ser):
if self.soc_to_set is None:
return False

# wait shortly, else the Daly is not ready and throws a lot of no reply errors
# if you see a lot of errors, try to increase in steps of 0.005
sleep(0.020)

cmd = bytearray(13)
now = datetime.now()

Expand Down Expand Up @@ -577,7 +581,7 @@ def write_soc_and_datetime(self, ser):
ser.write(cmd)

reply = self.read_sentence(ser, self.command_set_soc)
if reply[0] != 1:
if reply is False or reply[0] != 1:
logger.error("write soc failed")
return True

Expand Down Expand Up @@ -616,6 +620,10 @@ def write_charge_discharge_mos(self, ser):
):
return False

# wait shortly, else the Daly is not ready and throws a lot of no reply errors
# if you see a lot of errors, try to increase in steps of 0.005
sleep(0.020)

cmd = bytearray(self.command_base)

if self.trigger_force_disable_charge is not None:
Expand Down
Loading

0 comments on commit 465413e

Please sign in to comment.