Skip to content

Commit

Permalink
JKBMS BLE - Introduction of automatic SOC reset (HW Version 11) (#736)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ArendsM authored Sep 17, 2023
1 parent b1880f5 commit 7c5f1c7
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 9 deletions.
8 changes: 8 additions & 0 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
12 changes: 12 additions & 0 deletions etc/dbus-serialbattery/bms/jkbms_ble.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
53 changes: 45 additions & 8 deletions etc/dbus-serialbattery/bms/jkbms_brn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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__":
Expand Down
2 changes: 1 addition & 1 deletion etc/dbus-serialbattery/config.default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
3 changes: 3 additions & 0 deletions etc/dbus-serialbattery/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down

0 comments on commit 7c5f1c7

Please sign in to comment.