From 7c5f1c774a014c8bdb1f595754e7a9b8ac834410 Mon Sep 17 00:00:00 2001 From: ArendsM <136503378+ArendsM@users.noreply.github.com> Date: Sun, 17 Sep 2023 11:31:57 +0200 Subject: [PATCH] JKBMS BLE - Introduction of automatic SOC reset (HW Version 11) (#736) * Introduction of automatic SOC reset for JK BMS (HW Version 11) * Fixed value mapping * Rework of the code to make it simpler to use without additional configuration. Moved execution of SOC reset. It's now executed while changing from "Float" to "Float Transition". * Implementation of suggested changes Persist initial BMS OVP and OVPR settings Make use of max_cell_voltage to calculate trigger value for OVP alert --- etc/dbus-serialbattery/battery.py | 8 ++++ etc/dbus-serialbattery/bms/jkbms_ble.py | 12 +++++ etc/dbus-serialbattery/bms/jkbms_brn.py | 53 +++++++++++++++++++---- etc/dbus-serialbattery/config.default.ini | 2 +- etc/dbus-serialbattery/utils.py | 3 ++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index e3e73401..f93d8a56 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -408,6 +408,8 @@ def manage_charge_voltage_linear(self) -> None: self.transition_start_time = current_time self.initial_control_voltage = self.control_voltage chargeMode = "Float Transition" + # Assume battery SOC ist 100% at this stage + self.trigger_soc_reset() elif self.charge_mode.startswith("Float Transition"): elapsed_time = current_time - self.transition_start_time # Voltage reduction per second @@ -1396,3 +1398,9 @@ def force_discharging_off_callback(self, path, value): def turn_balancing_off_callback(self, path, value): return + + def trigger_soc_reset(self): + """ + This method can be used to implement SOC reset when the battery is assumed to be full + """ + return diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index 9029da28..46742807 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -2,6 +2,7 @@ from battery import Battery, Cell from typing import Callable from utils import logger +import utils from time import sleep, time from bms.jkbms_brn import Jkbms_Brn import os @@ -85,6 +86,11 @@ def get_settings(self): self.max_battery_voltage = st["cell_ovp"] * self.cell_count self.min_battery_voltage = st["cell_uvp"] * self.cell_count + # Persist initial OVP and OPVR settings of JK BMS BLE + if self.jk.ovp_initial_voltage is None or self.jk.ovpr_initial_voltage is None: + self.jk.ovp_initial_voltage = st["cell_ovp"] + self.jk.ovpr_initial_voltage = st["cell_ovpr"] + # "User Private Data" field in APP tmp = self.jk.get_status()["device_info"]["production"] self.custom_field = tmp if tmp != "Input Us" else None @@ -253,3 +259,9 @@ def reset_bluetooth(self): def get_balancing(self): return 1 if self.balancing else 0 + + def trigger_soc_reset(self): + if utils.AUTO_RESET_SOC: + self.jk.max_cell_voltage = self.get_max_cell_voltage() + self.jk.trigger_soc_reset = True + return diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index be5e20ab..1f19a881 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -21,6 +21,8 @@ FRAME_VERSION_JK02_32S = 0x03 PROTOCOL_VERSION_JK02 = 0x02 +JK_REGISTER_OVPR = 0x05 +JK_REGISTER_OVP = 0x04 protocol_version = PROTOCOL_VERSION_JK02 @@ -92,9 +94,17 @@ class Jkbms_Brn: _new_data_callback = None + # Variables to control automatic SOC reset for BLE connected JK BMS + # max_cell_voltage will be updated when a SOC reset is requested + max_cell_voltage = None + # OVP and OVPR will be persisted after the first successful readout of the BMS settings + ovp_initial_voltage = None + ovpr_initial_voltage = None + def __init__(self, addr): self.address = addr self.bt_thread = threading.Thread(target=self.connect_and_scrape) + self.trigger_soc_reset = False async def scanForDevices(self): devices = await BleakScanner.discover() @@ -281,7 +291,7 @@ def crc(self, arr: bytearray, length: int) -> int: return crc.to_bytes(2, "little")[0] async def write_register( - self, address, vals: bytearray, length: int, bleakC: BleakClient + self, address, vals: bytearray, length: int, bleakC: BleakClient, awaitresponse: bool ): frame = bytearray(20) frame[0] = 0xAA # start sequence @@ -304,8 +314,10 @@ async def write_register( frame[17] = 0x00 frame[18] = 0x00 frame[19] = self.crc(frame, len(frame) - 1) - logging.debug("Write register: ", frame) - await bleakC.write_gatt_char(CHAR_HANDLE, frame, False) + logging.debug("Write register: " + str(address) + " " + str(frame)) + await bleakC.write_gatt_char(CHAR_HANDLE, frame, response=awaitresponse) + if awaitresponse: + await asyncio.sleep(5) async def request_bt(self, rtype: str, client): timeout = time() @@ -323,7 +335,7 @@ async def request_bt(self, rtype: str, client): else: return - await self.write_register(cmd, b"\0\0\0\0", 0x00, client) + await self.write_register(cmd, b"\0\0\0\0", 0x00, client, False) def get_status(self): if "settings" in self.bms_status and "cell_info" in self.bms_status: @@ -358,6 +370,9 @@ async def asy_connect_and_scrape(self): # await self.enable_charging(client) # last_dev_info = time() while client.is_connected and self.run and self.main_thread.is_alive(): + if self.trigger_soc_reset: + self.trigger_soc_reset = False + await self.reset_soc_jk(client) await asyncio.sleep(0.01) except Exception as err: self.run = False @@ -406,10 +421,32 @@ async def enable_charging(self, c): # data is 01 00 00 00 for on 00 00 00 00 for off; # the following bytes up to 19 are unclear and changing # dynamically -> auth-mechanism? - await self.write_register(0x1D, b"\x01\x00\x00\x00", 4, c) - await self.write_register(0x1E, b"\x01\x00\x00\x00", 4, c) - await self.write_register(0x1F, b"\x01\x00\x00\x00", 4, c) - await self.write_register(0x40, b"\x01\x00\x00\x00", 4, c) + await self.write_register(0x1D, b"\x01\x00\x00\x00", 4, c, True) + await self.write_register(0x1E, b"\x01\x00\x00\x00", 4, c, True) + await self.write_register(0x1F, b"\x01\x00\x00\x00", 4, c, True) + await self.write_register(0x40, b"\x01\x00\x00\x00", 4, c, True) + + def jk_float_to_hex_little(self, val: float): + intval = int(val * 1000) + hexval = f'{intval:0>8X}' + return bytearray.fromhex(hexval)[::-1] + + async def reset_soc_jk(self, c): + # Lowering OVPR / OVP based on the maximum cell voltage at the time + # That will trigger a High Voltage Alert and resets SOC to 100% + ovp_trigger = round(self.max_cell_voltage - 0.05, 3) + ovpr_trigger = round(self.max_cell_voltage - 0.10, 3) + await self.write_register(JK_REGISTER_OVPR, self.jk_float_to_hex_little(ovpr_trigger), 0x04, c, True) + await self.write_register(JK_REGISTER_OVP, self.jk_float_to_hex_little(ovp_trigger), 0x04, c, True) + + # Give BMS some time to recognize + await asyncio.sleep(5) + + # Set values back to initial values + await self.write_register(JK_REGISTER_OVP, self.jk_float_to_hex_little(self.ovp_initial_voltage), 0X04, c, True) + await self.write_register(JK_REGISTER_OVPR, self.jk_float_to_hex_little(self.ovpr_initial_voltage), 0x04, c, True) + + logging.info("JK BMS SOC reset finished.") if __name__ == "__main__": diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 8bfd9ff8..4ae1371e 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -221,7 +221,7 @@ CUSTOM_BATTERY_NAMES = ; Auto reset SoC ; If on, then SoC is reset to 100%, if the value switches from absorption to float voltage -; Currently only working for Daly BMS +; Currently only working for Daly BMS and JK BMS BLE AUTO_RESET_SOC = True ; Publish the config settings to the dbus path "/Info/Config/" diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 040c7643..2bb73355 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -210,6 +210,9 @@ def _get_list_from_config( "DEFAULT", "CUSTOM_BATTERY_NAMES", lambda v: str(v) ) +# Auto reset SoC +# If on, then SoC is reset to 100%, if the value switches from absorption to float voltage +# Currently only working for Daly BMS and JK BMS BLE AUTO_RESET_SOC = "True" == config["DEFAULT"]["AUTO_RESET_SOC"] PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"])