diff --git a/etc/dbus-serialbattery/jkbms_ble.py b/etc/dbus-serialbattery/jkbms_ble.py index 228d0bd6..34ca6a20 100644 --- a/etc/dbus-serialbattery/jkbms_ble.py +++ b/etc/dbus-serialbattery/jkbms_ble.py @@ -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) @@ -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() @@ -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!") @@ -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"] @@ -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 @@ -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 @@ -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 diff --git a/etc/dbus-serialbattery/jkbms_brn.py b/etc/dbus-serialbattery/jkbms_brn.py index 5a3bea37..dc17bcff 100644 --- a/etc/dbus-serialbattery/jkbms_brn.py +++ b/etc/dbus-serialbattery/jkbms_brn.py @@ -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" @@ -56,12 +56,12 @@ TRANSLATE_CELL_INFO = [ - [["cell_info", "voltages", 16], 6, "= 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]) @@ -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(" 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) @@ -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: @@ -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) @@ -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") @@ -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()