Skip to content

Commit

Permalink
Merge pull request #466 from baranator/master
Browse files Browse the repository at this point in the history
jkbms_ble: 32s-Support and connection workaround for bt-fail
  • Loading branch information
mr-manuel authored Apr 12, 2023
2 parents 3f67884 + 500e7ab commit f5eb089
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 31 deletions.
87 changes: 68 additions & 19 deletions etc/dbus-serialbattery/jkbms_ble.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
from bleak import BleakScanner, BleakError
import asyncio
import time
import os


class Jkbms_Ble(Battery):
BATTERYTYPE = "Jkbms BLE"
resetting = False

def __init__(self, port, baud, address):
super(Jkbms_Ble, self).__init__("zero", baud)
Expand All @@ -25,20 +27,29 @@ def test_connection(self):
# check if device with given mac is found, otherwise abort

logger.info("test of jkbmsble")
try:
loop = asyncio.get_event_loop()
t = loop.create_task(BleakScanner.discover())
devices = loop.run_until_complete(t)
except BleakError as e:
logger.error(str(e))
return False

found = False
for d in devices:
if d.address == self.jk.address:
found = True
if not found:
return False
tries = 0
while True:
try:
loop = asyncio.get_event_loop()
t = loop.create_task(
BleakScanner.find_device_by_address(self.jk.address)
)
device = loop.run_until_complete(t)

if device is None:
logger.info("jkbmsble not found")
if tries > 2:
return False
else:
# device found, exit loop and continue test
break
except BleakError as e:
if tries > 2:
return False
# recover from error if tries left
logger.error(str(e))
self.reset_bluetooth()
tries += 1

# device was found, presumeably a jkbms so start scraping
self.jk.start_scraping()
Expand All @@ -51,9 +62,12 @@ def test_connection(self):
# load initial data, from here on get_status has valid values to be served to the dbus
status = self.jk.get_status()
if status is None:
self.jk.stop_scraping()
return False


if not status["device_info"]["vendor_id"].startswith(("JK-", "JK_")):
self.jk.stop_scraping()
return False

logger.info("JK BMS found!")
Expand Down Expand Up @@ -97,14 +111,25 @@ def refresh_data(self):
return False
if time.time() - st["last_update"] > 30:
# if data not updated for more than 30s, sth is wrong, then fail
logger.info("jkbmsble: bluetooth died")

# if the thread is still alive but data too old there is sth
# wrong with the bt-connection; restart whole stack
if not self.resetting:
self.reset_bluetooth()
self.jk.start_scraping()
time.sleep(2)

return False
else:
self.resetting = False

for c in range(self.cell_count):
self.cells[c].voltage = st["cell_info"]["voltages"][c]

self.to_temp(1, st["cell_info"]["temperature_sensor_1"])
self.to_temp(2, st["cell_info"]["temperature_sensor_2"])
self.to_temp('mos', st["cell_info"]["temperature_mos"])
self.to_temp("mos", st["cell_info"]["temperature_mos"])
self.current = st["cell_info"]["current"]
self.voltage = st["cell_info"]["total_voltage"]

Expand All @@ -116,11 +141,18 @@ def refresh_data(self):
self.balance_fet = st["settings"]["balancing_switch"]

self.balancing = False if st["cell_info"]["balancing_action"] == 0.000 else True
self.balancing_current = st["cell_info"]["balancing_current"] if st["cell_info"]["balancing_current"] < 32768 else ( 65536/1000 - st["cell_info"]["balancing_current"] ) * -1
self.balancing_current = (
st["cell_info"]["balancing_current"]
if st["cell_info"]["balancing_current"] < 32768
else ( 65536/1000 - st["cell_info"]["balancing_current"] ) * -1
)
self.balancing_action = st["cell_info"]["balancing_action"]

for c in range(self.cell_count):
if self.balancing and (st["cell_info"]["max_voltage_cell"] == c or st["cell_info"]["min_voltage_cell"] == c ):
if self.balancing and (
st["cell_info"]["max_voltage_cell"] == c
or st["cell_info"]["min_voltage_cell"] == c
):
self.cells[c].balance = True
else:
self.cells[c].balance = False
Expand All @@ -129,9 +161,13 @@ def refresh_data(self):
# self.protection.soc_low = 2 if status["cell_info"]["battery_soc"] < 10.0 else 0

# trigger cell imbalance warning when delta is to great
if st["cell_info"]["delta_cell_voltage"] > min(st["settings"]["cell_ovp"] * 0.05, 0.200):
if st["cell_info"]["delta_cell_voltage"] > min(
st["settings"]["cell_ovp"] * 0.05, 0.200
):
self.protection.cell_imbalance = 2
elif st["cell_info"]["delta_cell_voltage"] > min(st["settings"]["cell_ovp"] * 0.03, 0.120):
elif st["cell_info"]["delta_cell_voltage"] > min(
st["settings"]["cell_ovp"] * 0.03, 0.120
):
self.protection.cell_imbalance = 1
else:
self.protection.cell_imbalance = 0
Expand All @@ -157,6 +193,19 @@ def refresh_data(self):
)
return True

def reset_bluetooth(self):
logger.info("reset of bluetooth triggered")
self.resetting = True
# if self.jk.is_running():
# self.jk.stop_scraping()
logger.info("scraping ended, issuing sys-commands")
os.system("kill -9 $(pidof bluetoothd)")
# os.system("/etc/init.d/bluetooth stop") is not enugh, kill -9 via pid is needed
time.sleep(2)
os.system("rfkill block bluetooth")
os.system("rfkill unblock bluetooth")
os.system("/etc/init.d/bluetooth start")
logger.info("bluetooth should have been restarted")

def get_balancing(self):
return 1 if self.balancing else 0
42 changes: 30 additions & 12 deletions etc/dbus-serialbattery/jkbms_brn.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# zero means parse all incoming data (every second)
CELL_INFO_REFRESH_S = 0
DEVICE_INFO_REFRESH_S = 60 * 60 * 5 # every 5 Hours
DEVICE_INFO_REFRESH_S = 60 * 60 * 10 # every 10 Hours
CHAR_HANDLE = "0000ffe1-0000-1000-8000-00805f9b34fb"
MODEL_NBR_UUID = "00002a24-0000-1000-8000-00805f9b34fb"

Expand Down Expand Up @@ -56,12 +56,12 @@


TRANSLATE_CELL_INFO = [
[["cell_info", "voltages", 16], 6, "<H", 0.001],
[["cell_info", "voltages", 32], 6, "<H", 0.001],
[["cell_info", "average_cell_voltage"], 58, "<H", 0.001],
[["cell_info", "delta_cell_voltage"], 60, "<H", 0.001],
[["cell_info", "max_voltage_cell"], 62, "<B"],
[["cell_info", "min_voltage_cell"], 63, "<B"],
[["cell_info", "resistances", 16], 64, "<H", 0.001],
[["cell_info", "resistances", 32], 64, "<H", 0.001],
[["cell_info", "total_voltage"], 118, "<H", 0.001],
[["cell_info", "current"], 126, "<l", 0.001],
[["cell_info", "temperature_sensor_1"], 130, "<H", 0.1],
Expand Down Expand Up @@ -100,26 +100,32 @@ async def scanForDevices(self):
print(d)

# iterative implementation maybe later due to referencing
def translate(self, fb, translation, o, i=0):
def translate(self, fb, translation, o, f32s=False, i=0):
if i == len(translation[0]) - 1:
# keep things universal by using an n=1 list
kees = (
range(0, translation[0][i])
if isinstance(translation[0][i], int)
else [translation[0][i]]
)
offset = 0
if f32s:
if translation[1] >= 112:
offset = 32
elif translation[1] >= 54:
offset = 16
i = 0
for j in kees:
if isinstance(translation[2], int):
# handle raw bytes without unpack_from;
# 3. param gives no format but number of bytes
val = bytearray(
fb[translation[1] + i : translation[1] + i + translation[2]]
fb[translation[1] + i + offset: translation[1] + i + translation[2] + offset]
)
i += translation[2]
else:
val = unpack_from(
translation[2], bytearray(fb), translation[1] + i
translation[2], bytearray(fb), translation[1] + i + offset
)[0]
# calculate stepping in case of array
i = i + calcsize(translation[2])
Expand All @@ -137,7 +143,7 @@ def translate(self, fb, translation, o, i=0):
else:
o[translation[0][i]] = {}

self.translate(fb, translation, o[translation[0][i]], i + 1)
self.translate(fb, translation, o[translation[0][i]], f32s=f32s, i=i + 1)

def decode_warnings(self, fb):
val = unpack_from("<H", bytearray(fb), 136)[0]
Expand Down Expand Up @@ -166,8 +172,9 @@ def decode_device_info_jk02(self):

def decode_cellinfo_jk02(self):
fb = self.frame_buffer
has32s = (fb[189] == 0x00 and fb[189 + 32] > 0)
for t in TRANSLATE_CELL_INFO:
self.translate(fb, t, self.bms_status)
self.translate(fb, t, self.bms_status, f32s=has32s)
self.decode_warnings(fb)
debug(self.bms_status)

Expand All @@ -184,6 +191,11 @@ def decode(self):
info("Processing frame with settings info")
if protocol_version == PROTOCOL_VERSION_JK02:
self.decode_settings_jk02()
# adapt translation table for cell array lengths
ccount = self.bms_status["settings"]["cell_count"]
for i, t in enumerate(TRANSLATE_CELL_INFO):
if t[0][-2] == "voltages" or t[0][-2] == "voltages":
TRANSLATE_CELL_INFO[i][0][-1] = ccount
self.bms_status["last_update"] = time.time()

elif info_type == 0x02:
Expand Down Expand Up @@ -308,6 +320,7 @@ async def asy_connect_and_scrape(self):
client = BleakClient(self.address)
print("btloop")
try:
print("reconnect")
await client.connect()
self.bms_status["model_nbr"] = (
await client.read_gatt_char(MODEL_NBR_UUID)
Expand All @@ -320,15 +333,16 @@ async def asy_connect_and_scrape(self):
# await self.enable_charging(client)
last_dev_info = time.time()
while client.is_connected and self.run and self.main_thread.is_alive():
if time.time() - last_dev_info > DEVICE_INFO_REFRESH_S:
last_dev_info = time.time()
await self.request_bt("device_info", client)
await asyncio.sleep(0.01)
except Exception as e:
info("error while connecting to bt: " + str(e))
self.run = False
finally:
await client.disconnect()
if client.is_connected:
try:
await client.disconnect()
except Exception as e:
info("error while disconnecting")

print("Exiting bt-loop")

Expand All @@ -346,8 +360,12 @@ def start_scraping(self):

def stop_scraping(self):
self.run = False
stop = time.time()
while self.is_running():
time.sleep(0.1)
if time.time() - stop > 10:
return False
return True

def is_running(self):
return self.bt_thread.is_alive()
Expand Down

0 comments on commit f5eb089

Please sign in to comment.