Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jkbms_ble: 32s-Support and connection workaround for bt-fail #466

Merged
merged 27 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 67 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,11 @@ 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-"):
self.jk.stop_scraping()
return False

logger.info("JK BMS found!")
Expand Down Expand Up @@ -97,14 +110,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 +140,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 +160,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 +192,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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why you chose 10 hours and not 12 hours?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh: That line can be removed completely as it not used anymore - device-info only contains non-changing data that do not need to be updated after connect.

That change comes from back then, when we tried to figure out where that crashes come from...

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