From e8865da723eaa4f3f0388387f1f9ab22382a26aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Holger=20Schulthei=C3=9F?= Date: Thu, 1 Jun 2023 12:42:59 +0200 Subject: [PATCH 001/114] Update reinstall-local.sh: Fixed charge current parameter Update reinstall-local.sh: Corrected charge current parameter for initial config.ini --- etc/dbus-serialbattery/reinstall-local.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index a518a100..d153a7e4 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -117,7 +117,7 @@ if [ ! -f "$filename" ]; then echo "; and insert them below to persist future driver updates." echo echo "; Example (remove the semicolon \";\" to uncomment and activate the value/setting):" - echo "; MAX_BATTERY_CURRENT = 50.0" + echo "; MAX_BATTERY_CHARGE_CURRENT = 50.0" echo "; MAX_BATTERY_DISCHARGE_CURRENT = 60.0" echo echo From 1d4deeed04998fceb07975dfdc7c3389ad2bce3a Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 1 Jun 2023 15:01:52 +0200 Subject: [PATCH 002/114] Exclude devices from driver startup This prevents blocking the serial port --- etc/dbus-serialbattery/config.default.ini | 4 ++++ etc/dbus-serialbattery/dbus-serialbattery.py | 12 +++++++++++- etc/dbus-serialbattery/utils.py | 11 +++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index e7c967ff..559de8bd 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -191,6 +191,10 @@ TIME_TO_SOC_INC_FROM = False ; Ant, MNB, Sinowealth BMS_TYPE = +; Exclute this serial devices from the driver startup +; Example: /dev/ttyUSB2, /dev/ttyUSB4 +EXCLUDED_DEVICES = + ; Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = 1 diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 4bca9b35..848c4405 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -100,7 +100,17 @@ def get_battery(_port) -> Union[Battery, None]: def get_port() -> str: # Get the port we need to use from the argument if len(sys.argv) > 1: - return sys.argv[1] + port = sys.argv[1] + if port not in utils.EXCLUDED_DEVICES: + return port + else: + logger.info( + "Stopping dbus-serialbattery: " + + str(port) + + " is excluded trough the config file" + ) + sleep(86400) + sys.exit(0) else: # just for MNB-SPI logger.info("No Port needed") diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index e5f22fa5..1b04323a 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -27,7 +27,10 @@ def _get_list_from_config( ) -> List[Any]: rawList = config[group][option].split(",") return list( - map(mapper, [item for item in rawList if item != "" and item is not None]) + map( + mapper, + [item.strip() for item in rawList if item != "" and item is not None], + ) ) @@ -35,7 +38,7 @@ def _get_list_from_config( # if not specified: baud = 9600 # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230531" +DRIVER_VERSION = "1.0.20230526dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -272,6 +275,10 @@ def _get_list_from_config( # Ant, MNB, Sinowealth BMS_TYPE = config["DEFAULT"]["BMS_TYPE"] +EXCLUDED_DEVICES = _get_list_from_config( + "DEFAULT", "EXCLUDED_DEVICES", lambda v: str(v) +) + # Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"]) From 513a0098376c8ae9314a68c736474b5720ae2dca Mon Sep 17 00:00:00 2001 From: Stefan Seidel Date: Sun, 4 Jun 2023 20:00:42 +0000 Subject: [PATCH 003/114] implement callback function for update --- etc/dbus-serialbattery/battery.py | 13 ++++++++++++- etc/dbus-serialbattery/bms/jkbms_ble.py | 5 +++++ etc/dbus-serialbattery/bms/jkbms_brn.py | 7 +++++++ etc/dbus-serialbattery/dbus-serialbattery.py | 3 ++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index eb5b2e2d..1ce3c5be 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from typing import Union, Tuple, List +from typing import Union, Tuple, List, Callable from utils import logger import utils @@ -150,6 +150,17 @@ def get_settings(self) -> bool: """ return False + def use_callback(self, callback: Callable) -> bool: + """ + Each driver may override this function to indicate whether it is + able to provide value updates on its own. + + :return: false when battery cannot provide updates by itself and will be polled + every poll_interval milliseconds for new values + true if callable should be used for updates as they arrive from the battery + """ + return False + @abstractmethod def refresh_data(self) -> bool: """ diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index 276103c4..5d16d630 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from battery import Battery, Cell +from typing import Callable from utils import logger from bms.jkbms_brn import Jkbms_Brn from bleak import BleakScanner, BleakError @@ -141,6 +142,10 @@ def get_settings(self): logger.info("BAT: " + self.hardware_version) return True + def use_callback(self, callback: Callable) -> bool: + self.jk.set_callback(callback) + return callback is not None + def refresh_data(self): # call all functions that will refresh the battery data. # This will be called for every iteration (1 second) diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 917f291f..82f55eae 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -91,6 +91,8 @@ class Jkbms_Brn: waiting_for_response = "" last_cell_info = 0 + _new_data_callback = None + def __init__(self, addr): self.address = addr self.bt_thread = threading.Thread(target=self.connect_and_scrape) @@ -240,6 +242,9 @@ def decode(self): if self.waiting_for_response == "device_info": self.waiting_for_response = "" + def set_callback(self, callback): + self._new_data_callback = callback + def assemble_frame(self, data: bytearray): if len(self.frame_buffer) > MAX_RESPONSE_SIZE: info("data dropped because it alone was longer than max frame length") @@ -261,6 +266,8 @@ def assemble_frame(self, data: bytearray): debug("great success! frame complete and sane, lets decode") self.decode() self.frame_buffer = [] + if self._new_data_callback is not None: + self._new_data_callback() def ncallback(self, sender: int, data: bytearray): debug(f"------> NEW PACKAGE!laenge: {len(data)}") diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 4bca9b35..06ad3068 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -152,7 +152,8 @@ def get_port() -> str: sys.exit(1) # Poll the battery at INTERVAL and run the main loop - gobject.timeout_add(battery.poll_interval, lambda: poll_battery(mainloop)) + if not battery.use_callback(lambda: poll_battery(mainloop)): + gobject.timeout_add(battery.poll_interval, lambda: poll_battery(mainloop)) try: mainloop.run() except KeyboardInterrupt: From ea8fd45d5519bdebc12b4f2a6de142dc653b65c2 Mon Sep 17 00:00:00 2001 From: Stefan Seidel Date: Mon, 5 Jun 2023 06:14:30 +0000 Subject: [PATCH 004/114] fix comments to reflect new logic --- etc/dbus-serialbattery/dbus-serialbattery.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 06ad3068..2ee7b780 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -151,9 +151,12 @@ def get_port() -> str: logger.error("ERROR >>> Problem with battery set up at " + port) sys.exit(1) - # Poll the battery at INTERVAL and run the main loop + # try using active callback on this battery if not battery.use_callback(lambda: poll_battery(mainloop)): + # if not possible, poll the battery every poll_interval milliseconds gobject.timeout_add(battery.poll_interval, lambda: poll_battery(mainloop)) + + # Run the main loop try: mainloop.run() except KeyboardInterrupt: From aec74718e22ecf74a79c5e2df59dae71f900f9c6 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 5 Jun 2023 08:40:17 +0200 Subject: [PATCH 005/114] update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c14b0b..eb5ae2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## v1.0.0 +## v1.0.x +* Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel +* Added: Implement callback function for update by @seidler2547 + +## v1.0.20230531 ### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` gets lost. The changes in the `config.ini` will persists future updates. From 1d2e47d1ab8fe28e4b62de957798df77adc128ff Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Mon, 5 Jun 2023 20:28:23 +0000 Subject: [PATCH 006/114] set soc=100% when charge mode changes to float, apply exponential smoothing on current readout --- etc/dbus-serialbattery/bms/daly.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 87510c48..af72d10f 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -27,6 +27,7 @@ def __init__(self, port, baud, address): self.trigger_force_disable_discharge = None self.trigger_force_disable_charge = None self.cells_volts_data_lastreadbad = False + self.last_charge_mode = self.charge_mode # command bytes [StartFlag=A5][Address=40][Command=94][DataLength=8][8x zero bytes][checksum] command_base = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x81" @@ -174,6 +175,8 @@ def refresh_data(self): self.write_charge_discharge_mos(ser) + self.update_soc(ser) + except OSError: logger.warning("Couldn't open serial port") @@ -181,6 +184,16 @@ def refresh_data(self): logger.info("refresh_data: result: " + str(result)) return result + def update_soc(self, ser): + if self.last_charge_mode is not None and self.charge_mode is not None: + if not self.last_charge_mode.startswith( + "Float" + ) and self.charge_mode.startswith("Float"): + # we just entered float mode, so the battery must be full + self.soc_to_set = 100 + self.write_soc_and_datetime(ser) + self.last_charge_mode = self.charge_mode + def read_status_data(self, ser): status_data = self.request_data(ser, self.command_status) # check if connection success @@ -229,7 +242,10 @@ def read_soc_data(self, ser): ) if crntMinValid < current < crntMaxValid: self.voltage = voltage / 10 - self.current = current + # apply exponential smoothing on the flickering current measurement + self.current = (0.1 * current) + ( + 0.9 * (0 if self.current is None else self.current) + ) self.soc = soc / 10 return True From 0d9a76fb8fd86d6a1c4ffea4e1527f766d9505c2 Mon Sep 17 00:00:00 2001 From: seidler2547 Date: Wed, 7 Jun 2023 20:01:20 +0000 Subject: [PATCH 007/114] remove scan for devices the scan for devices and check if the BMS to test is present doesn't add value if the device is not within range (or the MAC is wrong), then the subsequent start_scraping call will either fail or fail to produce usable data --- etc/dbus-serialbattery/bms/jkbms_ble.py | 53 +------------------------ 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index 276103c4..0056c902 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -28,58 +28,9 @@ def test_connection(self): # The result or call should be unique to this BMS. Battery name or version, etc. # Return True if success, False for failure - # check if device with given mac is found, otherwise abort - logger.info("Test of Jkbms_Ble at " + self.jk.address) - try: - loop = asyncio.get_event_loop() - t = loop.create_task(BleakScanner.discover()) - devices = loop.run_until_complete(t) - except BleakError as err: - logger.error(str(err)) - return False - except Exception as err: - logger.error(f"Unexpected {err=}, {type(err)=}") - return False - - found = False - for d in devices: - if d.address == self.jk.address: - found = True - if not found: - logger.error("No Jkbms_Ble found at " + self.jk.address) - return False - - """ - # before indipended service, has to be checked - - logger.info("test of jkbmsble") - 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 + + # start scraping self.jk.start_scraping() tries = 1 From 2f354486a259517a9d7461e932082087615802f3 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 8 Jun 2023 09:23:24 +0200 Subject: [PATCH 008/114] JKBMS_BLE driver fixes --- CHANGELOG.md | 2 + etc/dbus-serialbattery/bms/jkbms.py | 1 + etc/dbus-serialbattery/bms/jkbms_ble.py | 83 +++++++++++++++---------- etc/dbus-serialbattery/bms/jkbms_brn.py | 59 +++++++++--------- etc/dbus-serialbattery/utils.py | 2 +- 5 files changed, 83 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb5ae2cb..fb6bd551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## v1.0.x * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 +* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Changed: Fixed typo in `config.ini` sample by @hoschult ## v1.0.20230531 diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index 0aca8876..5fc87901 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -126,6 +126,7 @@ def read_status_data(self): unpack_from(">H", self.get_data(status_data, b"\x99", offset, 2))[0] ) + # the JKBMS resets to 95% SoC, if all cell voltages are above or equal to 3.500 V offset = cellbyte_count + 18 self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0] diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index 5f5911ce..d513f3cb 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -2,12 +2,13 @@ from battery import Battery, Cell from typing import Callable from utils import logger +from time import sleep, time from bms.jkbms_brn import Jkbms_Brn -from bleak import BleakScanner, BleakError -import asyncio -import time import os +# from bleak import BleakScanner, BleakError +# import asyncio + class Jkbms_Ble(Battery): BATTERYTYPE = "Jkbms_Ble" @@ -28,34 +29,46 @@ def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. # The result or call should be unique to this BMS. Battery name or version, etc. # Return True if success, False for failure + result = False + logger.info("Test of Jkbms_Ble at " + self.address) + try: + if self.address and self.address != "": + result = True + + if result: + # start scraping + self.jk.start_scraping() + tries = 1 - logger.info("Test of Jkbms_Ble at " + self.jk.address) - - # start scraping - self.jk.start_scraping() - tries = 1 + while self.jk.get_status() is None and tries < 20: + sleep(0.5) + tries += 1 - while self.jk.get_status() is None and tries < 20: - time.sleep(0.5) - tries += 1 + # load initial data, from here on get_status has valid values to be served to the dbus + status = self.jk.get_status() - # 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 status is None: + self.jk.stop_scraping() + result = False - if not status["device_info"]["vendor_id"].startswith(("JK-", "JK_")): - self.jk.stop_scraping() - return False + if result and not status["device_info"]["vendor_id"].startswith( + ("JK-", "JK_") + ): + self.jk.stop_scraping() + result = False - logger.info("Jkbms_Ble found!") + # get first data to show in startup log + if result: + self.get_settings() + self.refresh_data() + if not result: + logger.error("No BMS found at " + self.address) - # get first data to show in startup log - self.get_settings() - self.refresh_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False - return True + return result def get_settings(self): # After successful connection get_settings will be call to set up the battery. @@ -107,16 +120,17 @@ def refresh_data(self): st = self.jk.get_status() if st is None: return False - if time.time() - st["last_update"] > 30: - # if data not updated for more than 30s, sth is wrong, then fail - logger.info("Jkbms_Ble: Bluetooth died") - # if the thread is still alive but data too old there is sth + if time() - st["last_update"] >= 60: + # if data not updated for more than 60s, something is wrong, then fail + logger.info("Jkbms_Ble: Bluetooth died. Got no fresh data since 60s.") + + # if the thread is still alive but data too old there is something # wrong with the bt-connection; restart whole stack if not self.resetting: self.reset_bluetooth() self.jk.start_scraping() - time.sleep(2) + sleep(2) return False else: @@ -193,20 +207,21 @@ def refresh_data(self): return True def reset_bluetooth(self): - logger.info("Reset of Bluetooth triggered") + logger.info("Reset of system Bluetooth daemon triggered") self.resetting = True - # if self.jk.is_running(): - # self.jk.stop_scraping() + if self.jk.is_running(): + self.jk.stop_scraping() + logger.info("Scraping ended, issuing sys-commands") # process kill is needed, since the service/bluetooth driver is probably freezed os.system('pkill -f "bluetoothd"') # stop will not work, if service/bluetooth driver is stuck # os.system("/etc/init.d/bluetooth stop") - time.sleep(2) + 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") + logger.info("System Bluetooth daemon should have been restarted") def get_balancing(self): return 1 if self.balancing else 0 diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 82f55eae..01f79a78 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -1,9 +1,8 @@ -import asyncio +from struct import unpack_from, calcsize from bleak import BleakScanner, BleakClient -import time -from logging import info, debug +from time import sleep, time +import asyncio import logging -from struct import unpack_from, calcsize import threading logging.basicConfig(level=logging.INFO) @@ -191,19 +190,19 @@ def decode_cellinfo_jk02(self): for t in TRANSLATE_CELL_INFO: self.translate(fb, t, self.bms_status, f32s=has32s) self.decode_warnings(fb) - debug(self.bms_status) + logging.debug(self.bms_status) def decode_settings_jk02(self): fb = self.frame_buffer for t in TRANSLATE_SETTINGS: self.translate(fb, t, self.bms_status) - debug(self.bms_status) + logging.debug(self.bms_status) def decode(self): # check what kind of info the frame contains info_type = self.frame_buffer[4] if info_type == 0x01: - info("Processing frame with settings info") + logging.info("Processing frame with settings info") if protocol_version == PROTOCOL_VERSION_JK02: self.decode_settings_jk02() # adapt translation table for cell array lengths @@ -211,18 +210,18 @@ def decode(self): 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() + self.bms_status["last_update"] = time() elif info_type == 0x02: if ( CELL_INFO_REFRESH_S == 0 - or time.time() - self.last_cell_info > CELL_INFO_REFRESH_S + or time() - self.last_cell_info > CELL_INFO_REFRESH_S ): - self.last_cell_info = time.time() - info("processing frame with battery cell info") + self.last_cell_info = time() + logging.info("processing frame with battery cell info") if protocol_version == PROTOCOL_VERSION_JK02: self.decode_cellinfo_jk02() - self.bms_status["last_update"] = time.time() + self.bms_status["last_update"] = time() # power is calculated from voltage x current as # register 122 contains unsigned power-value self.bms_status["cell_info"]["power"] = ( @@ -233,10 +232,10 @@ def decode(self): self.waiting_for_response = "" elif info_type == 0x03: - info("processing frame with device info") + logging.info("processing frame with device info") if protocol_version == PROTOCOL_VERSION_JK02: self.decode_device_info_jk02() - self.bms_status["last_update"] = time.time() + self.bms_status["last_update"] = time() else: return if self.waiting_for_response == "device_info": @@ -247,7 +246,9 @@ def set_callback(self, callback): def assemble_frame(self, data: bytearray): if len(self.frame_buffer) > MAX_RESPONSE_SIZE: - info("data dropped because it alone was longer than max frame length") + logging.info( + "data dropped because it alone was longer than max frame length" + ) self.frame_buffer = [] if data[0] == 0x55 and data[1] == 0xAA and data[2] == 0xEB and data[3] == 0x90: @@ -261,16 +262,16 @@ def assemble_frame(self, data: bytearray): # actual frame-lentgh, so crc up to 299 ccrc = self.crc(self.frame_buffer, 300 - 1) rcrc = self.frame_buffer[300 - 1] - debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}") + logging.debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}") if ccrc == rcrc: - debug("great success! frame complete and sane, lets decode") + logging.debug("great success! frame complete and sane, lets decode") self.decode() self.frame_buffer = [] if self._new_data_callback is not None: self._new_data_callback() def ncallback(self, sender: int, data: bytearray): - debug(f"------> NEW PACKAGE!laenge: {len(data)}") + logging.debug(f"------> NEW PACKAGE!laenge: {len(data)}") self.assemble_frame(data) def crc(self, arr: bytearray, length: int) -> int: @@ -303,13 +304,13 @@ async def write_register( frame[17] = 0x00 frame[18] = 0x00 frame[19] = self.crc(frame, len(frame) - 1) - debug("Write register: ", frame) + logging.debug("Write register: ", frame) await bleakC.write_gatt_char(CHAR_HANDLE, frame, False) async def request_bt(self, rtype: str, client): - timeout = time.time() + timeout = time() - while self.waiting_for_response != "" and time.time() - timeout < 10: + while self.waiting_for_response != "" and time() - timeout < 10: await asyncio.sleep(1) print(self.waiting_for_response) @@ -351,18 +352,18 @@ async def asy_connect_and_scrape(self): await self.request_bt("cell_info", client) # await self.enable_charging(client) - # last_dev_info = time.time() + # last_dev_info = time() while client.is_connected and self.run and self.main_thread.is_alive(): await asyncio.sleep(0.01) except Exception as e: - info("error while connecting to bt: " + str(e)) + logging.info("error while connecting to bt: " + str(e)) self.run = False finally: if client.is_connected: try: await client.disconnect() except Exception as e: - info("error while disconnecting: " + str(e)) + logging.info("error while disconnecting: " + str(e)) print("Exiting bt-loop") @@ -371,7 +372,7 @@ def start_scraping(self): if self.is_running(): return self.bt_thread.start() - info( + logging.info( "scraping thread started -> main thread id: " + str(self.main_thread.ident) + " scraping thread: " @@ -380,10 +381,10 @@ def start_scraping(self): def stop_scraping(self): self.run = False - stop = time.time() + stop = time() while self.is_running(): - time.sleep(0.1) - if time.time() - stop > 10: + sleep(0.1) + if time() - stop > 10: return False return True @@ -407,5 +408,5 @@ async def enable_charging(self, c): jk.start_scraping() while True: print(jk.get_status()) - time.sleep(5) + sleep(5) """ diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 1b04323a..f3553d61 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # if not specified: baud = 9600 # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230526dev" +DRIVER_VERSION = "1.0.20230608dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 795e523e9d014134431f7a159c3ea8dffcbba791 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 8 Jun 2023 22:26:00 +0200 Subject: [PATCH 009/114] added Bluetooth signal strenght, increased debug --- CHANGELOG.md | 5 +- etc/dbus-serialbattery/bms/jkbms_ble.py | 18 +++++-- etc/dbus-serialbattery/reinstall-local.sh | 59 +++++++++++++++++++---- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb6bd551..4762fb5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog ## v1.0.x +* Added: Bluetooth: Show signal strenght of BMS in log by @mr-manuel * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 -* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult +* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel ## v1.0.20230531 diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index d513f3cb..91e2075a 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -25,6 +25,9 @@ def __init__(self, port, baud, address): def connection_name(self) -> str: return "BLE " + self.address + def custom_name(self) -> str: + return "SerialBattery(" + self.type + ") " + self.address[-5:] + def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. # The result or call should be unique to this BMS. Battery name or version, etc. @@ -121,13 +124,22 @@ def refresh_data(self): if st is None: return False - if time() - st["last_update"] >= 60: + last_update = int(time() - st["last_update"]) + if last_update >= 15 and last_update % 15 == 0: # if data not updated for more than 60s, something is wrong, then fail - logger.info("Jkbms_Ble: Bluetooth died. Got no fresh data since 60s.") + logger.info( + f"Jkbms_Ble: Bluetooth died. Got no fresh data since {last_update}s." + ) + bluetoothctl_info = os.popen( + "bluetoothctl info " + + self.address + + ' | grep -i -E "device|name|alias|pair|trusted|blocked|connected|rssi|power"' + ) + logger.info(bluetoothctl_info) # if the thread is still alive but data too old there is something # wrong with the bt-connection; restart whole stack - if not self.resetting: + if not self.resetting and last_update >= 120: self.reset_bluetooth() self.jk.start_scraping() sleep(2) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index d153a7e4..a3eea2b2 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -152,21 +152,41 @@ pkill -f "blebattery" if [ "$length" -gt 0 ]; then + echo echo "Found $length Bluetooth BMS in the config file!" - echo "" + echo # install required packages # TO DO: Check first if packages are already installed - echo "Installing required packages..." + echo "Installing required packages to use Bluetooth connection..." + opkg update opkg install python3-misc python3-pip pip3 install bleak - # setup cronjob to restart Bluetooth - grep -qxF "5 0,12 * * * /etc/init.d/bluetooth restart" /var/spool/cron/root || echo "5 0,12 * * * /etc/init.d/bluetooth restart" >> /var/spool/cron/root + echo "done." + echo # function to install ble battery install_blebattery_service() { + if [ -z "$1" ]; then + echo "ERROR: BMS unique number is empty. Aborting installation." + echo + exit 1 + fi + if [ -z "$2" ]; then + echo "ERROR: BMS type for battery $1 is empty. Aborting installation." + echo + exit 1 + fi + if [ -z "$3" ]; then + echo "ERROR: BMS MAC address for battery $1 with BMS type $2 is empty. Aborting installation." + echo + exit 1 + fi + + echo "Installing \"$2\" with MAC address \"$3\" as dbus-blebattery.$1" + mkdir -p "/service/dbus-blebattery.$1/log" { echo "#!/bin/sh" @@ -177,24 +197,42 @@ if [ "$length" -gt 0 ]; then { echo "#!/bin/sh" echo "exec 2>&1" + echo "echo" + echo "echo \"INFO:Bluetooth details\"" + # close all open connections, else the driver can't connect echo "bluetoothctl disconnect $3" + # enable bluetoothctl scan in background to display signal strength (RSSI), else it's missing + echo "bluetoothctl scan on | grep \"$3\" | grep \"RSSI\" &" + # with multiple Bluetooth BMS one scan for all would be enough. Check if that can be changed globally, maybe with a cronjob after reboot + # echo "bluetoothctl scan on > /dev/null &" + # wait 5 seconds to finish the scan + echo "sleep 5" + # display some Bluetooth device details + echo "bluetoothctl info $3 | grep -E \"Device|Alias|Pair|Trusted|Blocked|Connected|RSSI|Power\"" + echo "echo" echo "python /opt/victronenergy/dbus-serialbattery/dbus-serialbattery.py $2 $3" + echo "pkill -f \"bluetoothctl scan on\"" } > "/service/dbus-blebattery.$1/run" chmod 755 "/service/dbus-blebattery.$1/run" } - echo "Packages installed." - echo "" - + # Example # install_blebattery_service 0 Jkbms_Ble C8:47:8C:00:00:00 # install_blebattery_service 1 Jkbms_Ble C8:47:8C:00:00:11 for (( i=0; i> /var/spool/cron/root + else # remove cronjob @@ -202,6 +240,7 @@ else echo "No Bluetooth battery configuration found in \"/data/etc/dbus-serialbattery/config.ini\"." echo "You can ignore this, if you are using only a serial connection." + echo fi ### BLUETOOTH PART | END ### @@ -224,12 +263,12 @@ pkill -f "python .*/$DRIVERNAME.py" # restart bluetooth service, if Bluetooth BMS configured if [ "$length" -gt 0 ]; then /etc/init.d/bluetooth restart + echo fi # install notes echo -echo echo "SERIAL battery connection: The installation is complete. You don't have to do anything more." echo echo "BLUETOOTH battery connection: There are a few more steps to complete installation." From 6388a5d45ed4f9f88db206e56fe014fa0c13fa0f Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 12:59:21 +0200 Subject: [PATCH 010/114] Optimized reinstallation procedure - Changed: Optimized restart sequence for the bluetooth installation - Changed: Run serial part first and then bluetooth part. This allows the serial driver to get operative faster - Removed: $DRIVERNAME variable for clearer paths - Removed: Bluetooth system driver restart, since the devices get disconnected by the service before starting the dbus-serialbatterydriver --- etc/dbus-serialbattery/reinstall-local.sh | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index a3eea2b2..39bcbe88 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -3,8 +3,6 @@ # remove comment for easier troubleshooting #set -x -DRIVERNAME=dbus-serialbattery - # check if minimum required Venus OS is installed | start versionRequired="v2.90" @@ -66,15 +64,15 @@ fi bash /opt/victronenergy/swupdate-scripts/remount-rw.sh # install -rm -rf /opt/victronenergy/service/$DRIVERNAME -rm -rf /opt/victronenergy/service-templates/$DRIVERNAME -rm -rf /opt/victronenergy/$DRIVERNAME -mkdir /opt/victronenergy/$DRIVERNAME -mkdir /opt/victronenergy/$DRIVERNAME/bms -cp -f /data/etc/$DRIVERNAME/* /opt/victronenergy/$DRIVERNAME &>/dev/null -cp -f /data/etc/$DRIVERNAME/bms/* /opt/victronenergy/$DRIVERNAME/bms &>/dev/null -cp -rf /data/etc/$DRIVERNAME/service /opt/victronenergy/service-templates/$DRIVERNAME -bash /data/etc/$DRIVERNAME/install-qml.sh +rm -rf /opt/victronenergy/service/dbus-serialbattery +rm -rf /opt/victronenergy/service-templates/dbus-serialbattery +rm -rf /opt/victronenergy/dbus-serialbattery +mkdir /opt/victronenergy/dbus-serialbattery +mkdir /opt/victronenergy/dbus-serialbattery/bms +cp -f /data/etc/dbus-serialbattery/* /opt/victronenergy/dbus-serialbattery &>/dev/null +cp -f /data/etc/dbus-serialbattery/bms/* /opt/victronenergy/dbus-serialbattery/bms &>/dev/null +cp -rf /data/etc/dbus-serialbattery/service /opt/victronenergy/service-templates/dbus-serialbattery +bash /data/etc/dbus-serialbattery/install-qml.sh # check if serial-starter.d was deleted serialstarter_path="/data/conf/serial-starter.d" @@ -105,10 +103,10 @@ if [ ! -f "$filename" ]; then echo "#!/bin/bash" > "$filename" chmod 755 "$filename" fi -grep -qxF "bash /data/etc/$DRIVERNAME/reinstall-local.sh" $filename || echo "bash /data/etc/$DRIVERNAME/reinstall-local.sh" >> $filename +grep -qxF "bash /data/etc/dbus-serialbattery/reinstall-local.sh" $filename || echo "bash /data/etc/dbus-serialbattery/reinstall-local.sh" >> $filename # add empty config.ini, if it does not exist to make it easier for users to add custom settings -filename="/data/etc/$DRIVERNAME/config.ini" +filename="/data/etc/dbus-serialbattery/config.ini" if [ ! -f "$filename" ]; then { echo "[DEFAULT]" @@ -124,6 +122,9 @@ if [ ! -f "$filename" ]; then } > $filename fi +# kill driver, if running. It gets restarted by the service daemon +pkill -f "python .*/dbus-serialbattery.py$" + ### BLUETOOTH PART | START ### @@ -144,11 +145,17 @@ IFS="," read -r -a bms_array <<< "$bluetooth_bms_clean" length=${#bms_array[@]} # echo $length +# stop all dbus-blebattery services +svc -u /service/dbus-blebattery.* + # always remove existing blebattery services to cleanup rm -rf /service/dbus-blebattery.* -# kill all blebattery processes -pkill -f "blebattery" +# kill all blebattery processes that remain +pkill -f "supervise dbus-blebattery.*" +pkill -f "multilog .* /var/log/dbus-blebattery.*" +pkill -f "python .*/dbus-serialbattery.py .*_Ble" + if [ "$length" -gt 0 ]; then @@ -201,10 +208,12 @@ if [ "$length" -gt 0 ]; then echo "echo \"INFO:Bluetooth details\"" # close all open connections, else the driver can't connect echo "bluetoothctl disconnect $3" + # enable bluetoothctl scan in background to display signal strength (RSSI), else it's missing echo "bluetoothctl scan on | grep \"$3\" | grep \"RSSI\" &" - # with multiple Bluetooth BMS one scan for all would be enough. Check if that can be changed globally, maybe with a cronjob after reboot + # with multiple Bluetooth BMS one scan for all should be enough. Check if that can be changed globally, maybe with a cronjob after reboot? # echo "bluetoothctl scan on > /dev/null &" + # wait 5 seconds to finish the scan echo "sleep 5" # display some Bluetooth device details @@ -257,15 +266,6 @@ sed -i "/^sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local ### needed for upgrading from older versions | end ### -# kill driver, if running. It gets restarted by the service daemon -pkill -f "python .*/$DRIVERNAME.py" - -# restart bluetooth service, if Bluetooth BMS configured -if [ "$length" -gt 0 ]; then - /etc/init.d/bluetooth restart - echo -fi - # install notes echo From 4fcbee0204cd9dc1075b3f2f15aee7f66426d050 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 13:08:50 +0200 Subject: [PATCH 011/114] Improved Jkbms_Ble error handling --- CHANGELOG.md | 2 + etc/dbus-serialbattery/bms/jkbms_ble.py | 19 +++++++--- etc/dbus-serialbattery/bms/jkbms_brn.py | 49 ++++++++++++++++--------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4762fb5f..240a30ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult +* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel + ## v1.0.20230531 ### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` gets lost. The changes in the `config.ini` will persists future updates. diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index 91e2075a..d687e42a 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -126,21 +126,26 @@ def refresh_data(self): last_update = int(time() - st["last_update"]) if last_update >= 15 and last_update % 15 == 0: - # if data not updated for more than 60s, something is wrong, then fail logger.info( - f"Jkbms_Ble: Bluetooth died. Got no fresh data since {last_update}s." + f"Jkbms_Ble: Bluetooth connection interrupted. Got no fresh data since {last_update}s." ) + # show Bluetooth signal strenght (RSSI) bluetoothctl_info = os.popen( "bluetoothctl info " + self.address + ' | grep -i -E "device|name|alias|pair|trusted|blocked|connected|rssi|power"' ) - logger.info(bluetoothctl_info) + logger.info(bluetoothctl_info.read()) + bluetoothctl_info.close() # if the thread is still alive but data too old there is something # wrong with the bt-connection; restart whole stack - if not self.resetting and last_update >= 120: + if not self.resetting and last_update >= 60: + logger.error( + "Jkbms_Ble: Bluetooth died. Restarting Bluetooth system driver." + ) self.reset_bluetooth() + sleep(2) self.jk.start_scraping() sleep(2) @@ -222,9 +227,11 @@ def reset_bluetooth(self): logger.info("Reset of system Bluetooth daemon triggered") self.resetting = True if self.jk.is_running(): - self.jk.stop_scraping() + if self.jk.stop_scraping(): + logger.info("Scraping stopped, issuing sys-commands") + else: + logger.warning("Scraping was unable to stop, issuing sys-commands") - logger.info("Scraping ended, issuing sys-commands") # process kill is needed, since the service/bluetooth driver is probably freezed os.system('pkill -f "bluetoothd"') # stop will not work, if service/bluetooth driver is stuck diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 01f79a78..be5e20ab 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -99,7 +99,7 @@ def __init__(self, addr): async def scanForDevices(self): devices = await BleakScanner.discover() for d in devices: - print(d) + logging.debug(d) # iterative implementation maybe later due to referencing def translate(self, fb, translation, o, f32s=False, i=0): @@ -271,7 +271,7 @@ def assemble_frame(self, data: bytearray): self._new_data_callback() def ncallback(self, sender: int, data: bytearray): - logging.debug(f"------> NEW PACKAGE!laenge: {len(data)}") + logging.debug(f"--> NEW PACKAGE! lenght: {len(data)}") self.assemble_frame(data) def crc(self, arr: bytearray, length: int) -> int: @@ -312,7 +312,7 @@ async def request_bt(self, rtype: str, client): while self.waiting_for_response != "" and time() - timeout < 10: await asyncio.sleep(1) - print(self.waiting_for_response) + logging.debug(self.waiting_for_response) if rtype == "cell_info": cmd = COMMAND_CELL_INFO @@ -334,14 +334,18 @@ def get_status(self): def connect_and_scrape(self): asyncio.run(self.asy_connect_and_scrape()) + # self.bt_thread async def asy_connect_and_scrape(self): - print("connect and scrape on address: " + self.address) + logging.debug( + "--> asy_connect_and_scrape(): Connect and scrape on address: " + + self.address + ) self.run = True while self.run and self.main_thread.is_alive(): # autoreconnect client = BleakClient(self.address) - print("btloop") + logging.debug("--> asy_connect_and_scrape(): btloop") try: - print("reconnect") + logging.debug("--> asy_connect_and_scrape(): reconnect") await client.connect() self.bms_status["model_nbr"] = ( await client.read_gatt_char(MODEL_NBR_UUID) @@ -355,21 +359,27 @@ async def asy_connect_and_scrape(self): # last_dev_info = time() while client.is_connected and self.run and self.main_thread.is_alive(): await asyncio.sleep(0.01) - except Exception as e: - logging.info("error while connecting to bt: " + str(e)) + except Exception as err: self.run = False + logging.info( + f"--> asy_connect_and_scrape(): error while connecting to bt: {err}" + ) finally: + self.run = False if client.is_connected: try: await client.disconnect() - except Exception as e: - logging.info("error while disconnecting: " + str(e)) + except Exception as err: + logging.info( + f"--> asy_connect_and_scrape(): error while disconnecting: {err}" + ) - print("Exiting bt-loop") + logging.info("--> asy_connect_and_scrape(): Exit") def start_scraping(self): self.main_thread = threading.current_thread() if self.is_running(): + logging.info("screaping thread already running") return self.bt_thread.start() logging.info( @@ -402,11 +412,14 @@ async def enable_charging(self, c): await self.write_register(0x40, b"\x01\x00\x00\x00", 4, c) -""" if __name__ == "__main__": - jk = Jkbms_Brn("C8:47:8C:00:00:00") - jk.start_scraping() - while True: - print(jk.get_status()) - sleep(5) -""" + import sys + + jk = Jkbms_Brn(sys.argv[1]) + if not jk.test_connection(): + logging.error(">>> ERROR: Unable to connect") + else: + jk.start_scraping() + while True: + logging.debug(jk.get_status()) + sleep(5) From 68bd0a5a74397bee911c0d3f0fb38f0bdbfc5019 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 13:22:10 +0200 Subject: [PATCH 012/114] optimized disable procedure --- etc/dbus-serialbattery/disable.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/disable.sh b/etc/dbus-serialbattery/disable.sh index f1902881..bd0c5d1c 100755 --- a/etc/dbus-serialbattery/disable.sh +++ b/etc/dbus-serialbattery/disable.sh @@ -16,8 +16,8 @@ rm -rf /service/dbus-serialbattery.* rm -rf /service/dbus-blebattery.* # kill driver, if running -pkill -f "python .*/dbus-serialbattery.py" -pkill -f "blebattery" +pkill -f "dbus-serialbattery" +pkill -f "dbus-blebattery" # remove install script from rc.local sed -i "/bash \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local From fed6ca7b928031b04de83b6784ce2f310370b340 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 18:09:35 +0200 Subject: [PATCH 013/114] small fixes --- etc/dbus-serialbattery/dbus-serialbattery.py | 2 +- etc/dbus-serialbattery/reinstall-local.sh | 21 +++++++++++--------- etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index b41588e2..29fd624e 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -114,7 +114,7 @@ def get_port() -> str: else: # just for MNB-SPI logger.info("No Port needed") - return "/dev/tty/USB9" + return "/dev/ttyUSB9" logger.info("dbus-serialbattery v" + str(utils.DRIVER_VERSION)) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 39bcbe88..d0982864 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -123,7 +123,7 @@ if [ ! -f "$filename" ]; then fi # kill driver, if running. It gets restarted by the service daemon -pkill -f "python .*/dbus-serialbattery.py$" +pkill -f "python .*/dbus-serialbattery.py /dev/tty.*" @@ -145,16 +145,18 @@ IFS="," read -r -a bms_array <<< "$bluetooth_bms_clean" length=${#bms_array[@]} # echo $length -# stop all dbus-blebattery services -svc -u /service/dbus-blebattery.* +# stop all dbus-blebattery services, if at least one exists +if [ -d "/service/dbus-blebattery.0" ]; then + svc -u /service/dbus-blebattery.* -# always remove existing blebattery services to cleanup -rm -rf /service/dbus-blebattery.* + # always remove existing blebattery services to cleanup + rm -rf /service/dbus-blebattery.* -# kill all blebattery processes that remain -pkill -f "supervise dbus-blebattery.*" -pkill -f "multilog .* /var/log/dbus-blebattery.*" -pkill -f "python .*/dbus-serialbattery.py .*_Ble" + # kill all blebattery processes that remain + pkill -f "supervise dbus-blebattery.*" + pkill -f "multilog .* /var/log/dbus-blebattery.*" + pkill -f "python .*/dbus-serialbattery.py .*_Ble" +fi if [ "$length" -gt 0 ]; then @@ -247,6 +249,7 @@ else # remove cronjob sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root + echo echo "No Bluetooth battery configuration found in \"/data/etc/dbus-serialbattery/config.ini\"." echo "You can ignore this, if you are using only a serial connection." echo diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index f3553d61..8ea766ba 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # if not specified: baud = 9600 # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230608dev" +DRIVER_VERSION = "1.0.20230610dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From fee962368cb2a223f03e02bc7a2329af57e50624 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 18:31:51 +0200 Subject: [PATCH 014/114] save custom name and make it restart persistant https://github.com/Louisvdw/dbus-serialbattery/issues/100 --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 153 ++++++++++++++++++- etc/dbus-serialbattery/config.default.ini | 6 + etc/dbus-serialbattery/dbus-serialbattery.py | 2 +- etc/dbus-serialbattery/dbushelper.py | 5 +- etc/dbus-serialbattery/utils.py | 18 ++- 6 files changed, 180 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 240a30ed..94b8e2ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Added: Save custom name and make it restart persistant by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 1ce3c5be..79ddce46 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -7,6 +7,8 @@ import math from time import time from abc import ABC, abstractmethod +import re +import sys class Protection(object): @@ -133,7 +135,16 @@ def connection_name(self) -> str: return "Serial " + self.port def custom_name(self) -> str: - return "SerialBattery(" + self.type + ")" + """ + Check if the custom name is present in the config file, else return default name + """ + if len(utils.CUSTOM_BATTERY_NAMES) > 0: + for name in utils.CUSTOM_BATTERY_NAMES: + tmp = name.split(":") + if tmp[0].strip() == self.port: + return tmp[1].strip() + else: + return "SerialBattery(" + self.type + ")" def product_name(self) -> str: return "SerialBattery(" + self.type + ")" @@ -999,6 +1010,146 @@ def log_settings(self) -> None: return + # save custom name to config file + def custom_name_callback(self, path, value): + try: + if path == "/CustomName": + file = open( + "/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "r" + ) + lines = file.readlines() + last = len(lines) + + # remove not allowed characters + value = value.replace(":", "").replace("=", "").replace(",", "").strip() + + # empty string to save new config file + config_file_new = "" + + # make sure we are in the [DEFAULT] section + current_line_in_default_section = False + default_section_checked = False + + # check if already exists + exists = False + + # count lines + i = 0 + # looping through the file + for line in lines: + # increment by one + i += 1 + + # stripping line break + line = line.strip() + + # check, if current line is after the [DEFAULT] section + if line == "[DEFAULT]": + current_line_in_default_section = True + + # check, if current line starts a new section + if line != "[DEFAULT]" and re.match(r"^\[.*\]", line): + # set default_section_checked to true, if it was already checked and a new section comes on + if current_line_in_default_section and not exists: + default_section_checked = True + current_line_in_default_section = False + + # check, if the current line is the last line + if i == last: + default_section_checked = True + + # insert or replace only in [DEFAULT] section + if current_line_in_default_section and re.match( + r"^CUSTOM_BATTERY_NAMES.*", line + ): + # set that the setting was found, else a new one is created + exists = True + + # remove setting name + line = re.sub( + "^CUSTOM_BATTERY_NAMES\s*=\s*", "", line # noqa: W605 + ) + + # change only the name of the current BMS + result = [] + bms_name_list = line.split(",") + for bms_name_pair in bms_name_list: + tmp = bms_name_pair.split(":") + if tmp[0] == self.port: + result.append(tmp[0] + ":" + value) + else: + result.append(bms_name_pair) + + new_line = "CUSTOM_BATTERY_NAMES = " + ",".join(result) + + else: + if default_section_checked and not exists: + exists = True + + # add before current line + if i != last: + new_line = ( + "CUSTOM_BATTERY_NAMES = " + + self.port + + ":" + + value + + "\n\n" + + line + ) + + # add at the end if last line + else: + new_line = ( + line + + "\n\n" + + "CUSTOM_BATTERY_NAMES = " + + self.port + + ":" + + value + ) + else: + new_line = line + # concatenate the new string and add an end-line break + config_file_new = config_file_new + new_line + "\n" + + # close the file + file.close() + # Open file in write mode + write_file = open( + "/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "w" + ) + # overwriting the old file contents with the new/replaced content + write_file.write(config_file_new) + # close the file + write_file.close() + + # logger.error("value (saved): " + str(value)) + + """ + # this removes all comments and tranfsorm the values to lowercase + utils.config.set( + "DEFAULT", + "CUSTOM_BATTERY_NAMES", + self.port + ":" + value, + ) + + # Writing our configuration file to 'example.ini' + with open( + "/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "w" + ) as configfile: + type(utils.config.write(configfile)) + """ + + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) + + return value + def reset_soc_callback(self, path, value): # callback for handling reset soc request return diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 559de8bd..35d01f4c 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -195,6 +195,12 @@ BMS_TYPE = ; Example: /dev/ttyUSB2, /dev/ttyUSB4 EXCLUDED_DEVICES = +; Enter custom battery names here or change it over the GUI +; Example: +; /dev/ttyUSB0:My first battery +; /dev/ttyUSB0:My first battery,/dev/ttyUSB1:My second battery +CUSTOM_BATTERY_NAMES = + ; Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = 1 diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 29fd624e..7794071d 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -58,7 +58,7 @@ if battery_type["bms"].__name__ == utils.BMS_TYPE or utils.BMS_TYPE == "" ] -print("") +logger.info("") logger.info("Starting dbus-serialbattery") diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 0cca3187..9edc57b1 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -123,7 +123,10 @@ def setup_vedbus(self): self._dbusservice.add_path("/HardwareVersion", self.battery.hardware_version) self._dbusservice.add_path("/Connected", 1) self._dbusservice.add_path( - "/CustomName", self.battery.custom_name(), writeable=True + "/CustomName", + self.battery.custom_name(), + writeable=True, + onchangecallback=self.battery.custom_name_callback, ) self._dbusservice.add_path( "/Serial", self.battery.unique_identifier, writeable=True diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 8ea766ba..ca64c12a 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -15,10 +15,13 @@ logger = logging.getLogger("SerialBattery") logger.setLevel(logging.INFO) +PATH_CONFIG_DEFAULT = "config.default.ini" +PATH_CONFIG_USER = "config.ini" + config = configparser.ConfigParser() path = Path(__file__).parents[0] -default_config_file_path = path.joinpath("config.default.ini").absolute().__str__() -custom_config_file_path = path.joinpath("config.ini").absolute().__str__() +default_config_file_path = path.joinpath(PATH_CONFIG_DEFAULT).absolute().__str__() +custom_config_file_path = path.joinpath(PATH_CONFIG_USER).absolute().__str__() config.read([default_config_file_path, custom_config_file_path]) @@ -275,10 +278,20 @@ def _get_list_from_config( # Ant, MNB, Sinowealth BMS_TYPE = config["DEFAULT"]["BMS_TYPE"] +# Exclute this serial devices from the driver startup +# Example: /dev/ttyUSB2, /dev/ttyUSB4 EXCLUDED_DEVICES = _get_list_from_config( "DEFAULT", "EXCLUDED_DEVICES", lambda v: str(v) ) +# Enter custom battery names here or change it over the GUI +# Example: +# /dev/ttyUSB0:My first battery +# /dev/ttyUSB0:My first battery, /dev/ttyUSB1:My second battery +CUSTOM_BATTERY_NAMES = _get_list_from_config( + "DEFAULT", "CUSTOM_BATTERY_NAMES", lambda v: str(v) +) + # Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"]) @@ -526,6 +539,7 @@ def read_serialport_data( locals_copy = locals().copy() +# Publish config variables to dbus def publish_config_variables(dbusservice): for variable, value in locals_copy.items(): if variable.startswith("__"): From a81bf055beded89f9fd3c15c2f24d6b9a42a0b90 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 19:14:10 +0200 Subject: [PATCH 015/114] changed unique identifier from string to function function can be overridden by BMS battery class --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 24 +++++++++++++------ .../bms/battery_template.py | 14 +++++++---- etc/dbus-serialbattery/bms/daly.py | 14 +++++++---- etc/dbus-serialbattery/bms/heltecmodbus.py | 11 +++++++-- etc/dbus-serialbattery/bms/jkbms.py | 11 +++++++-- etc/dbus-serialbattery/bms/jkbms_ble.py | 11 ++++++++- etc/dbus-serialbattery/dbushelper.py | 2 +- 8 files changed, 65 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b8e2ab..84bfb4bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## v1.0.x * Added: Bluetooth: Show signal strenght of BMS in log by @mr-manuel +* Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 79ddce46..a8c410a3 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -71,15 +71,12 @@ def __init__(self, port, baud, address): self.max_battery_discharge_current = None self.has_settings = 0 - self.init_values() - - # used to identify a BMS when multiple BMS are connected - planned for future use - self.unique_identifier = None - # fetched from the BMS from a field where the user can input a custom string # only if available self.custom_field = None + self.init_values() + def init_values(self): self.voltage = None self.current = None @@ -131,6 +128,20 @@ def test_connection(self) -> bool: # return false when failed, true if successful return False + def unique_identifier(self) -> str: + """ + Used to identify a BMS when multiple BMS are connected + If not provided by the BMS/driver then the hardware version and capacity is used, + since it can be changed by small amounts to make a battery unique. + On +/- 5 Ah you can identify 11 batteries + """ + return ( + "".join(filter(str.isalnum, self.hardware_version)) + + "_" + + str(self.capacity) + + "Ah" + ) + def connection_name(self) -> str: return "Serial " + self.port @@ -1005,8 +1016,7 @@ def log_settings(self) -> None: logger.info( f"> CCCM SOC: {str(utils.CCCM_SOC_ENABLE).ljust(5)} | DCCM SOC: {utils.DCCM_SOC_ENABLE}" ) - if self.unique_identifier is not None: - logger.info(f"Serial Number/Unique Identifier: {self.unique_identifier}") + logger.info(f"Serial Number/Unique Identifier: {self.unique_identifier()}") return diff --git a/etc/dbus-serialbattery/bms/battery_template.py b/etc/dbus-serialbattery/bms/battery_template.py index e32e424b..92c7fa49 100644 --- a/etc/dbus-serialbattery/bms/battery_template.py +++ b/etc/dbus-serialbattery/bms/battery_template.py @@ -36,6 +36,15 @@ def test_connection(self): return result + def unique_identifier(self) -> str: + """ + Used to identify a BMS when multiple BMS are connected + Provide a unique identifier from the BMS to identify a BMS, if multiple same BMS are connected + e.g. the serial number + If there is no such value, please remove this function + """ + return self.serialnumber + def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc @@ -53,11 +62,6 @@ def get_settings(self): self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count - # provide a unique identifier from the BMS to identify a BMS, if multiple same BMS are connected - # e.g. the serial number - # If there is no such value, please leave the line commented. In this case the capacity is used, - # since it can be changed by small amounts to make a battery unique. On +/- 5 Ah you can identify 11 batteries - # self.unique_identifier = str() return True def refresh_data(self): diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 87510c48..732af406 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -522,13 +522,17 @@ def read_battery_code(self, ser): " ", (battery_code.strip()), ) - self.unique_identifier = self.custom_field.replace(" ", "_") - else: - self.unique_identifier = ( - str(self.production) + "_" + str(int(self.capacity)) - ) return True + def unique_identifier(self) -> str: + """ + Used to identify a BMS when multiple BMS are connected + """ + if self.custom_field != "": + return self.custom_field.replace(" ", "_") + else: + return str(self.production) + "_" + str(int(self.capacity)) + def reset_soc_callback(self, path, value): if value is None: return False diff --git a/etc/dbus-serialbattery/bms/heltecmodbus.py b/etc/dbus-serialbattery/bms/heltecmodbus.py index e7f866a0..1ceaa43e 100644 --- a/etc/dbus-serialbattery/bms/heltecmodbus.py +++ b/etc/dbus-serialbattery/bms/heltecmodbus.py @@ -30,6 +30,7 @@ class HeltecModbus(Battery): def __init__(self, port, baud, address): super(HeltecModbus, self).__init__(port, baud, address) self.type = "Heltec_Smart" + self.unique_identifier_tmp = "" def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. @@ -174,7 +175,7 @@ def read_status_data(self): time.sleep(SLPTIME) serial1 = mbdev.read_registers(2, number_of_registers=4) - self.unique_identifier = "-".join( + self.unique_identifier_tmp = "-".join( "{:04x}".format(x) for x in serial1 ) time.sleep(SLPTIME) @@ -234,7 +235,7 @@ def read_status_data(self): logger.info(self.hardware_version) logger.info("Heltec-" + self.hwTypeName) logger.info(" Dev name: " + self.devName) - logger.info(" Serial: " + self.unique_identifier) + logger.info(" Serial: " + self.unique_identifier_tmp) logger.info(" Made on: " + self.production_date) logger.info(" Cell count: " + str(self.cell_count)) logger.info(" Cell type: " + self.cellType) @@ -245,6 +246,12 @@ def read_status_data(self): return True + def unique_identifier(self) -> str: + """ + Used to identify a BMS when multiple BMS are connected + """ + return self.unique_identifier_tmp + def read_soc_data(self): mbdev = mbdevs[self.address] diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index 5fc87901..8925d824 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -10,6 +10,7 @@ class Jkbms(Battery): def __init__(self, port, baud, address): super(Jkbms, self).__init__(port, baud, address) self.type = self.BATTERYTYPE + self.unique_identifier_tmp = "" BATTERYTYPE = "Jkbms" LENGTH_CHECK = 1 @@ -184,9 +185,9 @@ def read_status_data(self): )[0].decode() offset = cellbyte_count + 197 - self.unique_identifier = sub( + self.unique_identifier_tmp = sub( " +", - " ", + "_", ( unpack_from(">24s", self.get_data(status_data, b"\xBA", offset, 24))[0] .decode() @@ -209,6 +210,12 @@ def read_status_data(self): # logger.info(self.hardware_version) return True + def unique_identifier(self) -> str: + """ + Used to identify a BMS when multiple BMS are connected + """ + return self.unique_identifier_tmp + def to_fet_bits(self, byte_data): tmp = bin(byte_data)[2:].rjust(3, utils.zero_char) self.charge_fet = is_bit_set(tmp[2]) diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index d687e42a..6a2e3011 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -19,6 +19,7 @@ def __init__(self, port, baud, address): self.address = address self.type = self.BATTERYTYPE self.jk = Jkbms_Brn(address) + self.unique_identifier_tmp = "" logger.info("Init of Jkbms_Ble at " + address) @@ -91,7 +92,9 @@ def get_settings(self): tmp = self.jk.get_status()["device_info"]["manufacturing_date"] self.production = "20" + tmp if tmp and tmp != "" else None - self.unique_identifier = self.jk.get_status()["device_info"]["serial_number"] + self.unique_identifier_tmp = self.jk.get_status()["device_info"][ + "serial_number" + ] for c in range(self.cell_count): self.cells.append(Cell(False)) @@ -109,6 +112,12 @@ def get_settings(self): logger.info("BAT: " + self.hardware_version) return True + def unique_identifier(self) -> str: + """ + Used to identify a BMS when multiple BMS are connected + """ + return self.unique_identifier_tmp + def use_callback(self, callback: Callable) -> bool: self.jk.set_callback(callback) return callback is not None diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 9edc57b1..946eb4d6 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -129,7 +129,7 @@ def setup_vedbus(self): onchangecallback=self.battery.custom_name_callback, ) self._dbusservice.add_path( - "/Serial", self.battery.unique_identifier, writeable=True + "/Serial", self.battery.unique_identifier(), writeable=True ) self._dbusservice.add_path( "/DeviceName", self.battery.custom_field, writeable=True From 64f0248731d61716504c0506f7e219ac24ae6ead Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 10 Jun 2023 19:38:36 +0200 Subject: [PATCH 016/114] fix typo --- CHANGELOG.md | 2 +- etc/dbus-serialbattery/bms/jkbms_ble.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84bfb4bd..39fac70d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## v1.0.x -* Added: Bluetooth: Show signal strenght of BMS in log by @mr-manuel +* Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py index 6a2e3011..9029da28 100644 --- a/etc/dbus-serialbattery/bms/jkbms_ble.py +++ b/etc/dbus-serialbattery/bms/jkbms_ble.py @@ -138,7 +138,7 @@ def refresh_data(self): logger.info( f"Jkbms_Ble: Bluetooth connection interrupted. Got no fresh data since {last_update}s." ) - # show Bluetooth signal strenght (RSSI) + # show Bluetooth signal strength (RSSI) bluetoothctl_info = os.popen( "bluetoothctl info " + self.address From f72bbe5ed7a269d7be998c7541a68bd065e7f05e Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 14:26:12 +0200 Subject: [PATCH 017/114] fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 --- etc/dbus-serialbattery/bms/sinowealth.py | 36 +++++++++++--------- etc/dbus-serialbattery/dbus-serialbattery.py | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/etc/dbus-serialbattery/bms/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py index 7a9d9fdb..81512671 100755 --- a/etc/dbus-serialbattery/bms/sinowealth.py +++ b/etc/dbus-serialbattery/bms/sinowealth.py @@ -44,8 +44,8 @@ def test_connection(self): result = False try: result = self.read_status_data() - result = result and self.read_remaining_capacity() - result = result and self.read_pack_config_data() + result = result and self.get_settings() + result = result and self.refresh_data() except Exception as err: logger.error(f"Unexpected {err=}, {type(err)=}") result = False @@ -64,7 +64,7 @@ def get_settings(self): self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.hardware_version = "Daly/Sinowealth BMS " + str(self.cell_count) + " cells" - logger.info(self.hardware_version) + logger.debug(self.hardware_version) self.read_capacity() @@ -95,7 +95,7 @@ def read_status_data(self): # [1] - FAST_DSG MID_DSG SLOW_DSG DSGING CHGING DSGMOS CHGMOS self.discharge_fet = bool(status_data[1] >> 1 & int(1)) # DSGMOS self.charge_fet = bool(status_data[1] & int(1)) # CHGMOS - logger.info( + logger.debug( ">>> INFO: Discharge fet: %s, charge fet: %s", self.discharge_fet, self.charge_fet, @@ -145,7 +145,7 @@ def read_soc(self): # check if connection success if soc_data is False: return False - logger.info(">>> INFO: current SOC: %u", soc_data[1]) + logger.debug(">>> INFO: current SOC: %u", soc_data[1]) self.soc = soc_data[1] return True @@ -156,7 +156,7 @@ def read_cycle_count(self): if cycle_count is False: return False self.cycles = int(unpack_from(">H", cycle_count[:2])[0]) - logger.info(">>> INFO: current cycle count: %u", self.cycles) + logger.debug(">>> INFO: current cycle count: %u", self.cycles) return True def read_pack_voltage(self): @@ -165,7 +165,7 @@ def read_pack_voltage(self): return False pack_voltage = unpack_from(">H", pack_voltage_data[:-1]) pack_voltage = pack_voltage[0] / 1000 - logger.info(">>> INFO: current pack voltage: %f", pack_voltage) + logger.debug(">>> INFO: current pack voltage: %f", pack_voltage) self.voltage = pack_voltage return True @@ -175,7 +175,7 @@ def read_pack_current(self): return False current = unpack_from(">i", current_data[:-1]) current = current[0] / 1000 - logger.info(">>> INFO: current pack current: %f", current) + logger.debug(">>> INFO: current pack current: %f", current) self.current = current return True @@ -187,7 +187,9 @@ def read_remaining_capacity(self): return False remaining_capacity = unpack_from(">i", remaining_capacity_data[:-1]) self.capacity_remain = remaining_capacity[0] / 1000 - logger.info(">>> INFO: remaining battery capacity: %f Ah", self.capacity_remain) + logger.debug( + ">>> INFO: remaining battery capacity: %f Ah", self.capacity_remain + ) return True def read_capacity(self): @@ -195,7 +197,7 @@ def read_capacity(self): if capacity_data is False: return False capacity = unpack_from(">i", capacity_data[:-1]) - logger.info(">>> INFO: Battery capacity: %f Ah", capacity[0] / 1000) + logger.debug(">>> INFO: Battery capacity: %f Ah", capacity[0] / 1000) self.capacity = capacity[0] / 1000 return True @@ -210,12 +212,12 @@ def read_pack_config_data(self): if self.cell_count < 1 or self.cell_count > 32: logger.error(">>> ERROR: No valid cell count returnd: %u", self.cell_count) return False - logger.info(">>> INFO: Number of cells: %u", self.cell_count) + logger.debug(">>> INFO: Number of cells: %u", self.cell_count) temp_sens_mask = int(~(1 << 6)) self.temp_sensors = ( 1 if (pack_config_data[1] & temp_sens_mask) else 2 ) # one means two - logger.info(">>> INFO: Number of temperatur sensors: %u", self.temp_sensors) + logger.debug(">>> INFO: Number of temperatur sensors: %u", self.temp_sensors) return True def read_cell_data(self): @@ -235,7 +237,7 @@ def read_cell_voltage(self, cell_index): cell_voltage = unpack_from(">H", cell_data[:-1]) cell_voltage = cell_voltage[0] / 1000 - logger.info(">>> INFO: Cell %u voltage: %f V", cell_index, cell_voltage) + logger.debug(">>> INFO: Cell %u voltage: %f V", cell_index, cell_voltage) return cell_voltage def read_temperature_data(self): @@ -248,7 +250,7 @@ def read_temperature_data(self): temp_ext1 = unpack_from(">H", temp_ext1_data[:-1]) self.to_temp(1, kelvin_to_celsius(temp_ext1[0] / 10)) - logger.info(">>> INFO: BMS external temperature 1: %f C", self.temp1) + logger.debug(">>> INFO: BMS external temperature 1: %f C", self.temp1) if self.temp_sensors == 2: temp_ext2_data = self.read_serial_data_sinowealth(self.command_temp_ext2) @@ -257,7 +259,7 @@ def read_temperature_data(self): temp_ext2 = unpack_from(">H", temp_ext2_data[:-1]) self.to_temp(2, kelvin_to_celsius(temp_ext2[0] / 10)) - logger.info(">>> INFO: BMS external temperature 2: %f C", self.temp2) + logger.debug(">>> INFO: BMS external temperature 2: %f C", self.temp2) # Internal temperature 1 seems to give a logical value temp_int1_data = self.read_serial_data_sinowealth(self.command_temp_int1) @@ -265,7 +267,7 @@ def read_temperature_data(self): return False temp_int1 = unpack_from(">H", temp_int1_data[:-1]) - logger.info( + logger.debug( ">>> INFO: BMS internal temperature 1: %f C", kelvin_to_celsius(temp_int1[0] / 10), ) @@ -276,7 +278,7 @@ def read_temperature_data(self): return False temp_int2 = unpack_from(">H", temp_int2_data[:-1]) - logger.info( + logger.debug( ">>> INFO: BMS internal temperature 2: %f C", kelvin_to_celsius(temp_int2[0] / 10), ) diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 7794071d..704ef019 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -50,7 +50,7 @@ {"bms": Seplos, "baud": 19200}, # {"bms": Ant, "baud": 19200}, # {"bms": MNB, "baud": 9600}, - # {"bms": Sinowealth}, + # {"bms": Sinowealth, "baud": 9600}, ] expected_bms_types = [ battery_type From acf9d2be315b735b983113985fc9496ab6d21122 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 14:27:33 +0200 Subject: [PATCH 018/114] fix unique identifier function --- etc/dbus-serialbattery/battery.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index a8c410a3..6fb79618 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -135,12 +135,13 @@ def unique_identifier(self) -> str: since it can be changed by small amounts to make a battery unique. On +/- 5 Ah you can identify 11 batteries """ - return ( - "".join(filter(str.isalnum, self.hardware_version)) - + "_" - + str(self.capacity) - + "Ah" + string = ( + "".join(filter(str.isalnum, self.hardware_version)) + "_" + if self.hardware_version is not None and self.hardware_version != "" + else "" ) + string += str(self.capacity) + "Ah" + return string def connection_name(self) -> str: return "Serial " + self.port From 240c3733fbb7adf5d3e756a6db08aae70cae4e8b Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 14:33:50 +0200 Subject: [PATCH 019/114] enable BMS over config, if disabled by default Now you can also add more then one BMS for BMS_TYPE --- CHANGELOG.md | 3 +++ etc/dbus-serialbattery/bms/ant.py | 3 ++- etc/dbus-serialbattery/bms/mnb.py | 3 ++- etc/dbus-serialbattery/bms/sinowealth.py | 3 ++- etc/dbus-serialbattery/config.default.ini | 5 ++-- etc/dbus-serialbattery/dbus-serialbattery.py | 24 ++++++++++++++------ etc/dbus-serialbattery/utils.py | 7 ++---- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39fac70d..d26c7347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel +* Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel +* Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult +* Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel diff --git a/etc/dbus-serialbattery/bms/ant.py b/etc/dbus-serialbattery/bms/ant.py index 124f036f..dca9cc29 100644 --- a/etc/dbus-serialbattery/bms/ant.py +++ b/etc/dbus-serialbattery/bms/ant.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -# disable ANT BMS by default as it causes other issues but can be enabled manually +# ANT BMS is disabled by default as it causes issues with other devices +# can be enabled by specifying it in the BMS_TYPE setting in the "config.ini" # https://github.com/Louisvdw/dbus-serialbattery/issues/479 from battery import Battery diff --git a/etc/dbus-serialbattery/bms/mnb.py b/etc/dbus-serialbattery/bms/mnb.py index 84365866..a9d38853 100644 --- a/etc/dbus-serialbattery/bms/mnb.py +++ b/etc/dbus-serialbattery/bms/mnb.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -# disable MNB battery by default +# # MNB is disabled by default +# can be enabled by specifying it in the BMS_TYPE setting in the "config.ini" # https://github.com/Louisvdw/dbus-serialbattery/commit/65241cbff36feb861ff43dbbcfb2b495f14a01ce # remove duplicate MNB lines # https://github.com/Louisvdw/dbus-serialbattery/commit/23afec33c2fd87fd4d4c53516f0a25f290643c82 diff --git a/etc/dbus-serialbattery/bms/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py index 81512671..b4cd7a4b 100755 --- a/etc/dbus-serialbattery/bms/sinowealth.py +++ b/etc/dbus-serialbattery/bms/sinowealth.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -# disable Sinowealth by default as it causes other issues but can be enabled manually +# Sinowealth is disabled by default as it causes issues with other devices +# can be enabled by specifying it in the BMS_TYPE setting in the "config.ini" # https://github.com/Louisvdw/dbus-serialbattery/commit/7aab4c850a5c8d9c205efefc155fe62bb527da8e from battery import Battery, Cell diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 35d01f4c..1eb08303 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -183,11 +183,10 @@ TIME_TO_SOC_INC_FROM = False ; --------- Additional settings --------- -; Specify only one BMS type to load else leave empty to try to load all available +; Specify one or more BMS types to load else leave empty to try to load all available ; -- Available BMS: ; Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos -; -- Available BMS, but disabled by default: -; https://louisvdw.github.io/dbus-serialbattery/general/install#how-to-enable-a-disabled-bms +; -- Available BMS, but disabled by default (just enter one or more below and it will be enabled): ; Ant, MNB, Sinowealth BMS_TYPE = diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 704ef019..6b14d950 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -32,9 +32,13 @@ from bms.renogy import Renogy from bms.seplos import Seplos -# from bms.ant import Ant -# from bms.mnb import MNB -# from bms.sinowealth import Sinowealth +# enabled only if explicitly set in config under "BMS_TYPE" +if "Ant" in utils.BMS_TYPE: + from bms.ant import Ant +if "MNB" in utils.BMS_TYPE: + from bms.mnb import MNB +if "Sinowealth" in utils.BMS_TYPE: + from bms.sinowealth import Sinowealth supported_bms_types = [ {"bms": Daly, "baud": 9600, "address": b"\x40"}, @@ -48,14 +52,20 @@ {"bms": Renogy, "baud": 9600, "address": b"\x30"}, {"bms": Renogy, "baud": 9600, "address": b"\xF7"}, {"bms": Seplos, "baud": 19200}, - # {"bms": Ant, "baud": 19200}, - # {"bms": MNB, "baud": 9600}, - # {"bms": Sinowealth, "baud": 9600}, ] + +# enabled only if explicitly set in config under "BMS_TYPE" +if "Ant" in utils.BMS_TYPE: + supported_bms_types.append({"bms": Ant, "baud": 19200}) +if "MNB" in utils.BMS_TYPE: + supported_bms_types.append({"bms": MNB, "baud": 9600}) +if "Sinowealth" in utils.BMS_TYPE: + supported_bms_types.append({"bms": Sinowealth, "baud": 9600}) + expected_bms_types = [ battery_type for battery_type in supported_bms_types - if battery_type["bms"].__name__ == utils.BMS_TYPE or utils.BMS_TYPE == "" + if battery_type["bms"].__name__ in utils.BMS_TYPE or len(utils.BMS_TYPE) == 0 ] logger.info("") diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index ca64c12a..1a29237b 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,11 +37,8 @@ def _get_list_from_config( ) -# battery types -# if not specified: baud = 9600 - # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230610dev" +DRIVER_VERSION = "1.0.20230611dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -276,7 +273,7 @@ def _get_list_from_config( # -- Available BMS, but disabled by default: # https://louisvdw.github.io/dbus-serialbattery/general/install#how-to-enable-a-disabled-bms # Ant, MNB, Sinowealth -BMS_TYPE = config["DEFAULT"]["BMS_TYPE"] +BMS_TYPE = _get_list_from_config("DEFAULT", "BMS_TYPE", lambda v: str(v)) # Exclute this serial devices from the driver startup # Example: /dev/ttyUSB2, /dev/ttyUSB4 From 790fa043a08d09742e7409f55cd8ea6aaa017eee Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 16:21:00 +0200 Subject: [PATCH 020/114] show battery port in log --- etc/dbus-serialbattery/dbus-serialbattery.py | 31 ++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 6b14d950..c72dd76f 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -5,7 +5,6 @@ from time import sleep from dbus.mainloop.glib import DBusGMainLoop -# from threading import Thread ## removed with https://github.com/Louisvdw/dbus-serialbattery/pull/582 import sys if sys.version_info.major == 2: @@ -80,13 +79,25 @@ def poll_battery(loop): def get_battery(_port) -> Union[Battery, None]: # all the different batteries the driver support and need to test for # try to establish communications with the battery 3 times, else exit - count = 3 - while count > 0: + retry = 1 + retries = 3 + while retry <= retries: + logger.info( + "-- Testing BMS: " + str(retry) + " of " + str(retries) + " rounds" + ) # create a new battery object that can read the battery and run connection test for test in expected_bms_types: # noinspection PyBroadException try: - logger.info("Testing " + test["bms"].__name__) + logger.info( + "Testing " + + test["bms"].__name__ + + ( + ' at address "' + f"\\x{bytes(test['address']).hex()}" + '"' + if "address" in test + else "" + ) + ) batteryClass = test["bms"] baud = test["baud"] battery: Battery = batteryClass( @@ -100,9 +111,19 @@ def get_battery(_port) -> Union[Battery, None]: except KeyboardInterrupt: return None except Exception: + ( + exception_type, + exception_object, + exception_traceback, + ) = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) # Ignore any malfunction test_function() pass - count -= 1 + retry += 1 sleep(0.5) return None From 1c6225225d6c90c2200cba58790412003925a446 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 18:40:52 +0200 Subject: [PATCH 021/114] ANT BMS fixes Fixed that other devices are recognized as ANT BMS --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/ant.py | 15 +++++++++++++-- etc/dbus-serialbattery/config.default.ini | 2 +- etc/dbus-serialbattery/dbus-serialbattery.py | 8 ++++---- etc/dbus-serialbattery/utils.py | 7 +++---- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d26c7347..046be504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel +* Changed: Fixed that other devices are recognized as ANT BMS by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/ant.py b/etc/dbus-serialbattery/bms/ant.py index dca9cc29..b6e2d904 100644 --- a/etc/dbus-serialbattery/bms/ant.py +++ b/etc/dbus-serialbattery/bms/ant.py @@ -10,9 +10,9 @@ from struct import unpack_from -class Ant(Battery): +class ANT(Battery): def __init__(self, port, baud, address): - super(Ant, self).__init__(port, baud, address) + super(ANT, self).__init__(port, baud, address) self.type = self.BATTERYTYPE command_general = b"\xDB\xDB\x00\x00\x00\x00" @@ -31,6 +31,7 @@ def test_connection(self): result = False try: result = self.read_status_data() + result = result and self.refresh_data() except Exception as err: logger.error(f"Unexpected {err=}, {type(err)=}") result = False @@ -62,8 +63,15 @@ def read_status_data(self): voltage = unpack_from(">H", status_data, 4) self.voltage = voltage[0] * 0.1 + # check if data is in the thresholds, if not it's very likely that it's not an ANT BMS + if self.voltage < 0 and self.voltage > 100: + return False + current, self.soc = unpack_from(">lB", status_data, 70) self.current = 0.0 if current == 0 else current / -10 + # check if data is in the thresholds, if not it's very likely that it's not an ANT BMS + if self.soc < 0 or self.soc > 100 or abs(self.current) > 1000: + return False self.cell_count = unpack_from(">b", status_data, 123)[0] self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count @@ -79,6 +87,9 @@ def read_status_data(self): capacity = unpack_from(">L", status_data, 75) self.capacity = capacity[0] / 1000000 + # check if data is in the thresholds, if not it's very likely that it's not an ANT BMS + if self.capacity < 0 or self.capacity > 1000: + return False capacity_remain = unpack_from(">L", status_data, 79) self.capacity_remain = capacity_remain[0] / 1000000 diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 1eb08303..96cd90f4 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -187,7 +187,7 @@ TIME_TO_SOC_INC_FROM = False ; -- Available BMS: ; Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos ; -- Available BMS, but disabled by default (just enter one or more below and it will be enabled): -; Ant, MNB, Sinowealth +; ANT, MNB, Sinowealth BMS_TYPE = ; Exclute this serial devices from the driver startup diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index c72dd76f..5703e79b 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -32,8 +32,8 @@ from bms.seplos import Seplos # enabled only if explicitly set in config under "BMS_TYPE" -if "Ant" in utils.BMS_TYPE: - from bms.ant import Ant +if "ANT" in utils.BMS_TYPE: + from bms.ant import ANT if "MNB" in utils.BMS_TYPE: from bms.mnb import MNB if "Sinowealth" in utils.BMS_TYPE: @@ -54,8 +54,8 @@ ] # enabled only if explicitly set in config under "BMS_TYPE" -if "Ant" in utils.BMS_TYPE: - supported_bms_types.append({"bms": Ant, "baud": 19200}) +if "ANT" in utils.BMS_TYPE: + supported_bms_types.append({"bms": ANT, "baud": 19200}) if "MNB" in utils.BMS_TYPE: supported_bms_types.append({"bms": MNB, "baud": 9600}) if "Sinowealth" in utils.BMS_TYPE: diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 1a29237b..36d8523a 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -267,12 +267,11 @@ def _get_list_from_config( # --------- Additional settings --------- -# Specify only one BMS type to load else leave empty to try to load all available +# Specify one or more BMS types to load else leave empty to try to load all available # -- Available BMS: # Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos -# -- Available BMS, but disabled by default: -# https://louisvdw.github.io/dbus-serialbattery/general/install#how-to-enable-a-disabled-bms -# Ant, MNB, Sinowealth +# -- Available BMS, but disabled by default (just enter one or more below and it will be enabled): +# ANT, MNB, Sinowealth BMS_TYPE = _get_list_from_config("DEFAULT", "BMS_TYPE", lambda v: str(v)) # Exclute this serial devices from the driver startup From 3c11a1b6fe82db054ffd93f7b24faad4ee936e7a Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 18:42:09 +0200 Subject: [PATCH 022/114] Sinowealth BMS fixes Fixed that other devices are recognized as Sinowealth BMS --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/sinowealth.py | 27 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 046be504..2dee7efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed that other devices are recognized as ANT BMS by @mr-manuel +* Changed: Fixed that other devices are recognized as Sinowealth BMS by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py index b4cd7a4b..6806217c 100755 --- a/etc/dbus-serialbattery/bms/sinowealth.py +++ b/etc/dbus-serialbattery/bms/sinowealth.py @@ -67,7 +67,8 @@ def get_settings(self): self.hardware_version = "Daly/Sinowealth BMS " + str(self.cell_count) + " cells" logger.debug(self.hardware_version) - self.read_capacity() + if not self.read_capacity(): + return False for c in range(self.cell_count): self.cells.append(Cell(False)) @@ -147,7 +148,12 @@ def read_soc(self): if soc_data is False: return False logger.debug(">>> INFO: current SOC: %u", soc_data[1]) - self.soc = soc_data[1] + soc = soc_data[1] + # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS + if soc < 0 or soc > 100: + return False + + self.soc = soc return True def read_cycle_count(self): @@ -167,6 +173,10 @@ def read_pack_voltage(self): pack_voltage = unpack_from(">H", pack_voltage_data[:-1]) pack_voltage = pack_voltage[0] / 1000 logger.debug(">>> INFO: current pack voltage: %f", pack_voltage) + # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS + if pack_voltage < 0 or pack_voltage > 100: + return False + self.voltage = pack_voltage return True @@ -177,6 +187,10 @@ def read_pack_current(self): current = unpack_from(">i", current_data[:-1]) current = current[0] / 1000 logger.debug(">>> INFO: current pack current: %f", current) + # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS + if abs(current) > 1000: + return False + self.current = current return True @@ -198,8 +212,13 @@ def read_capacity(self): if capacity_data is False: return False capacity = unpack_from(">i", capacity_data[:-1]) - logger.debug(">>> INFO: Battery capacity: %f Ah", capacity[0] / 1000) - self.capacity = capacity[0] / 1000 + capacity = capacity[0] / 1000 + logger.debug(">>> INFO: Battery capacity: %f Ah", capacity) + # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS + if capacity < 0 or capacity > 1000: + return False + + self.capacity = capacity return True def read_pack_config_data(self): From d0a9d1991064e17373cacccfd64bbf68eef55a1d Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 19:01:45 +0200 Subject: [PATCH 023/114] improved publish_battery error handling switched from error count to seconds --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 3 +++ etc/dbus-serialbattery/dbushelper.py | 28 +++++++++++++++++++--------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dee7efa..85e3a2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Changed: Improved battery error handling on connection loss by @mr-manuel ## v1.0.20230531 diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 6fb79618..b6eb096c 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -78,6 +78,9 @@ def __init__(self, port, baud, address): self.init_values() def init_values(self): + """ + Used to reset values, if battery unexpectly disconnects + """ self.voltage = None self.current = None self.capacity_remain = None diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 946eb4d6..849225c4 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -33,7 +33,7 @@ def __init__(self, battery): self.battery = battery self.instance = 1 self.settings = None - self.error_count = 0 + self.error = {"count": 0, "timestamp_first": None, "timestamp_last": None} self.block_because_disconnect = False self._dbusservice = VeDbusService( "com.victronenergy.battery." @@ -361,9 +361,10 @@ def publish_battery(self, loop): # This is called every battery.poll_interval milli second as set up per battery type to read and update the data try: # Call the battery's refresh_data function - success = self.battery.refresh_data() - if success: - self.error_count = 0 + result = self.battery.refresh_data() + if result: + # reset error variables + self.error["count"] = 0 self.battery.online = True # unblock charge/discharge, if it was blocked when battery went offline @@ -371,9 +372,18 @@ def publish_battery(self, loop): self.block_because_disconnect = False else: - self.error_count += 1 - # If the battery is offline for more than 10 polls (polled every second for most batteries) - if self.error_count >= 10: + # update error variables + if self.error["count"] == 0: + self.error["timestamp_first"] = int(time()) + self.error["timestamp_last"] = int(time()) + self.error["count"] += 1 + + time_since_first_error = ( + self.error["timestamp_last"] - self.error["timestamp_first"] + ) + + # if the battery did not update in 10 second, it's assumed to be offline + if time_since_first_error >= 10: self.battery.online = False self.battery.init_values() @@ -381,8 +391,8 @@ def publish_battery(self, loop): if utils.BLOCK_ON_DISCONNECT: self.block_because_disconnect = True - # Has it completely failed - if self.error_count >= 60: + # if the battery did not update in 60 second, it's assumed to be completely failed + if time_since_first_error >= 60: loop.quit() # This is to mannage CVCL From e375512b9b5d3a23774f84f09f4cffd5db7c30b0 Mon Sep 17 00:00:00 2001 From: ogurevich <50322596+ogurevich@users.noreply.github.com> Date: Sun, 11 Jun 2023 20:18:00 +0200 Subject: [PATCH 024/114] Improve Battery Voltage Handling in Linear Absorption Mode * Refactor change time() to int(time()) for consistency in max_voltage_start_time and tDiff calculation * Refactor battery voltage calculations for efficiency and clarity * Remove penalty_buffer * Reset max_voltage_start_time wenn we going to bulk(dynamic) mode --- etc/dbus-serialbattery/battery.py | 48 ++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index a8c410a3..99fe3688 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -101,14 +101,16 @@ def init_values(self): self.cells: List[Cell] = [] self.control_charging = None self.control_voltage = None + self.max_battery_voltage = None + self.min_battery_voltage = None self.allow_max_voltage = True + self.max_voltage_start_time = None self.charge_mode = None self.charge_limitation = None self.discharge_limitation = None self.linear_cvl_last_set = 0 self.linear_ccl_last_set = 0 self.linear_dcl_last_set = 0 - self.max_voltage_start_time = None self.control_current = None self.control_previous_total = None self.control_previous_max = None @@ -218,6 +220,7 @@ def manage_charge_voltage(self) -> None: manages the charge voltage by setting self.control_voltage :return: None """ + self.prepare_voltage_management() if utils.CVCM_ENABLE: if utils.LINEAR_LIMITATION_ENABLE: self.manage_charge_voltage_linear() @@ -225,9 +228,13 @@ def manage_charge_voltage(self) -> None: self.manage_charge_voltage_step() # on CVCM_ENABLE = False apply max voltage else: - self.control_voltage = round((utils.MAX_CELL_VOLTAGE * self.cell_count), 3) + self.control_voltage = round(self.max_battery_voltage, 3) self.charge_mode = "Keep always max voltage" + def prepare_voltage_management(self) -> None: + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count + def manage_charge_voltage_linear(self) -> None: """ manages the charge voltage using linear interpolation by setting self.control_voltage @@ -250,20 +257,19 @@ def manage_charge_voltage_linear(self) -> None: if voltage > utils.MAX_CELL_VOLTAGE: # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second foundHighCellVoltage = True - penaltySum += voltage - utils.MAX_CELL_VOLTAGE - 0.010 + penaltySum += voltage - utils.MAX_CELL_VOLTAGE voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage() if self.max_voltage_start_time is None: # start timer, if max voltage is reached and cells are balanced if ( - (utils.MAX_CELL_VOLTAGE * self.cell_count) - utils.VOLTAGE_DROP - <= voltageSum + self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL and self.allow_max_voltage ): - self.max_voltage_start_time = time() + self.max_voltage_start_time = int(time()) # allow max voltage again, if cells are unbalanced or SoC threshold is reached elif ( @@ -272,12 +278,16 @@ def manage_charge_voltage_linear(self) -> None: ) and not self.allow_max_voltage: self.allow_max_voltage = True else: - tDiff = time() - self.max_voltage_start_time + tDiff = int(time()) - self.max_voltage_start_time # if utils.MAX_VOLTAGE_TIME_SEC < tDiff: # keep max voltage for 300 more seconds if 300 < tDiff: self.allow_max_voltage = False self.max_voltage_start_time = None + # we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode + # regardless of whether we were in absorption mode or not + if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP: + self.max_voltage_start_time = None # INFO: battery will only switch to Absorption, if all cells are balanced. # Reach MAX_CELL_VOLTAGE * cell count if they are all balanced. @@ -294,9 +304,9 @@ def manage_charge_voltage_linear(self) -> None: min( max( voltageSum - penaltySum, - utils.MIN_CELL_VOLTAGE * self.cell_count, + self.min_battery_voltage, ), - utils.MAX_CELL_VOLTAGE * self.cell_count, + self.max_battery_voltage, ), 3, ) @@ -312,17 +322,20 @@ def manage_charge_voltage_linear(self) -> None: else "Absorption dynamic" # + "(vS: " # + str(round(voltageSum, 2)) - # + " - pS: " + # + " tDiff: " + # + str(tDiff) + # + " pS: " # + str(round(penaltySum, 2)) # + ")" ) elif self.allow_max_voltage: - self.control_voltage = round( - (utils.MAX_CELL_VOLTAGE * self.cell_count), 3 - ) + self.control_voltage = round(self.max_battery_voltage, 3) self.charge_mode = ( - "Bulk" if self.max_voltage_start_time is None else "Absorption" + # "Bulk" if self.max_voltage_start_time is None else "Absorption" + "Bulk" + if self.max_voltage_start_time is None + else "Absorption" ) else: @@ -363,8 +376,9 @@ def manage_charge_voltage_step(self) -> None: if self.max_voltage_start_time is None: # check if max voltage is reached and start timer to keep max voltage if ( - utils.MAX_CELL_VOLTAGE * self.cell_count - ) - utils.VOLTAGE_DROP <= voltageSum and self.allow_max_voltage: + self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum + and self.allow_max_voltage + ): # example 2 self.max_voltage_start_time = time() @@ -391,7 +405,7 @@ def manage_charge_voltage_step(self) -> None: pass if self.allow_max_voltage: - self.control_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.control_voltage = self.max_battery_voltage self.charge_mode = ( "Bulk" if self.max_voltage_start_time is None else "Absorption" ) From 041f6a9ddc2dfa05a31069fef0d80a080358b09e Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 20:24:17 +0200 Subject: [PATCH 025/114] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e3a2f1..0dd60e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel * Changed: Improved battery error handling on connection loss by @mr-manuel +* Changed: Improved battery voltage handling in linear absorption mode by @ogurevich ## v1.0.20230531 From 2eddd5047133af9dd84c12ee6973ebae8c31c245 Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Sun, 11 Jun 2023 18:27:02 +0000 Subject: [PATCH 026/114] fix reply processing --- etc/dbus-serialbattery/bms/daly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 87510c48..d4a0eb0f 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -573,7 +573,7 @@ def write_soc_and_datetime(self, ser): ser.write(cmd) reply = self.read_sentence(ser, self.command_set_soc) - if reply[0] != 1: + if reply is False or reply[0] != 1: logger.error("write soc failed") return True From 80aa06c92dd9844017856e57c65b5c76fef2cb75 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 11 Jun 2023 22:57:42 +0200 Subject: [PATCH 027/114] Reduce the big inrush current, if the CVL jumps from Bulk/Absorbtion to Float fix https://github.com/Louisvdw/dbus-serialbattery/issues/659 --- CHANGELOG.md | 7 ++++--- etc/dbus-serialbattery/battery.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd60e19..bf60d635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,15 @@ * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel -* Changed: Fixed that other devices are recognized as ANT BMS by @mr-manuel +* Changed: Fixed that other devices are recognized as ANT BMS https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: Fixed that other devices are recognized as Sinowealth BMS by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel -* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel -* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel * Changed: Improved battery error handling on connection loss by @mr-manuel * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich +* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel +* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS ## v1.0.20230531 diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 0c7bef68..ed17ac00 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -343,9 +343,14 @@ def manage_charge_voltage_linear(self) -> None: ) else: - self.control_voltage = round( - (utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3 - ) + floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3) + if self.control_voltage: + if self.control_voltage >= (floatVoltage + 0.005): + self.control_voltage -= 0.005 + else: + self.control_voltage = floatVoltage + else: + self.control_voltage = floatVoltage self.charge_mode = "Float" if ( From 809ae600d9c7e13b0b720f82fce32e9e8f8397b5 Mon Sep 17 00:00:00 2001 From: wollew Date: Mon, 12 Jun 2023 11:36:53 +0200 Subject: [PATCH 028/114] Check returned data lenght for Seplos BMS Be stricter about the return data we accept, might fix the problem of grid meters accidently being recognized as a Seplos --- etc/dbus-serialbattery/bms/seplos.py | 34 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/etc/dbus-serialbattery/bms/seplos.py b/etc/dbus-serialbattery/bms/seplos.py index b7c9a2e1..2da2723c 100644 --- a/etc/dbus-serialbattery/bms/seplos.py +++ b/etc/dbus-serialbattery/bms/seplos.py @@ -115,7 +115,6 @@ def refresh_data(self): # This will be called for every iteration (self.poll_interval) # Return True if success, False for failure result_status = self.read_status_data() - # sleep(0.5) result_alarm = self.read_alarm_data() return result_status and result_alarm @@ -129,15 +128,20 @@ def decode_alarm_byte(data_byte: int, alarm_bit: int, warn_bit: int): return Protection.OK def read_alarm_data(self): + logger.debug("read alarm data") data = self.read_serial_data_seplos( self.encode_cmd(address=0x00, cid2=self.COMMAND_ALARM, info=b"01") ) - # check if connection success - if data is False: + # check if we could successfully read data and we have the expected length of 98 bytes + if data is False or len(data) != 98: return False - logger.debug("alarm info raw {}".format(data)) - return self.decode_alarm_data(bytes.fromhex(data.decode("ascii"))) + try: + logger.debug("alarm info raw {}".format(data)) + return self.decode_alarm_data(bytes.fromhex(data.decode("ascii"))) + except (ValueError, UnicodeDecodeError) as e: + logger.warning("could not hex-decode raw alarm data", exc_info=e) + return False def decode_alarm_data(self, data: bytes): logger.debug("alarm info decoded {}".format(data)) @@ -191,14 +195,20 @@ def decode_alarm_data(self, data: bytes): def read_status_data(self): logger.debug("read status data") + data = self.read_serial_data_seplos( self.encode_cmd(address=0x00, cid2=0x42, info=b"01") ) - # check if connection success - if data is False: + # check if reading data was successful and has the expected data length of 150 byte + if data is False or len(data) != 150: return False + self.decode_status_data(data) + + return True + + def decode_status_data(self, data): cell_count_offset = 4 voltage_offset = 6 temps_offset = 72 @@ -218,7 +228,6 @@ def read_status_data(self): ) / 10 self.cells[i].temp = temp logger.debug("Temp cell[{}]={}°C".format(i, temp)) - self.temp1 = ( Seplos.int_from_2byte_hex_ascii(data, temps_offset + 4 * 4) - 2731 ) / 10 @@ -234,7 +243,6 @@ def read_status_data(self): self.soc = Seplos.int_from_2byte_hex_ascii(data, offset=114) / 10 self.cycles = Seplos.int_from_2byte_hex_ascii(data, offset=122) self.hardware_version = "Seplos BMS {} cells".format(self.cell_count) - logger.debug("Current = {}A , Voltage = {}V".format(self.current, self.voltage)) logger.debug( "Capacity = {}/{}Ah , SOC = {}%".format( @@ -249,8 +257,6 @@ def read_status_data(self): ) logger.debug("HW:" + self.hardware_version) - return True - @staticmethod def is_valid_frame(data: bytes) -> bool: """checks if data contains a valid frame @@ -260,7 +266,7 @@ def is_valid_frame(data: bytes) -> bool: * not checked: lchksum """ if len(data) < 18: - logger.debug("short read, data={}".format(data)) + logger.warning("short read, data={}".format(data)) return False chksum = Seplos.get_checksum(data[1:-5]) @@ -297,7 +303,9 @@ def read_serial_data_seplos(self, command): return_data = data[length_pos + 3 : -5] info_length = Seplos.int_from_2byte_hex_ascii(b"0" + data[length_pos:], 0) logger.debug( - "return info data of length {} : {}".format(info_length, return_data) + "returning info data of length {}, info_length is {} : {}".format( + len(return_data), info_length, return_data + ) ) return return_data From 882daf50c06fd31f4ded1fbce88f46d3e28b899e Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 12 Jun 2023 15:38:51 +0200 Subject: [PATCH 029/114] Validate current, voltage, capacity and SoC for all BMS This prevents that a device, which is no BMS, is detected as BMS --- CHANGELOG.md | 3 +- etc/dbus-serialbattery/battery.py | 28 +++++++++++++++++++ etc/dbus-serialbattery/bms/ant.py | 9 ------ .../bms/battery_template.py | 5 ++-- etc/dbus-serialbattery/bms/hlpdatabms4s.py | 8 +++--- etc/dbus-serialbattery/bms/seplos.py | 7 +++-- etc/dbus-serialbattery/bms/sinowealth.py | 16 +---------- etc/dbus-serialbattery/dbus-serialbattery.py | 8 +++--- 8 files changed, 46 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf60d635..d0c9b7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,11 @@ * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel -* Changed: Fixed that other devices are recognized as ANT BMS https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel -* Changed: Fixed that other devices are recognized as Sinowealth BMS by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved battery error handling on connection loss by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index ed17ac00..083d00d4 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -986,6 +986,34 @@ def get_mos_temp(self) -> Union[float, None]: else: return None + def validate_data(self) -> bool: + """ + Used to validate the data received from the BMS. + If the data is in the thresholds return True, + else return False since it's very probably not a BMS + """ + if self.capacity < 0 or self.capacity > 1000: + logger.debug( + "Capacity outside of thresholds (from 0 to 1000): " + str(self.capacity) + ) + return False + if abs(self.current) > 1000: + logger.debug( + "Current outside of thresholds (from -1000 to 1000): " + + str(self.current) + ) + return False + if self.voltage < 0 or self.voltage > 100: + logger.debug( + "Voltage outside of thresholds (form 0 to 100): " + str(self.voltage) + ) + return False + if self.soc < 0 or self.soc > 100: + logger.debug("SoC outside of thresholds (from 0 to 100): " + str(self.soc)) + return False + + return True + def log_cell_data(self) -> bool: if logger.getEffectiveLevel() > logging.INFO and len(self.cells) == 0: return False diff --git a/etc/dbus-serialbattery/bms/ant.py b/etc/dbus-serialbattery/bms/ant.py index b6e2d904..ceb6be68 100644 --- a/etc/dbus-serialbattery/bms/ant.py +++ b/etc/dbus-serialbattery/bms/ant.py @@ -63,15 +63,9 @@ def read_status_data(self): voltage = unpack_from(">H", status_data, 4) self.voltage = voltage[0] * 0.1 - # check if data is in the thresholds, if not it's very likely that it's not an ANT BMS - if self.voltage < 0 and self.voltage > 100: - return False current, self.soc = unpack_from(">lB", status_data, 70) self.current = 0.0 if current == 0 else current / -10 - # check if data is in the thresholds, if not it's very likely that it's not an ANT BMS - if self.soc < 0 or self.soc > 100 or abs(self.current) > 1000: - return False self.cell_count = unpack_from(">b", status_data, 123)[0] self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count @@ -87,9 +81,6 @@ def read_status_data(self): capacity = unpack_from(">L", status_data, 75) self.capacity = capacity[0] / 1000000 - # check if data is in the thresholds, if not it's very likely that it's not an ANT BMS - if self.capacity < 0 or self.capacity > 1000: - return False capacity_remain = unpack_from(">L", status_data, 79) self.capacity_remain = capacity_remain[0] / 1000000 diff --git a/etc/dbus-serialbattery/bms/battery_template.py b/etc/dbus-serialbattery/bms/battery_template.py index 92c7fa49..e859c675 100644 --- a/etc/dbus-serialbattery/bms/battery_template.py +++ b/etc/dbus-serialbattery/bms/battery_template.py @@ -28,8 +28,7 @@ def test_connection(self): try: result = self.read_status_data() # get first data to show in startup log, only if result is true - if result: - self.refresh_data() + result = result and self.refresh_data() except Exception as err: logger.error(f"Unexpected {err=}, {type(err)=}") result = False @@ -87,6 +86,8 @@ def read_status_data(self): self.cycles, ) = unpack_from(">bb??bhx", status_data) + # Integrate a check to be sure, that the received data is from the BMS type you are making this driver for + self.hardware_version = "TemplateBMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) return True diff --git a/etc/dbus-serialbattery/bms/hlpdatabms4s.py b/etc/dbus-serialbattery/bms/hlpdatabms4s.py index 7faf8b2c..16eee75e 100644 --- a/etc/dbus-serialbattery/bms/hlpdatabms4s.py +++ b/etc/dbus-serialbattery/bms/hlpdatabms4s.py @@ -196,16 +196,16 @@ def manage_charge_current(self): self.control_discharge_current = 1000 def read_serial_data_HLPdataBMS4S(self, command, time, min_len): - data = read_serial_data2(command, self.port, self.baud_rate, time, min_len) + data = read_serial_data(command, self.port, self.baud_rate, time, min_len) if data is False: return False return data -def read_serial_data2(command, port, baud, time, min_len): +def read_serial_data(command, port, baud, time, min_len): try: with serial.Serial(port, baudrate=baud, timeout=0.5) as ser: - ret = read_serialport_data2(ser, command, time, min_len) + ret = read_serialport_data(ser, command, time, min_len) if ret is True: return ret return False @@ -218,7 +218,7 @@ def read_serial_data2(command, port, baud, time, min_len): return False -def read_serialport_data2(ser, command, time, min_len): +def read_serialport_data(ser, command, time, min_len): try: cnt = 0 while cnt < 3: diff --git a/etc/dbus-serialbattery/bms/seplos.py b/etc/dbus-serialbattery/bms/seplos.py index 2da2723c..0a0c3fe2 100644 --- a/etc/dbus-serialbattery/bms/seplos.py +++ b/etc/dbus-serialbattery/bms/seplos.py @@ -204,7 +204,8 @@ def read_status_data(self): if data is False or len(data) != 150: return False - self.decode_status_data(data) + if not self.decode_status_data(data): + return False return True @@ -257,6 +258,8 @@ def decode_status_data(self, data): ) logger.debug("HW:" + self.hardware_version) + return True + @staticmethod def is_valid_frame(data: bytes) -> bool: """checks if data contains a valid frame @@ -266,7 +269,7 @@ def is_valid_frame(data: bytes) -> bool: * not checked: lchksum """ if len(data) < 18: - logger.warning("short read, data={}".format(data)) + logger.debug("short read, data={}".format(data)) return False chksum = Seplos.get_checksum(data[1:-5]) diff --git a/etc/dbus-serialbattery/bms/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py index 6806217c..8e4e900e 100755 --- a/etc/dbus-serialbattery/bms/sinowealth.py +++ b/etc/dbus-serialbattery/bms/sinowealth.py @@ -149,10 +149,6 @@ def read_soc(self): return False logger.debug(">>> INFO: current SOC: %u", soc_data[1]) soc = soc_data[1] - # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS - if soc < 0 or soc > 100: - return False - self.soc = soc return True @@ -172,12 +168,8 @@ def read_pack_voltage(self): return False pack_voltage = unpack_from(">H", pack_voltage_data[:-1]) pack_voltage = pack_voltage[0] / 1000 - logger.debug(">>> INFO: current pack voltage: %f", pack_voltage) - # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS - if pack_voltage < 0 or pack_voltage > 100: - return False - self.voltage = pack_voltage + logger.debug(">>> INFO: current pack voltage: %f", self.voltage) return True def read_pack_current(self): @@ -187,9 +179,6 @@ def read_pack_current(self): current = unpack_from(">i", current_data[:-1]) current = current[0] / 1000 logger.debug(">>> INFO: current pack current: %f", current) - # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS - if abs(current) > 1000: - return False self.current = current return True @@ -214,9 +203,6 @@ def read_capacity(self): capacity = unpack_from(">i", capacity_data[:-1]) capacity = capacity[0] / 1000 logger.debug(">>> INFO: Battery capacity: %f Ah", capacity) - # check if data is in the thresholds, if not it's very likely that it's not a Sinowealth BMS - if capacity < 0 or capacity > 1000: - return False self.capacity = capacity return True diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 5703e79b..72c3234c 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -103,7 +103,7 @@ def get_battery(_port) -> Union[Battery, None]: battery: Battery = batteryClass( port=_port, baud=baud, address=test.get("address") ) - if battery.test_connection(): + if battery.test_connection() and battery.validate_data(): logger.info( "Connection established to " + battery.__class__.__name__ ) @@ -135,12 +135,12 @@ def get_port() -> str: if port not in utils.EXCLUDED_DEVICES: return port else: - logger.info( + logger.debug( "Stopping dbus-serialbattery: " + str(port) + " is excluded trough the config file" ) - sleep(86400) + sleep(60) sys.exit(0) else: # just for MNB-SPI @@ -166,7 +166,7 @@ def get_port() -> str: class_ = eval(port) testbms = class_("", 9600, sys.argv[2]) - if testbms.test_connection() is True: + if testbms.test_connection(): logger.info("Connection established to " + testbms.__class__.__name__) battery = testbms else: From 16ad3e19a9bb2fe47252e6749cf5396e11c6079e Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 12 Jun 2023 16:54:01 +0200 Subject: [PATCH 030/114] removed double check --- etc/dbus-serialbattery/battery.py | 149 +++++++++++++++--------------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 083d00d4..b03517f0 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -250,48 +250,46 @@ def manage_charge_voltage_linear(self) -> None: tDiff = 0 try: - if utils.CVCM_ENABLE: - # calculate battery sum - for i in range(self.cell_count): - voltage = self.get_cell_voltage(i) - if voltage: - voltageSum += voltage - - # calculate penalty sum to prevent single cell overcharge by using current cell voltage - if voltage > utils.MAX_CELL_VOLTAGE: - # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second - foundHighCellVoltage = True - penaltySum += voltage - utils.MAX_CELL_VOLTAGE - - voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage() - - if self.max_voltage_start_time is None: - # start timer, if max voltage is reached and cells are balanced - if ( - self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum - and voltageDiff - <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL - and self.allow_max_voltage - ): - self.max_voltage_start_time = int(time()) - - # allow max voltage again, if cells are unbalanced or SoC threshold is reached - elif ( - utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc - or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT - ) and not self.allow_max_voltage: - self.allow_max_voltage = True - else: - tDiff = int(time()) - self.max_voltage_start_time - # if utils.MAX_VOLTAGE_TIME_SEC < tDiff: - # keep max voltage for 300 more seconds - if 300 < tDiff: - self.allow_max_voltage = False - self.max_voltage_start_time = None - # we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode - # regardless of whether we were in absorption mode or not - if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP: - self.max_voltage_start_time = None + # calculate battery sum + for i in range(self.cell_count): + voltage = self.get_cell_voltage(i) + if voltage: + voltageSum += voltage + + # calculate penalty sum to prevent single cell overcharge by using current cell voltage + if voltage > utils.MAX_CELL_VOLTAGE: + # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second + foundHighCellVoltage = True + penaltySum += voltage - utils.MAX_CELL_VOLTAGE + + voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage() + + if self.max_voltage_start_time is None: + # start timer, if max voltage is reached and cells are balanced + if ( + self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum + and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL + and self.allow_max_voltage + ): + self.max_voltage_start_time = int(time()) + + # allow max voltage again, if cells are unbalanced or SoC threshold is reached + elif ( + utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc + or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT + ) and not self.allow_max_voltage: + self.allow_max_voltage = True + else: + tDiff = int(time()) - self.max_voltage_start_time + # if utils.MAX_VOLTAGE_TIME_SEC < tDiff: + # keep max voltage for 300 more seconds + if 300 < tDiff: + self.allow_max_voltage = False + self.max_voltage_start_time = None + # we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode + # regardless of whether we were in absorption mode or not + if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP: + self.max_voltage_start_time = None # INFO: battery will only switch to Absorption, if all cells are balanced. # Reach MAX_CELL_VOLTAGE * cell count if they are all balanced. @@ -375,43 +373,42 @@ def manage_charge_voltage_step(self) -> None: tDiff = 0 try: - if utils.CVCM_ENABLE: - # calculate battery sum - for i in range(self.cell_count): - voltage = self.get_cell_voltage(i) - if voltage: - voltageSum += voltage - - if self.max_voltage_start_time is None: - # check if max voltage is reached and start timer to keep max voltage - if ( - self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum - and self.allow_max_voltage - ): - # example 2 - self.max_voltage_start_time = time() - - # check if reset soc is greater than battery soc - # this prevents flapping between max and float voltage - elif ( - utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc - and not self.allow_max_voltage - ): - self.allow_max_voltage = True - - # do nothing - else: - pass + # calculate battery sum + for i in range(self.cell_count): + voltage = self.get_cell_voltage(i) + if voltage: + voltageSum += voltage + + if self.max_voltage_start_time is None: + # check if max voltage is reached and start timer to keep max voltage + if ( + self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum + and self.allow_max_voltage + ): + # example 2 + self.max_voltage_start_time = time() + + # check if reset soc is greater than battery soc + # this prevents flapping between max and float voltage + elif ( + utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc + and not self.allow_max_voltage + ): + self.allow_max_voltage = True - # timer started + # do nothing else: - tDiff = time() - self.max_voltage_start_time - if utils.MAX_VOLTAGE_TIME_SEC < tDiff: - self.allow_max_voltage = False - self.max_voltage_start_time = None + pass - else: - pass + # timer started + else: + tDiff = time() - self.max_voltage_start_time + if utils.MAX_VOLTAGE_TIME_SEC < tDiff: + self.allow_max_voltage = False + self.max_voltage_start_time = None + + else: + pass if self.allow_max_voltage: self.control_voltage = self.max_battery_voltage From 3f220d23abaa4f7422c3b35cbf638bbcea0a7615 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 12 Jun 2023 18:03:58 +0200 Subject: [PATCH 031/114] bump version --- etc/dbus-serialbattery/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 36d8523a..0e49af52 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230611dev" +DRIVER_VERSION = "1.0.20230612dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 19b437ba23a3c4bd39acd9bb20eda158bd3a59a1 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 12 Jun 2023 19:30:55 +0200 Subject: [PATCH 032/114] fix validation if None --- etc/dbus-serialbattery/battery.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index b03517f0..5ffabd81 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -989,23 +989,23 @@ def validate_data(self) -> bool: If the data is in the thresholds return True, else return False since it's very probably not a BMS """ - if self.capacity < 0 or self.capacity > 1000: + if self.capacity is not None and (self.capacity < 0 or self.capacity > 1000): logger.debug( "Capacity outside of thresholds (from 0 to 1000): " + str(self.capacity) ) return False - if abs(self.current) > 1000: + if self.current is not None and abs(self.current) > 1000: logger.debug( "Current outside of thresholds (from -1000 to 1000): " + str(self.current) ) return False - if self.voltage < 0 or self.voltage > 100: + if self.voltage is not None and (self.voltage < 0 or self.voltage > 100): logger.debug( "Voltage outside of thresholds (form 0 to 100): " + str(self.voltage) ) return False - if self.soc < 0 or self.soc > 100: + if self.soc is not None and (self.soc < 0 or self.soc > 100): logger.debug("SoC outside of thresholds (from 0 to 100): " + str(self.soc)) return False From 0245022098126b99781a5ec8f4df801377a9dd74 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 12 Jun 2023 20:08:33 +0200 Subject: [PATCH 033/114] updated changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c9b7ea..a3e65b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,10 @@ * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel -* Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel +* Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel +* Changed: Fix daly readsentence by @transistorgit * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed typo in `config.ini` sample by @hoschult From 708d2545e949dd16390150190c07d2f0fd98e4fa Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Tue, 13 Jun 2023 07:35:11 +0200 Subject: [PATCH 034/114] proposal to #659 formatted :) --- etc/dbus-serialbattery/battery.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 5ffabd81..16427db8 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -108,6 +108,8 @@ def init_values(self): self.min_battery_voltage = None self.allow_max_voltage = True self.max_voltage_start_time = None + self.transition_start_time = None + self.control_voltage_at_transition_start = None self.charge_mode = None self.charge_limitation = None self.discharge_limitation = None @@ -343,13 +345,25 @@ def manage_charge_voltage_linear(self) -> None: else: floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3) if self.control_voltage: - if self.control_voltage >= (floatVoltage + 0.005): - self.control_voltage -= 0.005 - else: - self.control_voltage = floatVoltage + if ( + self.charge_mode != "Float" + and self.charge_mode != "Float Transition" + ): + self.transition_start_time = time.time() + self.initial_control_voltage = self.control_voltage + self.charge_mode = "Float Transition" + elif self.charge_mode == "Float Transition": + elapsed_time = time.time() - self.transition_start_time + # Duration in seconds for smooth voltage drop from absorption to float + FLOAT_MODE_TRANSITION_DURATION = 180 + t = min(1, elapsed_time / FLOAT_MODE_TRANSITION_DURATION) + self.control_voltage = ( + 1 - t + ) * self.initial_control_voltage + t * floatVoltage + if t == 1: + self.charge_mode = "Float" else: self.control_voltage = floatVoltage - self.charge_mode = "Float" if ( self.allow_max_voltage From 0ab8bc665ce578745909d7648a0f71a1d73a0614 Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Tue, 13 Jun 2023 14:24:02 +0200 Subject: [PATCH 035/114] bugfix proposal to #659 --- etc/dbus-serialbattery/battery.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 16427db8..1f5914bf 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -345,15 +345,12 @@ def manage_charge_voltage_linear(self) -> None: else: floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3) if self.control_voltage: - if ( - self.charge_mode != "Float" - and self.charge_mode != "Float Transition" - ): - self.transition_start_time = time.time() + if not self.charge_mode.startswith("Float"): + self.transition_start_time = int(time()) self.initial_control_voltage = self.control_voltage self.charge_mode = "Float Transition" - elif self.charge_mode == "Float Transition": - elapsed_time = time.time() - self.transition_start_time + elif self.charge_mode.startswith("Float Transition"): + elapsed_time = int(time()) - self.transition_start_time # Duration in seconds for smooth voltage drop from absorption to float FLOAT_MODE_TRANSITION_DURATION = 180 t = min(1, elapsed_time / FLOAT_MODE_TRANSITION_DURATION) @@ -362,8 +359,11 @@ def manage_charge_voltage_linear(self) -> None: ) * self.initial_control_voltage + t * floatVoltage if t == 1: self.charge_mode = "Float" + else: + self.charge_mode = "Float Transition" else: self.control_voltage = floatVoltage + self.charge_mode = "Float" if ( self.allow_max_voltage From cac8bfb12e77ee82afc8d23e0c7fa7f47bf0fe4a Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Tue, 13 Jun 2023 14:36:28 +0200 Subject: [PATCH 036/114] refactor setting float charge_mode --- etc/dbus-serialbattery/battery.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 1f5914bf..d5c87e4b 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -344,11 +344,12 @@ def manage_charge_voltage_linear(self) -> None: else: floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3) + chargeMode = "Float" if self.control_voltage: if not self.charge_mode.startswith("Float"): self.transition_start_time = int(time()) self.initial_control_voltage = self.control_voltage - self.charge_mode = "Float Transition" + chargeMode = "Float Transition" elif self.charge_mode.startswith("Float Transition"): elapsed_time = int(time()) - self.transition_start_time # Duration in seconds for smooth voltage drop from absorption to float @@ -358,12 +359,12 @@ def manage_charge_voltage_linear(self) -> None: 1 - t ) * self.initial_control_voltage + t * floatVoltage if t == 1: - self.charge_mode = "Float" + chargeMode = "Float" else: - self.charge_mode = "Float Transition" + chargeMode = "Float Transition" else: self.control_voltage = floatVoltage - self.charge_mode = "Float" + self.charge_mode = chargeMode if ( self.allow_max_voltage From b9838e43b2865df52e54f2e9622f31dc1ef61344 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 13 Jun 2023 16:49:13 +0200 Subject: [PATCH 037/114] fix type error, removed bluetooth cronjob --- etc/dbus-serialbattery/battery.py | 2 +- etc/dbus-serialbattery/reinstall-local.sh | 7 +++++-- etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 5ffabd81..de2d986e 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -141,7 +141,7 @@ def unique_identifier(self) -> str: On +/- 5 Ah you can identify 11 batteries """ string = ( - "".join(filter(str.isalnum, self.hardware_version)) + "_" + "".join(filter(str.isalnum, str(self.hardware_version))) + "_" if self.hardware_version is not None and self.hardware_version != "" else "" ) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index d0982864..d5195cab 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -241,8 +241,11 @@ if [ "$length" -gt 0 ]; then echo # setup cronjob to restart Bluetooth - # remove if not needed anymore, has to be checked first - grep -qxF "5 0,12 * * * /etc/init.d/bluetooth restart" /var/spool/cron/root || echo "5 0,12 * * * /etc/init.d/bluetooth restart" >> /var/spool/cron/root + # remove if not needed anymore, has to be checked first --> seems that it's not needed anymore + # grep -qxF "5 0,12 * * * /etc/init.d/bluetooth restart" /var/spool/cron/root || echo "5 0,12 * * * /etc/init.d/bluetooth restart" >> /var/spool/cron/root + + # remove cronjob + sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root else diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 0e49af52..aec28640 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230612dev" +DRIVER_VERSION = "1.0.20230613dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From a0ba94501fd20b542321bad909f4011105e66e48 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 13 Jun 2023 16:51:58 +0200 Subject: [PATCH 038/114] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e65b26..1db8a136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS +* Removed: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel ## v1.0.20230531 From b0ed2a44314cb08a2c2f1d4afcf224fa76516d6b Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Tue, 13 Jun 2023 20:44:37 +0000 Subject: [PATCH 039/114] fix rs485 write communication errors by inserting sleeps, add debug print for charge mode and fix crash on write soc failures --- etc/dbus-serialbattery/bms/daly.py | 13 ++++++++++++- etc/dbus-serialbattery/service/log/run | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index af72d10f..c748fd86 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -186,6 +186,9 @@ def refresh_data(self): def update_soc(self, ser): if self.last_charge_mode is not None and self.charge_mode is not None: + #debug + if self.last_charge_mode != self.charge_mode: + logger.info(f"Switch charge mode from {self.last_charge_mode} to {self.charge_mode}") if not self.last_charge_mode.startswith( "Float" ) and self.charge_mode.startswith("Float"): @@ -560,6 +563,10 @@ def write_soc_and_datetime(self, ser): if self.soc_to_set is None: return False + # wait shortly, else the Daly is not ready and throws a lot of no reply errors + # if you see a lot of errors, try to increase in steps of 0.005 + sleep(0.020) + cmd = bytearray(13) now = datetime.now() @@ -589,7 +596,7 @@ def write_soc_and_datetime(self, ser): ser.write(cmd) reply = self.read_sentence(ser, self.command_set_soc) - if reply[0] != 1: + if reply is False or reply[0] != 1: logger.error("write soc failed") return True @@ -622,6 +629,10 @@ def force_discharging_off_callback(self, path, value): return False def write_charge_discharge_mos(self, ser): + # wait shortly, else the Daly is not ready and throws a lot of no reply errors + # if you see a lot of errors, try to increase in steps of 0.005 + sleep(0.020) + if ( self.trigger_force_disable_charge is None and self.trigger_force_disable_discharge is None diff --git a/etc/dbus-serialbattery/service/log/run b/etc/dbus-serialbattery/service/log/run index ca6196de..d826d8ba 100755 --- a/etc/dbus-serialbattery/service/log/run +++ b/etc/dbus-serialbattery/service/log/run @@ -1,3 +1,3 @@ #!/bin/sh exec 2>&1 -exec multilog t s25000 n4 /var/log/dbus-serialbattery.TTY +exec multilog t s2500000 n4 /var/log/dbus-serialbattery.TTY From 3ea976a8d684b1a07043657b95abb57e25873684 Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:13:44 +0000 Subject: [PATCH 040/114] fix write problem on set_soc. also changed the switch charge/discharge function, just in case --- etc/dbus-serialbattery/bms/daly.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 5a9375dd..3d571696 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -548,6 +548,10 @@ def write_soc_and_datetime(self, ser): if self.soc_to_set is None: return False + # wait shortly, else the Daly is not ready and throws a lot of no reply errors + # if you see a lot of errors, try to increase in steps of 0.005 + sleep(0.020) + cmd = bytearray(13) now = datetime.now() @@ -616,6 +620,10 @@ def write_charge_discharge_mos(self, ser): ): return False + # wait shortly, else the Daly is not ready and throws a lot of no reply errors + # if you see a lot of errors, try to increase in steps of 0.005 + sleep(0.020) + cmd = bytearray(self.command_base) if self.trigger_force_disable_charge is not None: From 75dd0091587d68736923fd582baaec8f4f5ede1c Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Thu, 15 Jun 2023 12:50:37 +0000 Subject: [PATCH 041/114] debug msg --- etc/dbus-serialbattery/battery.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 1ce3c5be..61d468e0 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -257,6 +257,9 @@ def manage_charge_voltage_linear(self) -> None: self.allow_max_voltage = False self.max_voltage_start_time = None + logger.info(f"highCell {foundHighCellVoltage}, vDiff {voltageDiff}, vStartTime {self.max_voltage_start_time}, allowMaxV {self.allow_max_voltage}") + + # INFO: battery will only switch to Absorption, if all cells are balanced. # Reach MAX_CELL_VOLTAGE * cell count if they are all balanced. if foundHighCellVoltage and self.allow_max_voltage: From c2c102b3a676eac81ef0569add42b15304cd58a7 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 15 Jun 2023 18:25:03 +0200 Subject: [PATCH 042/114] Bluetooth optimizations --- etc/dbus-serialbattery/reinstall-local.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index d5195cab..48585e3e 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -156,6 +156,9 @@ if [ -d "/service/dbus-blebattery.0" ]; then pkill -f "supervise dbus-blebattery.*" pkill -f "multilog .* /var/log/dbus-blebattery.*" pkill -f "python .*/dbus-serialbattery.py .*_Ble" + + # kill opened bluetoothctl processes + pkill -f "^bluetoothctl " fi From eb28b6187fd39f6d8b5473e1e393df5e31655c16 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 15 Jun 2023 18:26:00 +0200 Subject: [PATCH 043/114] Fixes by @peterohman https://github.com/Louisvdw/dbus-serialbattery/pull/505#issuecomment-1587665083 --- etc/dbus-serialbattery/bms/hlpdatabms4s.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/etc/dbus-serialbattery/bms/hlpdatabms4s.py b/etc/dbus-serialbattery/bms/hlpdatabms4s.py index 16eee75e..6a053753 100644 --- a/etc/dbus-serialbattery/bms/hlpdatabms4s.py +++ b/etc/dbus-serialbattery/bms/hlpdatabms4s.py @@ -54,21 +54,8 @@ def refresh_data(self): pass return result - # def log_settings(self): - # logger.info(f'Battery {self.type} connected to dbus from {self.port}') - # logger.info(f'=== Settings ===') - # cell_counter = len(self.cells) - # logger.info(f'> Connection voltage {self.voltage}V | current {self.current}A | SOC {self.soc}%') - # logger.info(f'> Cell count {self.cell_count} | cells populated {cell_counter}') - # logger.info(f'> CCCM SOC {CCCM_SOC_ENABLE} | DCCM SOC {DCCM_SOC_ENABLE}') - # logger.info(f'> CCCM CV {CCCM_CV_ENABLE} | DCCM CV {DCCM_CV_ENABLE}') - # logger.info(f'> CCCM T {CCCM_T_ENABLE} | DCCM T {DCCM_T_ENABLE}') - # logger.info(f'> MIN_CELL_VOLTAGE {MIN_CELL_VOLTAGE}V | MAX_CELL_VOLTAGE {MAX_CELL_VOLTAGE}V') - - return - def read_test_data(self): - test_data = self.read_serial_data_HLPdataBMS4S(b"pv\n", 1, 15) + test_data = self.read_serial_data_HLPdataBMS4S(b"pv\n", 0.2, 12) if test_data is False: return False s1 = str(test_data) @@ -220,6 +207,9 @@ def read_serial_data(command, port, baud, time, min_len): def read_serialport_data(ser, command, time, min_len): try: + if min_len == 12: + ser.write(b"\n") + sleep(0.2) cnt = 0 while cnt < 3: cnt += 1 @@ -227,7 +217,8 @@ def read_serialport_data(ser, command, time, min_len): ser.flushInput() ser.write(command) sleep(time) - res = ser.read(1000) + toread = ser.inWaiting() + res = ser.read(toread) if len(res) >= min_len: return res return False From 02e914015a33d909b1460ca32701c64850dbd00a Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 15 Jun 2023 18:27:00 +0200 Subject: [PATCH 044/114] fix #712 --- etc/dbus-serialbattery/bms/renogy.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/bms/renogy.py b/etc/dbus-serialbattery/bms/renogy.py index acfe2335..e920a771 100644 --- a/etc/dbus-serialbattery/bms/renogy.py +++ b/etc/dbus-serialbattery/bms/renogy.py @@ -48,8 +48,7 @@ def test_connection(self): try: result = self.read_gen_data() # get first data to show in startup log - if result: - self.refresh_data() + result = result and self.refresh_data() except Exception as err: logger.error(f"Unexpected {err=}, {type(err)=}") result = False @@ -145,6 +144,8 @@ def read_cell_data(self): self.cells[c].voltage = 0 return True + """ + # Did not found who changed this. "command_env_temp_count" is missing def read_temp_data(self): # Check to see how many Enviromental Temp Sensors this battery has, it may have none. num_env_temps = self.read_serial_data_renogy(self.command_env_temp_count) @@ -172,6 +173,17 @@ def read_temp_data(self): logger.info("temp2 = %s °C", temp2) return True + """ + + def read_temp_data(self): + temp1 = self.read_serial_data_renogy(self.command_bms_temp1) + temp2 = self.read_serial_data_renogy(self.command_bms_temp2) + if temp1 is False: + return False + self.temp1 = unpack(">H", temp1)[0] / 10 + self.temp2 = unpack(">H", temp2)[0] / 10 + + return True def read_bms_config(self): return True From 51b71d600f455b6eb5974ff537155ad0766d3610 Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:32:00 +0000 Subject: [PATCH 045/114] fix meaningless time to go values --- etc/dbus-serialbattery/dbushelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 849225c4..b0a217d7 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -612,7 +612,7 @@ def publish_dbus(self): ) ) ) - if self.battery.current + if self.battery.current and abs(self.battery.current) > 0.1 else None ) From b82c3ae64ce757ff294688acfabae5768062a8a6 Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:32:00 +0000 Subject: [PATCH 046/114] fix meaningless time to go values --- etc/dbus-serialbattery/dbushelper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 849225c4..b0a217d7 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -612,7 +612,7 @@ def publish_dbus(self): ) ) ) - if self.battery.current + if self.battery.current and abs(self.battery.current) > 0.1 else None ) From be2a4d5fdbc98755f4a3233a6c55fd9bd1ada2cb Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Fri, 16 Jun 2023 08:36:56 +0200 Subject: [PATCH 047/114] Duration of transition to float depends on number of cells --- etc/dbus-serialbattery/battery.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index d5c87e4b..b4b4623b 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -353,7 +353,8 @@ def manage_charge_voltage_linear(self) -> None: elif self.charge_mode.startswith("Float Transition"): elapsed_time = int(time()) - self.transition_start_time # Duration in seconds for smooth voltage drop from absorption to float - FLOAT_MODE_TRANSITION_DURATION = 180 + # depending on the number of cells + FLOAT_MODE_TRANSITION_DURATION = self.cell_count * 12 t = min(1, elapsed_time / FLOAT_MODE_TRANSITION_DURATION) self.control_voltage = ( 1 - t From 17fd33c4d1994cda58870007fea70aa69a747e7b Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Fri, 16 Jun 2023 08:57:17 +0200 Subject: [PATCH 048/114] Float transition - Voltage drop per second --- etc/dbus-serialbattery/battery.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index b4b4623b..01ef336c 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -352,14 +352,17 @@ def manage_charge_voltage_linear(self) -> None: chargeMode = "Float Transition" elif self.charge_mode.startswith("Float Transition"): elapsed_time = int(time()) - self.transition_start_time - # Duration in seconds for smooth voltage drop from absorption to float - # depending on the number of cells - FLOAT_MODE_TRANSITION_DURATION = self.cell_count * 12 - t = min(1, elapsed_time / FLOAT_MODE_TRANSITION_DURATION) + # Voltage drop per second + VOLTAGE_DROP_PER_SECOND = 0.01 / 10 + voltage_drop = min( + VOLTAGE_DROP_PER_SECOND * elapsed_time, + self.initial_control_voltage - floatVoltage, + ) self.control_voltage = ( - 1 - t - ) * self.initial_control_voltage + t * floatVoltage - if t == 1: + self.initial_control_voltage - voltage_drop + ) + if self.control_voltage <= floatVoltage: + self.control_voltage = floatVoltage chargeMode = "Float" else: chargeMode = "Float Transition" From bffffdbf6bcd49b930ac711abc3a832692179d34 Mon Sep 17 00:00:00 2001 From: peterohman Date: Fri, 16 Jun 2023 11:38:57 +0200 Subject: [PATCH 049/114] Update hlpdatabms4s.py --- etc/dbus-serialbattery/bms/hlpdatabms4s.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/bms/hlpdatabms4s.py b/etc/dbus-serialbattery/bms/hlpdatabms4s.py index 6a053753..9f33dc25 100644 --- a/etc/dbus-serialbattery/bms/hlpdatabms4s.py +++ b/etc/dbus-serialbattery/bms/hlpdatabms4s.py @@ -184,8 +184,6 @@ def manage_charge_current(self): def read_serial_data_HLPdataBMS4S(self, command, time, min_len): data = read_serial_data(command, self.port, self.baud_rate, time, min_len) - if data is False: - return False return data @@ -193,9 +191,7 @@ def read_serial_data(command, port, baud, time, min_len): try: with serial.Serial(port, baudrate=baud, timeout=0.5) as ser: ret = read_serialport_data(ser, command, time, min_len) - if ret is True: - return ret - return False + return ret except serial.SerialException as e: logger.error(e) From 605801a758405e1139f782ffeb014a401c23b69e Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Fri, 16 Jun 2023 12:41:35 +0200 Subject: [PATCH 050/114] Validate setting of FLOAT_CELL_VOLTAGE and avoid misconfiguration --- etc/dbus-serialbattery/utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 0e49af52..7b9fc235 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -55,6 +55,16 @@ def _get_list_from_config( MAX_CELL_VOLTAGE = float(config["DEFAULT"]["MAX_CELL_VOLTAGE"]) # Max voltage can seen as absorption voltage FLOAT_CELL_VOLTAGE = float(config["DEFAULT"]["FLOAT_CELL_VOLTAGE"]) +if FLOAT_CELL_VOLTAGE > MAX_CELL_VOLTAGE: + FLOAT_CELL_VOLTAGE = MAX_CELL_VOLTAGE + logger.error( + ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value greater than MAX_CELL_VOLTAGE. Please check the configuration." + ) +if FLOAT_CELL_VOLTAGE < MIN_CELL_VOLTAGE: + FLOAT_CELL_VOLTAGE = MIN_CELL_VOLTAGE + logger.error( + ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." + ) # --------- BMS disconnect behaviour --------- # Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the From abd69153a2445f8d6633dbad03a7c6308528a5b5 Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Fri, 16 Jun 2023 21:07:16 +0200 Subject: [PATCH 051/114] consider utils.LINEAR_RECALCULATION_EVERY to refresh CVL --- etc/dbus-serialbattery/battery.py | 32 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 01ef336c..14ab9b62 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -108,7 +108,6 @@ def init_values(self): self.min_battery_voltage = None self.allow_max_voltage = True self.max_voltage_start_time = None - self.transition_start_time = None self.control_voltage_at_transition_start = None self.charge_mode = None self.charge_limitation = None @@ -347,25 +346,28 @@ def manage_charge_voltage_linear(self) -> None: chargeMode = "Float" if self.control_voltage: if not self.charge_mode.startswith("Float"): - self.transition_start_time = int(time()) + self.linear_cvl_last_set = int(time()) self.initial_control_voltage = self.control_voltage chargeMode = "Float Transition" elif self.charge_mode.startswith("Float Transition"): - elapsed_time = int(time()) - self.transition_start_time + current_time = int(time()) + elapsed_time = current_time - self.linear_cvl_last_set # Voltage drop per second VOLTAGE_DROP_PER_SECOND = 0.01 / 10 - voltage_drop = min( - VOLTAGE_DROP_PER_SECOND * elapsed_time, - self.initial_control_voltage - floatVoltage, - ) - self.control_voltage = ( - self.initial_control_voltage - voltage_drop - ) - if self.control_voltage <= floatVoltage: - self.control_voltage = floatVoltage - chargeMode = "Float" - else: - chargeMode = "Float Transition" + if elapsed_time >= utils.LINEAR_RECALCULATION_EVERY: + voltage_drop = min( + VOLTAGE_DROP_PER_SECOND * elapsed_time, + self.initial_control_voltage - floatVoltage, + ) + self.control_voltage = ( + self.initial_control_voltage - voltage_drop + ) + self.linear_cvl_last_set = current_time + if self.control_voltage <= floatVoltage: + self.control_voltage = floatVoltage + chargeMode = "Float" + else: + chargeMode = "Float Transition" else: self.control_voltage = floatVoltage self.charge_mode = chargeMode From b4025b51552349273120e83dc27272f0e8b633df Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 17 Jun 2023 12:30:33 +0200 Subject: [PATCH 052/114] cleanup --- CHANGELOG.md | 2 +- etc/dbus-serialbattery/battery.py | 3 ++- etc/dbus-serialbattery/dbushelper.py | 26 -------------------------- etc/dbus-serialbattery/utils.py | 2 +- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1db8a136..96874f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel -* Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS +* Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich * Removed: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 232fbfbb..ed0ee732 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -354,7 +354,8 @@ def manage_charge_voltage_linear(self) -> None: elapsed_time = int(time()) - self.transition_start_time # Duration in seconds for smooth voltage drop from absorption to float # depending on the number of cells - FLOAT_MODE_TRANSITION_DURATION = self.cell_count * 12 + # 900 seconds / number of cells (mostly 16) = 56 + FLOAT_MODE_TRANSITION_DURATION = self.cell_count * 56 t = min(1, elapsed_time / FLOAT_MODE_TRANSITION_DURATION) self.control_voltage = ( 1 - t diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index b0a217d7..cec1148d 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -54,32 +54,6 @@ def setup_instance(self): 0, 0, ], - # 'CellVoltageMin': [path + '/CellVoltageMin', 2.8, 0.0, 5.0], - # 'CellVoltageMax': [path + '/CellVoltageMax', 3.45, 0.0, 5.0], - # 'CellVoltageFloat': [path + '/CellVoltageFloat', 3.35, 0.0, 5.0], - # 'VoltageMaxTime': [path + '/VoltageMaxTime', 900, 0, 0], - # 'VoltageResetSocLimit': [path + '/VoltageResetSocLimit', 90, 0, 100], - # 'MaxChargeCurrent': [path + '/MaxCurrentCharge', 5, 0.0, 500], - # 'MaxDischargeCurrent': [path + '/MaxCurrentDischarge', 7, 0.0, 500], - # 'AllowDynamicChargeCurrent': [path + '/AllowDynamicChargeCurrent', 1, 0, 1], - # 'AllowDynamicDischargeCurrent': [path + '/AllowDynamicDischargeCurrent', 1, 0, 1], - # 'AllowDynamicChargeVoltage': [path + '/AllowDynamicChargeVoltage', 0, 0, 1], - # 'SocLowWarning': [path + '/SocLowWarning', 20, 0, 100], - # 'SocLowAlarm': [path + '/SocLowAlarm', 10, 0, 100], - # 'Capacity': [path + '/Capacity', '', 0, 500], - # 'EnableInvertedCurrent': [path + '/EnableInvertedCurrent', 0, 0, 1], - # 'CCMSocLimitCharge1': [path + '/CCMSocLimitCharge1', 98, 0, 100], - # 'CCMSocLimitCharge2': [path + '/CCMSocLimitCharge2', 95, 0, 100], - # 'CCMSocLimitCharge3': [path + '/CCMSocLimitCharge3', 91, 0, 100], - # 'CCMSocLimitDischarge1': [path + '/CCMSocLimitDischarge1', 10, 0, 100], - # 'CCMSocLimitDischarge2': [path + '/CCMSocLimitDischarge2', 20, 0, 100], - # 'CCMSocLimitDischarge3': [path + '/CCMSocLimitDischarge3', 30, 0, 100], - # 'CCMCurrentLimitCharge1': [path + '/CCMCurrentLimitCharge1', 5, 0, 100], - # 'CCMCurrentLimitCharge2': [path + '/CCMCurrentLimitCharge2', '', 0, 100], - # 'CCMCurrentLimitCharge3': [path + '/CCMCurrentLimitCharge3', '', 0, 100], - # 'CCMCurrentLimitDischarge1': [path + '/CCMCurrentLimitDischarge1', 5, 0, 100], - # 'CCMCurrentLimitDischarge2': [path + '/CCMCurrentLimitDischarge2', '', 0, 100], - # 'CCMCurrentLimitDischarge3': [path + '/CCMCurrentLimitDischarge3', '', 0, 100], } self.settings = SettingsDevice(get_bus(), settings, self.handle_changed_setting) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index aec28640..a62e9f6e 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230613dev" +DRIVER_VERSION = "1.0.20230617dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 5f9225b63d69f6ce459a3ab56edb3e4190d822e0 Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Sat, 17 Jun 2023 15:08:05 +0200 Subject: [PATCH 053/114] consider utils.LINEAR_RECALCULATION_EVERY to refresh CVL --- etc/dbus-serialbattery/battery.py | 37 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 14ab9b62..c0c22a11 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -108,6 +108,7 @@ def init_values(self): self.min_battery_voltage = None self.allow_max_voltage = True self.max_voltage_start_time = None + self.transition_start_time = None self.control_voltage_at_transition_start = None self.charge_mode = None self.charge_limitation = None @@ -346,28 +347,24 @@ def manage_charge_voltage_linear(self) -> None: chargeMode = "Float" if self.control_voltage: if not self.charge_mode.startswith("Float"): - self.linear_cvl_last_set = int(time()) + self.transition_start_time = int(time()) self.initial_control_voltage = self.control_voltage chargeMode = "Float Transition" elif self.charge_mode.startswith("Float Transition"): current_time = int(time()) - elapsed_time = current_time - self.linear_cvl_last_set + elapsed_time = current_time - self.transition_start_time # Voltage drop per second VOLTAGE_DROP_PER_SECOND = 0.01 / 10 - if elapsed_time >= utils.LINEAR_RECALCULATION_EVERY: - voltage_drop = min( - VOLTAGE_DROP_PER_SECOND * elapsed_time, - self.initial_control_voltage - floatVoltage, - ) - self.control_voltage = ( - self.initial_control_voltage - voltage_drop - ) - self.linear_cvl_last_set = current_time - if self.control_voltage <= floatVoltage: - self.control_voltage = floatVoltage - chargeMode = "Float" - else: - chargeMode = "Float Transition" + voltage_drop = min( + VOLTAGE_DROP_PER_SECOND * elapsed_time, + self.initial_control_voltage - floatVoltage, + ) + self.set_cvl_linear(self.initial_control_voltage - voltage_drop) + if self.control_voltage <= floatVoltage: + self.control_voltage = floatVoltage + chargeMode = "Float" + else: + chargeMode = "Float Transition" else: self.control_voltage = floatVoltage self.charge_mode = chargeMode @@ -385,6 +382,14 @@ def manage_charge_voltage_linear(self) -> None: self.control_voltage = None self.charge_mode = "--" + def set_cvl_linear(self, control_voltage) -> bool: + current_time = int(time()) + if utils.LINEAR_RECALCULATION_EVERY <= current_time - self.linear_cvl_last_set: + self.control_voltage = control_voltage + self.linear_cvl_last_set = current_time + return True + return False + def manage_charge_voltage_step(self) -> None: """ manages the charge voltage using a step function by setting self.control_voltage From 5a5743b8e3c904fd92c611aeaa398a99d578bbe4 Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Sat, 17 Jun 2023 15:43:45 +0200 Subject: [PATCH 054/114] small refactor, introduced set_cvl_linear function to set CVL only once every LINEAR_RECALCULATION_EVERY seconds --- etc/dbus-serialbattery/battery.py | 32 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index c0c22a11..aaedb5d0 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -296,24 +296,18 @@ def manage_charge_voltage_linear(self) -> None: # INFO: battery will only switch to Absorption, if all cells are balanced. # Reach MAX_CELL_VOLTAGE * cell count if they are all balanced. if foundHighCellVoltage and self.allow_max_voltage: - # set CVL only once every LINEAR_RECALCULATION_EVERY seconds - if ( - int(time()) - self.linear_cvl_last_set - >= utils.LINEAR_RECALCULATION_EVERY - ): - self.linear_cvl_last_set = int(time()) - - # Keep penalty above min battery voltage and below max battery voltage - self.control_voltage = round( - min( - max( - voltageSum - penaltySum, - self.min_battery_voltage, - ), - self.max_battery_voltage, + # Keep penalty above min battery voltage and below max battery voltage + control_voltage = round( + min( + max( + voltageSum - penaltySum, + self.min_battery_voltage, ), - 3, - ) + self.max_battery_voltage, + ), + 3, + ) + self.set_cvl_linear(control_voltage) self.charge_mode = ( "Bulk dynamic" @@ -383,6 +377,10 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode = "--" def set_cvl_linear(self, control_voltage) -> bool: + """ + et CVL only once every LINEAR_RECALCULATION_EVERY seconds + :return: bool + """ current_time = int(time()) if utils.LINEAR_RECALCULATION_EVERY <= current_time - self.linear_cvl_last_set: self.control_voltage = control_voltage From f45fb41f5c71fd7ecab3679b1e61112f88178638 Mon Sep 17 00:00:00 2001 From: Oleg Gurevich Date: Sat, 17 Jun 2023 15:47:46 +0200 Subject: [PATCH 055/114] fix typo --- etc/dbus-serialbattery/battery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index aaedb5d0..c43b8360 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -378,7 +378,7 @@ def manage_charge_voltage_linear(self) -> None: def set_cvl_linear(self, control_voltage) -> bool: """ - et CVL only once every LINEAR_RECALCULATION_EVERY seconds + set CVL only once every LINEAR_RECALCULATION_EVERY seconds :return: bool """ current_time = int(time()) From 3dfeff1244d4d0d74de1d414e87f1fd8ae147985 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 17 Jun 2023 17:16:56 +0200 Subject: [PATCH 056/114] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96874f0e..dc057c94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Changed: Fix daly readsentence by @transistorgit * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel +* Changed: Fixed meaningless Time to Go values by @transistorgit * Changed: Fixed typo in `config.ini` sample by @hoschult * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved battery error handling on connection loss by @mr-manuel From 4b8be76ba09d9f014839de6916d116b209c3dc61 Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:43:39 +0000 Subject: [PATCH 057/114] remove debug msg --- etc/dbus-serialbattery/battery.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 24677317..de2d986e 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -291,9 +291,6 @@ def manage_charge_voltage_linear(self) -> None: if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP: self.max_voltage_start_time = None - logger.info(f"highCell {foundHighCellVoltage}, vDiff {voltageDiff}, vStartTime {self.max_voltage_start_time}, allowMaxV {self.allow_max_voltage}") - - # INFO: battery will only switch to Absorption, if all cells are balanced. # Reach MAX_CELL_VOLTAGE * cell count if they are all balanced. if foundHighCellVoltage and self.allow_max_voltage: From cc4ed9c62a52cb2736bd3b4c99569d8239c5c619 Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:45:51 +0000 Subject: [PATCH 058/114] remove debug msg --- etc/dbus-serialbattery/bms/daly.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 27de1ef5..1f67e7c8 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -186,9 +186,6 @@ def refresh_data(self): def update_soc(self, ser): if self.last_charge_mode is not None and self.charge_mode is not None: - #debug - if self.last_charge_mode != self.charge_mode: - logger.info(f"Switch charge mode from {self.last_charge_mode} to {self.charge_mode}") if not self.last_charge_mode.startswith( "Float" ) and self.charge_mode.startswith("Float"): From 12232d8fa0ac0d34cf96334ea2d55e8cc5cadf9f Mon Sep 17 00:00:00 2001 From: Bernd Stahlbock <6627385+transistorgit@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:01:53 +0000 Subject: [PATCH 059/114] undo debug change --- etc/dbus-serialbattery/service/log/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/service/log/run b/etc/dbus-serialbattery/service/log/run index d826d8ba..ca6196de 100755 --- a/etc/dbus-serialbattery/service/log/run +++ b/etc/dbus-serialbattery/service/log/run @@ -1,3 +1,3 @@ #!/bin/sh exec 2>&1 -exec multilog t s2500000 n4 /var/log/dbus-serialbattery.TTY +exec multilog t s25000 n4 /var/log/dbus-serialbattery.TTY From b384400537fddf1dc5550b3fc3071f07b281869c Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 20 Jun 2023 11:53:43 +0200 Subject: [PATCH 060/114] Daly BMS make auto reset soc configurable --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/daly.py | 3 ++- etc/dbus-serialbattery/config.default.ini | 5 +++++ etc/dbus-serialbattery/utils.py | 7 ++++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc057c94..c6a50ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v1.0.x * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel +* Added: Daly BMS: Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 1f67e7c8..d68eac78 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -175,7 +175,8 @@ def refresh_data(self): self.write_charge_discharge_mos(ser) - self.update_soc(ser) + if utils.AUTO_RESET_SOC: + self.update_soc(ser) except OSError: logger.warning("Couldn't open serial port") diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 96cd90f4..59dc3be0 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -200,6 +200,11 @@ EXCLUDED_DEVICES = ; /dev/ttyUSB0:My first battery,/dev/ttyUSB1:My second battery 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 +AUTO_RESET_SOC = True + ; Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = 1 diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 2a7a57a9..fb7a93fd 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230617dev" +DRIVER_VERSION = "1.0.20230620dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -298,6 +298,11 @@ 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 +AUTO_RESET_SOC = "True" == config["DEFAULT"]["AUTO_RESET_SOC"] + # Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"]) From 1fffdafcda0026c07b39bdb484ec8c1cff153668 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 25 Jun 2023 12:20:08 +0200 Subject: [PATCH 061/114] added debug and error information for CVL --- etc/dbus-serialbattery/battery.py | 79 +++++++++++++------ etc/dbus-serialbattery/config.default.ini | 12 ++- etc/dbus-serialbattery/dbushelper.py | 2 + .../qml/PageBatteryParameters.qml | 9 +++ etc/dbus-serialbattery/utils.py | 16 ++-- 5 files changed, 84 insertions(+), 34 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index d92500fd..4a6c7495 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -111,6 +111,7 @@ def init_values(self): self.transition_start_time = None self.control_voltage_at_transition_start = None self.charge_mode = None + self.charge_mode_debug = "" self.charge_limitation = None self.discharge_limitation = None self.linear_cvl_last_set = 0 @@ -250,6 +251,7 @@ def manage_charge_voltage_linear(self) -> None: voltageSum = 0 penaltySum = 0 tDiff = 0 + current_time = int(time()) try: # calculate battery sum @@ -273,7 +275,7 @@ def manage_charge_voltage_linear(self) -> None: and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL and self.allow_max_voltage ): - self.max_voltage_start_time = int(time()) + self.max_voltage_start_time = current_time # allow max voltage again, if cells are unbalanced or SoC threshold is reached elif ( @@ -281,13 +283,24 @@ def manage_charge_voltage_linear(self) -> None: or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT ) and not self.allow_max_voltage: self.allow_max_voltage = True + else: + pass + else: - tDiff = int(time()) - self.max_voltage_start_time - # if utils.MAX_VOLTAGE_TIME_SEC < tDiff: - # keep max voltage for 300 more seconds - if 300 < tDiff: + tDiff = current_time - self.max_voltage_start_time + # keep max voltage for MAX_VOLTAGE_TIME_SEC more seconds + if utils.MAX_VOLTAGE_TIME_SEC < tDiff: self.allow_max_voltage = False self.max_voltage_start_time = None + if self.soc <= utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT: + # write to log, that reset to float was not possible + logger.error( + f"Could not change to float voltage. Battery SoC ({self.soc}%) is lower" + + f" than SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT ({utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT}%)." + + " Please reset SoC manually or lower the SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT in the" + + ' "config.ini".' + ) + # we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode # regardless of whether we were in absorption mode or not if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP: @@ -311,20 +324,8 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode = ( "Bulk dynamic" - # + " (vS: " - # + str(round(voltageSum, 2)) - # + " - pS: " - # + str(round(penaltySum, 2)) - # + ")" if self.max_voltage_start_time is None else "Absorption dynamic" - # + "(vS: " - # + str(round(voltageSum, 2)) - # + " tDiff: " - # + str(tDiff) - # + " pS: " - # + str(round(penaltySum, 2)) - # + ")" ) elif self.allow_max_voltage: @@ -341,19 +342,20 @@ def manage_charge_voltage_linear(self) -> None: chargeMode = "Float" if self.control_voltage: if not self.charge_mode.startswith("Float"): - self.transition_start_time = int(time()) + self.transition_start_time = current_time self.initial_control_voltage = self.control_voltage chargeMode = "Float Transition" elif self.charge_mode.startswith("Float Transition"): - current_time = int(time()) elapsed_time = current_time - self.transition_start_time - # Voltage drop per second - VOLTAGE_DROP_PER_SECOND = 0.01 / 10 - voltage_drop = min( - VOLTAGE_DROP_PER_SECOND * elapsed_time, + # Voltage reduction per second + VOLTAGE_REDUCTION_PER_SECOND = 0.01 / 10 + voltage_reduction = min( + VOLTAGE_REDUCTION_PER_SECOND * elapsed_time, self.initial_control_voltage - floatVoltage, ) - self.set_cvl_linear(self.initial_control_voltage - voltage_drop) + self.set_cvl_linear( + self.initial_control_voltage - voltage_reduction + ) if self.control_voltage <= floatVoltage: self.control_voltage = floatVoltage chargeMode = "Float" @@ -372,6 +374,35 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode += " (Linear Mode)" + # uncomment for enabling debugging infos in GUI + """ + self.charge_mode_debug = ( + f"max_battery_voltage: {round(self.max_battery_voltage, 2)}V" + ) + self.charge_mode_debug += ( + f" - VOLTAGE_DROP: {round(utils.VOLTAGE_DROP, 2)}V" + ) + self.charge_mode_debug += f"\nvoltageSum: {round(voltageSum, 2)}V" + self.charge_mode_debug += f" • voltageDiff: {round(voltageDiff, 3)}V" + self.charge_mode_debug += ( + f"\ncontrol_voltage: {round(self.control_voltage, 2)}V" + ) + self.charge_mode_debug += f" • penaltySum: {round(penaltySum, 3)}V" + self.charge_mode_debug += f"\ntDiff: {tDiff}/{utils.MAX_VOLTAGE_TIME_SEC}" + self.charge_mode_debug += f" • SoC: {self.soc}%" + self.charge_mode_debug += ( + f" • Reset SoC: {utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT}%" + ) + self.charge_mode_debug += f"\nallow_max_voltage: {self.allow_max_voltage}" + self.charge_mode_debug += ( + f"\nmax_voltage_start_time: {self.max_voltage_start_time}" + ) + self.charge_mode_debug += f"\ncurrent_time: {current_time}" + self.charge_mode_debug += ( + f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}" + ) + """ + except TypeError: self.control_voltage = None self.charge_mode = "--" diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 59dc3be0..5466b1d3 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -76,11 +76,15 @@ CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL = 0.010 ; e.g. 3.2 V * 5 / 100 = 0.160 V CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = 0.080 -; -- CVL reset based on SoC option (step mode) -; Specify how long the max voltage should be kept, if reached then switch to float voltage +; -- CVL reset based on SoC option (step mode & linear mode) +; Specify how long the max voltage should be kept +; Step mode: If reached then switch to float voltage +; Linear mode: If cells are balanced keep max voltage for further MAX_VOLTAGE_TIME_SEC seconds MAX_VOLTAGE_TIME_SEC = 900 -; Specify SoC where CVL limit is reset to max voltage, if value gets below -SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90 +; Specify SoC where CVL limit is reset to max voltage +; Step mode: If SoC gets below +; Linear mode: If cells are unbalanced or if SoC gets below +SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 80 ; --------- Cell Voltage Current limitation (affecting CCL/DCL) --------- diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index cec1148d..12102411 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -133,6 +133,7 @@ def setup_vedbus(self): ) self._dbusservice.add_path("/Info/ChargeMode", None, writeable=True) + self._dbusservice.add_path("/Info/ChargeModeDebug", None, writeable=True) self._dbusservice.add_path("/Info/ChargeLimitation", None, writeable=True) self._dbusservice.add_path("/Info/DischargeLimitation", None, writeable=True) @@ -477,6 +478,7 @@ def publish_dbus(self): # Voltage and charge control info self._dbusservice["/Info/ChargeMode"] = self.battery.charge_mode + self._dbusservice["/Info/ChargeModeDebug"] = self.battery.charge_mode_debug self._dbusservice["/Info/ChargeLimitation"] = self.battery.charge_limitation self._dbusservice[ "/Info/DischargeLimitation" diff --git a/etc/dbus-serialbattery/qml/PageBatteryParameters.qml b/etc/dbus-serialbattery/qml/PageBatteryParameters.qml index b95161a3..c402e446 100644 --- a/etc/dbus-serialbattery/qml/PageBatteryParameters.qml +++ b/etc/dbus-serialbattery/qml/PageBatteryParameters.qml @@ -6,6 +6,8 @@ MbPage { property variant service + property VBusItem chargeModeDebug: VBusItem { bind: service.path("/Info/ChargeModeDebug") } + model: VisibleItemModel { MbItemValue { @@ -14,6 +16,13 @@ MbPage { show: item.valid } + // show debug informations + MbItemText { + text: chargeModeDebug.value + wrapMode: Text.WordWrap + show: chargeModeDebug.value != "" + } + MbItemValue { description: qsTr("Charge Voltage Limit (CVL)") item.bind: service.path("/Info/MaxChargeVoltage") diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index fb7a93fd..46771a25 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230620dev" +DRIVER_VERSION = "1.0.20230625dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -127,11 +127,15 @@ def _get_list_from_config( config["DEFAULT"]["CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT"] ) -# -- CVL Reset based on SoC option -# Specify how long the max voltage should be kept, if reached then switch to float voltage -MAX_VOLTAGE_TIME_SEC = float(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"]) -# Specify SoC where CVL limit is reset to max voltage, if value gets below -SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = float( +# -- CVL reset based on SoC option (step mode & linear mode) +# Specify how long the max voltage should be kept +# Step mode: If reached then switch to float voltage +# Linear mode: If cells are balanced keep max voltage for further MAX_VOLTAGE_TIME_SEC seconds +MAX_VOLTAGE_TIME_SEC = int(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"]) +# Specify SoC where CVL limit is reset to max voltage +# Step mode: If SoC gets below +# Linear mode: If cells are unbalanced or if SoC gets below +SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = int( config["DEFAULT"]["SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT"] ) From 975b5fc54a60eb1662fc339a5498c6ea6a32e06a Mon Sep 17 00:00:00 2001 From: Oleg Gurevich <50322596+ogurevich@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:10:04 +0200 Subject: [PATCH 062/114] fix proposal for #733 (#735) * Added: Tollerance to enter float voltage once the timer is triggered --- etc/dbus-serialbattery/battery.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index d92500fd..38f1aa62 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -250,7 +250,8 @@ def manage_charge_voltage_linear(self) -> None: voltageSum = 0 penaltySum = 0 tDiff = 0 - + # meassurment and variation tolerance in volts + measurementToleranceVariation = 0.022 try: # calculate battery sum for i in range(self.cell_count): @@ -290,7 +291,12 @@ def manage_charge_voltage_linear(self) -> None: self.max_voltage_start_time = None # we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode # regardless of whether we were in absorption mode or not - if voltageSum < self.max_battery_voltage - utils.VOLTAGE_DROP: + if ( + voltageSum + < self.max_battery_voltage + - utils.VOLTAGE_DROP + - measurementToleranceVariation + ): self.max_voltage_start_time = None # INFO: battery will only switch to Absorption, if all cells are balanced. From e9d6e550b12955189f310c71f2055e41a70dc408 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 29 Jun 2023 19:30:37 +0200 Subject: [PATCH 063/114] Add bulk voltage Load to bulk voltage every x days to reset the SoC to 100% for some BMS --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 86 +++++++++++++++++++---- etc/dbus-serialbattery/config.default.ini | 21 +++++- etc/dbus-serialbattery/utils.py | 33 ++++++++- 4 files changed, 124 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a50ae1..0b40ecd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 8080075d..349ae650 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -104,6 +104,9 @@ def init_values(self): self.cells: List[Cell] = [] self.control_charging = None self.control_voltage = None + self.bulk_requested = False + self.bulk_last_reached = 0 + self.bulk_battery_voltage = None self.max_battery_voltage = None self.min_battery_voltage = None self.allow_max_voltage = True @@ -239,8 +242,32 @@ def manage_charge_voltage(self) -> None: self.charge_mode = "Keep always max voltage" def prepare_voltage_management(self) -> None: - self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count + self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2) + self.max_battery_voltage = round(utils.MAX_CELL_VOLTAGE * self.cell_count, 2) + self.min_battery_voltage = round(utils.MIN_CELL_VOLTAGE * self.cell_count, 2) + + bulk_last_reached_days_ago = ( + 0 + if self.bulk_last_reached == 0 + else (((int(time()) - self.bulk_last_reached) / 60 / 60 / 24)) + ) + # set bulk_requested to True, if the days are over + # it gets set to False once the bulk voltage was reached once + if ( + utils.BULK_AFTER_DAYS is not False + and self.bulk_requested is not False + and self.allow_max_voltage + and ( + self.bulk_last_reached == 0 + or utils.BULK_AFTER_DAYS < bulk_last_reached_days_ago + ) + ): + logger.info( + f"set bulk_requested to True: first time or {utils.BULK_AFTER_DAYS}" + + f" < {round(bulk_last_reached_days_ago, 2)}" + ) + self.bulk_requested = True + self.max_battery_voltage = self.bulk_battery_voltage def manage_charge_voltage_linear(self) -> None: """ @@ -252,21 +279,32 @@ def manage_charge_voltage_linear(self) -> None: penaltySum = 0 tDiff = 0 current_time = int(time()) + # meassurment and variation tolerance in volts - measurementToleranceVariation = 0.022 - + measurementToleranceVariation = 0.025 + try: - # calculate battery sum + # calculate battery sum and check for cell overvoltage for i in range(self.cell_count): voltage = self.get_cell_voltage(i) if voltage: voltageSum += voltage # calculate penalty sum to prevent single cell overcharge by using current cell voltage - if voltage > utils.MAX_CELL_VOLTAGE: + if ( + self.max_battery_voltage != self.bulk_battery_voltage + and voltage > utils.MAX_CELL_VOLTAGE + ): # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second foundHighCellVoltage = True penaltySum += voltage - utils.MAX_CELL_VOLTAGE + elif ( + self.max_battery_voltage == self.bulk_battery_voltage + and voltage > utils.BULK_CELL_VOLTAGE + ): + # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second + foundHighCellVoltage = True + penaltySum += voltage - utils.BULK_CELL_VOLTAGE voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage() @@ -331,22 +369,30 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode = ( "Bulk dynamic" - if self.max_voltage_start_time is None + # if self.max_voltage_start_time is None # remove this line after testing + if self.max_battery_voltage == self.bulk_battery_voltage else "Absorption dynamic" ) elif self.allow_max_voltage: self.control_voltage = round(self.max_battery_voltage, 3) self.charge_mode = ( - # "Bulk" if self.max_voltage_start_time is None else "Absorption" "Bulk" - if self.max_voltage_start_time is None + # if self.max_voltage_start_time is None # remove this line after testing + if self.max_battery_voltage == self.bulk_battery_voltage else "Absorption" ) else: floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3) chargeMode = "Float" + # reset bulk when going into float + if self.bulk_requested: + logger.info("set bulk_requested to False") + self.bulk_requested = False + # IDEA: Save "bulk_last_reached" in the dbus path com.victronenergy.settings + # to make it restart persistent + self.bulk_last_reached = current_time if self.control_voltage: if not self.charge_mode.startswith("Float"): self.transition_start_time = current_time @@ -382,7 +428,7 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode += " (Linear Mode)" # uncomment for enabling debugging infos in GUI - """ + # """ self.charge_mode_debug = ( f"max_battery_voltage: {round(self.max_battery_voltage, 2)}V" ) @@ -408,7 +454,15 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode_debug += ( f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}" ) - """ + self.charge_mode_debug += "\nbulk_last_reached: " + str( + "Never" + if self.bulk_last_reached == 0 + else str( + round((current_time - self.bulk_last_reached) / 60 / 60 / 24, 2) + ) + + " days ago" + ) + # """ except TypeError: self.control_voltage = None @@ -433,6 +487,7 @@ def manage_charge_voltage_step(self) -> None: """ voltageSum = 0 tDiff = 0 + current_time = int(time()) try: # calculate battery sum @@ -448,7 +503,7 @@ def manage_charge_voltage_step(self) -> None: and self.allow_max_voltage ): # example 2 - self.max_voltage_start_time = time() + self.max_voltage_start_time = current_time # check if reset soc is greater than battery soc # this prevents flapping between max and float voltage @@ -464,7 +519,7 @@ def manage_charge_voltage_step(self) -> None: # timer started else: - tDiff = time() - self.max_voltage_start_time + tDiff = current_time - self.max_voltage_start_time if utils.MAX_VOLTAGE_TIME_SEC < tDiff: self.allow_max_voltage = False self.max_voltage_start_time = None @@ -481,6 +536,11 @@ def manage_charge_voltage_step(self) -> None: else: self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count self.charge_mode = "Float" + # reset bulk when going into float + if self.bulk_requested: + logger.info("set bulk_requested to False") + self.bulk_requested = False + self.bulk_last_reached = current_time self.charge_mode += " (Step Mode)" diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 5466b1d3..491dcb8e 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -8,10 +8,25 @@ MAX_BATTERY_DISCHARGE_CURRENT = 60.0 ; Description: Cell min/max voltages which are used to calculate the min/max battery voltage ; Example: 16 cells * 3.45V/cell = 55.2V max charge voltage. 16 cells * 2.90V = 46.4V min discharge voltage MIN_CELL_VOLTAGE = 2.900 -; Max voltage can seen as absorption voltage +; Max voltage (can seen as absorption voltage) MAX_CELL_VOLTAGE = 3.450 +; Float voltage (can be seen as resting voltage) FLOAT_CELL_VOLTAGE = 3.375 +; Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS) +; Has to be higher as the MAX_CELL_VOLTAGE +BULK_CELL_VOLTAGE = 3.650 +; Specify after how many days the bulk voltage should be reached again +; The timer is reset when the bulk voltage is reached +; Leave empty if you don't want to use this +; Example: Value is set to 15 +; day 1: bulk reached once +; day 16: bulk reached twice +; day 31: bulk not reached since it's very cloudy +; day 34: bulk reached since the sun came out +; day 49: bulk reached again, since last time it took 3 days to reach bulk voltage +BULK_AFTER_DAYS = + ; --------- Bluetooth BMS --------- ; Description: List the Bluetooth BMS here that you want to install ; -- Available Bluetooth BMS: @@ -135,7 +150,7 @@ CCCM_SOC_ENABLE = True ; Discharge current control management enable (True/False). DCCM_SOC_ENABLE = True -; Charge current soc limits +; Charge current SoC limits CC_SOC_LIMIT1 = 98 CC_SOC_LIMIT2 = 95 CC_SOC_LIMIT3 = 91 @@ -145,7 +160,7 @@ CC_CURRENT_LIMIT1_FRACTION = 0.1 CC_CURRENT_LIMIT2_FRACTION = 0.3 CC_CURRENT_LIMIT3_FRACTION = 0.5 -; Discharge current soc limits +; Discharge current SoC limits DC_SOC_LIMIT1 = 10 DC_SOC_LIMIT2 = 20 DC_SOC_LIMIT3 = 30 diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 46771a25..0c0c13f5 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230625dev" +DRIVER_VERSION = "1.0.20230629dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -66,6 +66,29 @@ def _get_list_from_config( ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) +# Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS) +# Has to be higher as the MAX_CELL_VOLTAGE +BULK_CELL_VOLTAGE = float(config["DEFAULT"]["BULK_CELL_VOLTAGE"]) +if BULK_CELL_VOLTAGE < MAX_CELL_VOLTAGE: + BULK_CELL_VOLTAGE = MAX_CELL_VOLTAGE + logger.error( + ">>> ERROR: BULK_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." + ) +# Specify after how many days the bulk voltage should be reached again +# The timer is reset when the bulk voltage is reached +# Leave empty if you don't want to use this +# Example: Value is set to 15 +# day 1: bulk reached once +# day 16: bulk reached twice +# day 31: bulk not reached since it's very cloudy +# day 34: bulk reached since the sun came out +# day 49: bulk reached again, since last time it took 3 days to reach bulk voltage +BULK_AFTER_DAYS = ( + int(config["DEFAULT"]["BULK_AFTER_DAYS"]) + if config["DEFAULT"]["BULK_AFTER_DAYS"] != "" + else False +) + # --------- BMS disconnect behaviour --------- # Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the # BMS on purpose, then you have to restart the driver/system to reset the block. @@ -157,6 +180,10 @@ def _get_list_from_config( CELL_VOLTAGES_WHILE_CHARGING = _get_list_from_config( "DEFAULT", "CELL_VOLTAGES_WHILE_CHARGING", lambda v: float(v) ) +if CELL_VOLTAGES_WHILE_CHARGING[0] < MAX_CELL_VOLTAGE: + logger.error( + ">>> ERROR: Maximum value of CELL_VOLTAGES_WHILE_CHARGING is set to a value lower than MAX_CELL_VOLTAGE. Please check the configuration." + ) MAX_CHARGE_CURRENT_CV = _get_list_from_config( "DEFAULT", "MAX_CHARGE_CURRENT_CV_FRACTION", @@ -166,6 +193,10 @@ def _get_list_from_config( CELL_VOLTAGES_WHILE_DISCHARGING = _get_list_from_config( "DEFAULT", "CELL_VOLTAGES_WHILE_DISCHARGING", lambda v: float(v) ) +if CELL_VOLTAGES_WHILE_DISCHARGING[0] > MIN_CELL_VOLTAGE: + logger.error( + ">>> ERROR: Minimum value of CELL_VOLTAGES_WHILE_DISCHARGING is set to a value greater than MIN_CELL_VOLTAGE. Please check the configuration." + ) MAX_DISCHARGE_CURRENT_CV = _get_list_from_config( "DEFAULT", "MAX_DISCHARGE_CURRENT_CV_FRACTION", From e7c3f0d5a958e2a0948dbc148c3f672e1ba547eb Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 29 Jun 2023 20:03:02 +0200 Subject: [PATCH 064/114] JKBMS disable high voltage warning on bulk reenable after bulk was completed --- etc/dbus-serialbattery/bms/jkbms.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index 8925d824..9061e7ba 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -127,7 +127,9 @@ def read_status_data(self): unpack_from(">H", self.get_data(status_data, b"\x99", offset, 2))[0] ) - # the JKBMS resets to 95% SoC, if all cell voltages are above or equal to 3.500 V + # the JKBMS resets to + # 95% SoC, if all cell voltages are above or equal to OVPR (Over Voltage Protection Recovery) + # 100% Soc, if all cell voltages are above or equal to OVP (Over Voltage Protection) offset = cellbyte_count + 18 self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0] @@ -279,7 +281,11 @@ def to_protection_bits(self, byte_data): # MOSFET temperature alarm self.protection.temp_high_internal = 2 if is_bit_set(tmp[pos - 1]) else 0 # charge over voltage alarm - self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0 + # TODO: check if "self.bulk_requested is False" works, + # else use "self.bulk_last_reached < int(time()) - (60 * 60)" + self.protection.voltage_high = ( + 2 if is_bit_set(tmp[pos - 2]) and self.bulk_requested is False else 0 + ) # discharge under voltage alarm self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0 # charge overcurrent alarm From 785e221006974593bc21883b41b45e5b9826a24c Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 4 Jul 2023 12:43:30 +0200 Subject: [PATCH 065/114] fixed error --- etc/dbus-serialbattery/battery.py | 29 +++++++++++++++++++---------- etc/dbus-serialbattery/utils.py | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 349ae650..ae3dd2fc 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -242,10 +242,6 @@ def manage_charge_voltage(self) -> None: self.charge_mode = "Keep always max voltage" def prepare_voltage_management(self) -> None: - self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2) - self.max_battery_voltage = round(utils.MAX_CELL_VOLTAGE * self.cell_count, 2) - self.min_battery_voltage = round(utils.MIN_CELL_VOLTAGE * self.cell_count, 2) - bulk_last_reached_days_ago = ( 0 if self.bulk_last_reached == 0 @@ -255,7 +251,7 @@ def prepare_voltage_management(self) -> None: # it gets set to False once the bulk voltage was reached once if ( utils.BULK_AFTER_DAYS is not False - and self.bulk_requested is not False + and self.bulk_requested is False and self.allow_max_voltage and ( self.bulk_last_reached == 0 @@ -263,11 +259,21 @@ def prepare_voltage_management(self) -> None: ) ): logger.info( - f"set bulk_requested to True: first time or {utils.BULK_AFTER_DAYS}" + f"set bulk_requested to True: first time (0) or {utils.BULK_AFTER_DAYS}" + f" < {round(bulk_last_reached_days_ago, 2)}" ) self.bulk_requested = True + + self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2) + + if self.bulk_requested: self.max_battery_voltage = self.bulk_battery_voltage + else: + self.max_battery_voltage = round( + utils.MAX_CELL_VOLTAGE * self.cell_count, 2 + ) + + self.min_battery_voltage = round(utils.MIN_CELL_VOLTAGE * self.cell_count, 2) def manage_charge_voltage_linear(self) -> None: """ @@ -454,13 +460,16 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode_debug += ( f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}" ) + bulk_days_ago = round( + (current_time - self.bulk_last_reached) / 60 / 60 / 24, 2 + ) self.charge_mode_debug += "\nbulk_last_reached: " + str( "Never" if self.bulk_last_reached == 0 - else str( - round((current_time - self.bulk_last_reached) / 60 / 60 / 24, 2) - ) - + " days ago" + else str(bulk_days_ago) + + " days ago - next in " + + str(utils.BULK_AFTER_DAYS - bulk_days_ago) + + "days" ) # """ diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 0c0c13f5..744514e4 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230629dev" +DRIVER_VERSION = "1.0.20230704dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 35f4009611cc0f6880fb81fd23acf4c99eae8a18 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 4 Jul 2023 12:44:12 +0200 Subject: [PATCH 066/114] disable high voltage warning for all BMS when charging to bulk voltage --- etc/dbus-serialbattery/bms/jkbms.py | 4 +--- etc/dbus-serialbattery/dbushelper.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index 9061e7ba..74b221b8 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -283,9 +283,7 @@ def to_protection_bits(self, byte_data): # charge over voltage alarm # TODO: check if "self.bulk_requested is False" works, # else use "self.bulk_last_reached < int(time()) - (60 * 60)" - self.protection.voltage_high = ( - 2 if is_bit_set(tmp[pos - 2]) and self.bulk_requested is False else 0 - ) + self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0 # discharge under voltage alarm self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0 # charge overcurrent alarm diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 12102411..1896bff6 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -2,7 +2,7 @@ import sys import os import platform -import dbus +import dbus # pyright: ignore[reportMissingImports] import traceback from time import time @@ -14,8 +14,10 @@ "/opt/victronenergy/dbus-systemcalc-py/ext/velib_python", ), ) -from vedbus import VeDbusService # noqa: E402 -from settingsdevice import SettingsDevice # noqa: E402 +from vedbus import VeDbusService # noqa: E402 # pyright: ignore[reportMissingImports] +from settingsdevice import ( # noqa: E402 # pyright: ignore[reportMissingImports] + SettingsDevice, +) from utils import logger, publish_config_variables # noqa: E402 import utils # noqa: E402 @@ -500,7 +502,15 @@ def publish_dbus(self): self._dbusservice[ "/Alarms/LowCellVoltage" ] = self.battery.protection.voltage_cell_low - self._dbusservice["/Alarms/HighVoltage"] = self.battery.protection.voltage_high + # disable high voltage warning temporarly, if loading to bulk voltage and bulk voltage reached is 30 minutes ago + self._dbusservice["/Alarms/HighVoltage"] = ( + self.battery.protection.voltage_high + if ( + self.bulk_requested is False + and self.bulk_last_reached < int(time()) - (60 * 30) + ) + else 0 + ) self._dbusservice["/Alarms/LowSoc"] = self.battery.protection.soc_low self._dbusservice[ "/Alarms/HighChargeCurrent" From ca3a3667d97484df3f0f8b1a1402a3f66b7617e9 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 11 Jul 2023 12:01:30 +0200 Subject: [PATCH 067/114] fix error and change default value measurementToleranceVariation from 0.025 to 0.5 else in OffGrid mode max voltage is always kept --- etc/dbus-serialbattery/battery.py | 5 +++-- etc/dbus-serialbattery/dbushelper.py | 4 ++-- etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index ae3dd2fc..af2f56ae 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -287,7 +287,7 @@ def manage_charge_voltage_linear(self) -> None: current_time = int(time()) # meassurment and variation tolerance in volts - measurementToleranceVariation = 0.025 + measurementToleranceVariation = 0.5 try: # calculate battery sum and check for cell overvoltage @@ -463,12 +463,13 @@ def manage_charge_voltage_linear(self) -> None: bulk_days_ago = round( (current_time - self.bulk_last_reached) / 60 / 60 / 24, 2 ) + bulk_in_days = round(utils.BULK_AFTER_DAYS - bulk_days_ago, 2) self.charge_mode_debug += "\nbulk_last_reached: " + str( "Never" if self.bulk_last_reached == 0 else str(bulk_days_ago) + " days ago - next in " - + str(utils.BULK_AFTER_DAYS - bulk_days_ago) + + str(bulk_in_days) + "days" ) # """ diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 1896bff6..a9878dec 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -506,8 +506,8 @@ def publish_dbus(self): self._dbusservice["/Alarms/HighVoltage"] = ( self.battery.protection.voltage_high if ( - self.bulk_requested is False - and self.bulk_last_reached < int(time()) - (60 * 30) + self.battery.bulk_requested is False + and self.battery.bulk_last_reached < int(time()) - (60 * 30) ) else 0 ) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 744514e4..696d0028 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230704dev" +DRIVER_VERSION = "1.0.20230711dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From a74223cc2968a9c2fd4ade02761e6ef326400a9d Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 17 Jul 2023 16:13:35 +0200 Subject: [PATCH 068/114] Added temperature names to dbus/mqtt --- CHANGELOG.md | 1 + etc/dbus-serialbattery/dbushelper.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b40ecd4..8df28974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel * Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel +* Added: Temperature names to dbus and mqtt by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix daly readsentence by @transistorgit diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index a9878dec..bca3b746 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -210,9 +210,13 @@ def setup_vedbus(self): self._dbusservice.add_path("/System/MaxTemperatureCellId", None, writeable=True) self._dbusservice.add_path("/System/MOSTemperature", None, writeable=True) self._dbusservice.add_path("/System/Temperature1", None, writeable=True) + self._dbusservice.add_path("/System/Temperature1Name", None, writeable=True) self._dbusservice.add_path("/System/Temperature2", None, writeable=True) + self._dbusservice.add_path("/System/Temperature2Name", None, writeable=True) self._dbusservice.add_path("/System/Temperature3", None, writeable=True) + self._dbusservice.add_path("/System/Temperature3Name", None, writeable=True) self._dbusservice.add_path("/System/Temperature4", None, writeable=True) + self._dbusservice.add_path("/System/Temperature4Name", None, writeable=True) self._dbusservice.add_path( "/System/MaxCellVoltage", None, @@ -463,9 +467,13 @@ def publish_dbus(self): ] = self.battery.get_max_temp_id() self._dbusservice["/System/MOSTemperature"] = self.battery.get_mos_temp() self._dbusservice["/System/Temperature1"] = self.battery.temp1 + self._dbusservice["/System/Temperature1Name"] = utils.TEMP_1_NAME self._dbusservice["/System/Temperature2"] = self.battery.temp2 + self._dbusservice["/System/Temperature2Name"] = utils.TEMP_2_NAME self._dbusservice["/System/Temperature3"] = self.battery.temp3 + self._dbusservice["/System/Temperature3Name"] = utils.TEMP_3_NAME self._dbusservice["/System/Temperature4"] = self.battery.temp4 + self._dbusservice["/System/Temperature4Name"] = utils.TEMP_4_NAME # Voltage control self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage From 6ece4474387240692e1d37d6d5fee92b582c2051 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 17 Jul 2023 17:08:35 +0200 Subject: [PATCH 069/114] Use current avg of last 300 cycles for TTG & TTS --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 2 ++ etc/dbus-serialbattery/dbushelper.py | 31 ++++++++++++++++++++++++---- etc/dbus-serialbattery/utils.py | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8df28974..0a15f695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel * Added: Temperature names to dbus and mqtt by @mr-manuel +* Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix daly readsentence by @transistorgit diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index af2f56ae..26140935 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -83,6 +83,8 @@ def init_values(self): """ self.voltage = None self.current = None + self.current_avg = None + self.current_avg_lst = [] self.capacity_remain = None self.capacity = None self.cycles = None diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index bca3b746..a564141e 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -582,6 +582,16 @@ def publish_dbus(self): # Update TimeToGo and/or TimeToSoC try: + # calculate current average for the last 300 cycles + # if Time-To-Go or Time-To-SoC is enabled + if utils.TIME_TO_GO_ENABLE or len(utils.TIME_TO_SOC_POINTS) > 0: + if self.battery.current is not None: + self.battery.current_avg_lst.append(self.battery.current) + + # delete oldest value + if len(self.battery.current_avg_lst) > 300: + del self.battery.current_avg_lst[0] + if ( self.battery.capacity is not None and (utils.TIME_TO_GO_ENABLE or len(utils.TIME_TO_SOC_POINTS) > 0) @@ -591,8 +601,15 @@ def publish_dbus(self): ) ): self.battery.time_to_soc_update = int(time()) + + self.battery.current_avg = round( + sum(self.battery.current_avg_lst) + / len(self.battery.current_avg_lst), + 2, + ) + crntPrctPerSec = ( - abs(self.battery.current / (self.battery.capacity / 100)) / 3600 + abs(self.battery.current_avg / (self.battery.capacity / 100)) / 3600 ) # Update TimeToGo item @@ -602,11 +619,17 @@ def publish_dbus(self): abs( int( self.battery.get_timeToSoc( - utils.SOC_LOW_WARNING, crntPrctPerSec, True + # switch value depending on charging/discharging + utils.SOC_LOW_WARNING + if self.battery.current_avg < 0 + else 100, + crntPrctPerSec, + True, ) ) ) - if self.battery.current and abs(self.battery.current) > 0.1 + if self.battery.current_avg + and abs(self.battery.current_avg) > 0.1 else None ) @@ -615,7 +638,7 @@ def publish_dbus(self): for num in utils.TIME_TO_SOC_POINTS: self._dbusservice["/TimeToSoC/" + str(num)] = ( self.battery.get_timeToSoc(num, crntPrctPerSec) - if self.battery.current + if self.battery.current_avg else None ) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 696d0028..73bab00a 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230711dev" +DRIVER_VERSION = "1.0.20230717dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From b914e8c8bd02984788c10acd69e2e85534236094 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 17 Jul 2023 17:09:30 +0200 Subject: [PATCH 070/114] Calculate only positive Time-to-SoC points --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a15f695..f4e73afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Added: Temperature names to dbus and mqtt by @mr-manuel * Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel +* Changed: Calculate only positive Time-to-SoC points by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix daly readsentence by @transistorgit * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 26140935..a89a4744 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -881,6 +881,14 @@ def get_timeToSoc(self, socnum, crntPrctPerSec, onlyNumber=False) -> str: else: diffSoc = self.soc - socnum + """ + calculate only positive SoC points, since negative points have no sense + when charging only points above current SoC are shown + when discharging only points below current SoC are shown + """ + if diffSoc < 0: + return None + ttgStr = None if self.soc != socnum and (diffSoc > 0 or utils.TIME_TO_SOC_INC_FROM is True): secondstogo = int(diffSoc / crntPrctPerSec) From af4ec01dc718bb072894465472df0fb4d1eb6b5f Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 23 Jul 2023 21:45:48 +0200 Subject: [PATCH 071/114] added current average of last 5 minutes --- CHANGELOG.md | 4 +- etc/dbus-serialbattery/battery.py | 8 +-- etc/dbus-serialbattery/dbushelper.py | 61 ++++++++++++++++------ etc/dbus-serialbattery/qml/PageBattery.qml | 6 +++ etc/dbus-serialbattery/utils.py | 2 +- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e73afc..1762738d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v1.0.x * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel +* Added: Current average of the last 5 minutes by @mr-manuel * Added: Daly BMS: Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 @@ -12,7 +13,6 @@ * Added: Temperature names to dbus and mqtt by @mr-manuel * Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel -* Changed: Calculate only positive Time-to-SoC points by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix daly readsentence by @transistorgit * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel @@ -25,6 +25,8 @@ * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich +* Changed: Time-to-Go and Time-to-SoC use the current average of the last 5 minutes for calculation by @mr-manuel +* Changed: Time-to-SoC calculate only positive points by @mr-manuel * Removed: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index a89a4744..b6111786 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -260,10 +260,12 @@ def prepare_voltage_management(self) -> None: or utils.BULK_AFTER_DAYS < bulk_last_reached_days_ago ) ): + """ logger.info( f"set bulk_requested to True: first time (0) or {utils.BULK_AFTER_DAYS}" + f" < {round(bulk_last_reached_days_ago, 2)}" ) + """ self.bulk_requested = True self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2) @@ -396,7 +398,7 @@ def manage_charge_voltage_linear(self) -> None: chargeMode = "Float" # reset bulk when going into float if self.bulk_requested: - logger.info("set bulk_requested to False") + # logger.info("set bulk_requested to False") self.bulk_requested = False # IDEA: Save "bulk_last_reached" in the dbus path com.victronenergy.settings # to make it restart persistent @@ -436,7 +438,7 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode += " (Linear Mode)" # uncomment for enabling debugging infos in GUI - # """ + """ self.charge_mode_debug = ( f"max_battery_voltage: {round(self.max_battery_voltage, 2)}V" ) @@ -550,7 +552,7 @@ def manage_charge_voltage_step(self) -> None: self.charge_mode = "Float" # reset bulk when going into float if self.bulk_requested: - logger.info("set bulk_requested to False") + # logger.info("set bulk_requested to False") self.bulk_requested = False self.bulk_last_reached = current_time diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index a564141e..3266665f 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -315,6 +315,12 @@ def setup_vedbus(self): # Create TimeToGo item if utils.TIME_TO_GO_ENABLE: self._dbusservice.add_path("/TimeToGo", None, writeable=True) + self._dbusservice.add_path( + "/CurrentAvg", + None, + writeable=True, + gettextcallback=lambda p, v: "{:0.2f}A".format(v), + ) # Create TimeToSoc items if len(utils.TIME_TO_SOC_POINTS) > 0: @@ -592,6 +598,20 @@ def publish_dbus(self): if len(self.battery.current_avg_lst) > 300: del self.battery.current_avg_lst[0] + """ + logger.info( + str(self.battery.capacity) + + " - " + + str(utils.TIME_TO_GO_ENABLE) + + " - " + + str(len(utils.TIME_TO_SOC_POINTS)) + + " - " + + str(int(time()) - self.battery.time_to_soc_update) + + " - " + + str(utils.TIME_TO_SOC_RECALCULATE_EVERY) + ) + """ + if ( self.battery.capacity is not None and (utils.TIME_TO_GO_ENABLE or len(utils.TIME_TO_SOC_POINTS) > 0) @@ -608,30 +628,35 @@ def publish_dbus(self): 2, ) + self._dbusservice["/CurrentAvg"] = self.battery.current_avg + crntPrctPerSec = ( abs(self.battery.current_avg / (self.battery.capacity / 100)) / 3600 ) # Update TimeToGo item if utils.TIME_TO_GO_ENABLE: - # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py - self._dbusservice["/TimeToGo"] = ( - abs( - int( - self.battery.get_timeToSoc( - # switch value depending on charging/discharging - utils.SOC_LOW_WARNING - if self.battery.current_avg < 0 - else 100, - crntPrctPerSec, - True, + if self.battery.current_avg is not None: + # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py + self._dbusservice["/TimeToGo"] = ( + abs( + int( + self.battery.get_timeToSoc( + # switch value depending on charging/discharging + utils.SOC_LOW_WARNING + if self.battery.current_avg < 0 + else 100, + crntPrctPerSec, + True, + ) ) ) + if self.battery.current_avg + and abs(self.battery.current_avg) > 0.1 + else None ) - if self.battery.current_avg - and abs(self.battery.current_avg) > 0.1 - else None - ) + else: + self._dbusservice["/TimeToGo"] = None # Update TimeToSoc items if len(utils.TIME_TO_SOC_POINTS) > 0: @@ -643,6 +668,12 @@ def publish_dbus(self): ) except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) pass if self.battery.soc is not None: diff --git a/etc/dbus-serialbattery/qml/PageBattery.qml b/etc/dbus-serialbattery/qml/PageBattery.qml index 286ce74c..ea7b44f5 100644 --- a/etc/dbus-serialbattery/qml/PageBattery.qml +++ b/etc/dbus-serialbattery/qml/PageBattery.qml @@ -94,6 +94,12 @@ MbPage { ] } + MbItemValue { + description: qsTr("Current (last 5 minutes avg.)") + item.bind: service.path("/CurrentAvg") + show: item.seen + } + MbItemValue { id: soc diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 73bab00a..081ee930 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230717dev" +DRIVER_VERSION = "1.0.20230723dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From e09658c9d97d58ceafcc2e9f2e95ea5c97f4b400 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 25 Jul 2023 17:01:27 +0200 Subject: [PATCH 072/114] make CCL and DCL more clear --- CHANGELOG.md | 1 + etc/dbus-serialbattery/battery.py | 66 +++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1762738d..6dbac3e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Changed: Make CCL and DCL limiting messages more clear by @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich * Changed: Time-to-Go and Time-to-SoC use the current average of the last 5 minutes for calculation by @mr-manuel * Changed: Time-to-SoC calculate only positive points by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index b6111786..a7feaa9d 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -564,17 +564,24 @@ def manage_charge_voltage_step(self) -> None: def manage_charge_current(self) -> None: # Manage Charge Current Limitations - charge_limits = {utils.MAX_BATTERY_CHARGE_CURRENT: "Config Limit"} + charge_limits = {utils.MAX_BATTERY_CHARGE_CURRENT: "Max Battery Charge Current"} - # if values are not the same, then the limit was read also from the BMS - if utils.MAX_BATTERY_CHARGE_CURRENT != self.max_battery_charge_current: - charge_limits.update({self.max_battery_charge_current: "BMS Limit"}) + # if BMS limit is lower then config limit and therefore the values are not the same, + # then the limit was also read from the BMS + if utils.MAX_BATTERY_CHARGE_CURRENT > self.max_battery_charge_current: + charge_limits.update({self.max_battery_charge_current: "BMS Settings"}) if utils.CCCM_CV_ENABLE: tmp = self.calcMaxChargeCurrentReferringToCellVoltage() if self.max_battery_charge_current != tmp: if tmp in charge_limits: - charge_limits.update({tmp: charge_limits[tmp] + ", Cell Voltage"}) + # do not add string, if global limitation is applied + if charge_limits["tmp"] != "Max Battery Charge Current": + charge_limits.update( + {tmp: charge_limits[tmp] + ", Cell Voltage"} + ) + else: + pass else: charge_limits.update({tmp: "Cell Voltage"}) @@ -582,7 +589,11 @@ def manage_charge_current(self) -> None: tmp = self.calcMaxChargeCurrentReferringToTemperature() if self.max_battery_charge_current != tmp: if tmp in charge_limits: - charge_limits.update({tmp: charge_limits[tmp] + ", Temp"}) + # do not add string, if global limitation is applied + if charge_limits["tmp"] != "Max Battery Charge Current": + charge_limits.update({tmp: charge_limits[tmp] + ", Temp"}) + else: + pass else: charge_limits.update({tmp: "Temp"}) @@ -590,7 +601,11 @@ def manage_charge_current(self) -> None: tmp = self.calcMaxChargeCurrentReferringToSoc() if self.max_battery_charge_current != tmp: if tmp in charge_limits: - charge_limits.update({tmp: charge_limits[tmp] + ", SoC"}) + # do not add string, if global limitation is applied + if charge_limits["tmp"] != "Max Battery Charge Current": + charge_limits.update({tmp: charge_limits[tmp] + ", SoC"}) + else: + pass else: charge_limits.update({tmp: "SoC"}) @@ -628,19 +643,28 @@ def manage_charge_current(self) -> None: ##### # Manage Discharge Current Limitations - discharge_limits = {utils.MAX_BATTERY_DISCHARGE_CURRENT: "Config Limit"} - - # if values are not the same, then the limit was read also from the BMS - if utils.MAX_BATTERY_DISCHARGE_CURRENT != self.max_battery_discharge_current: - discharge_limits.update({self.max_battery_discharge_current: "BMS Limit"}) + discharge_limits = { + utils.MAX_BATTERY_DISCHARGE_CURRENT: "Max Battery Discharge Current" + } + + # if BMS limit is lower then config limit and therefore the values are not the same, + # then the limit was also read from the BMS + if utils.MAX_BATTERY_DISCHARGE_CURRENT > self.max_battery_discharge_current: + discharge_limits.update( + {self.max_battery_discharge_current: "BMS Settings"} + ) if utils.DCCM_CV_ENABLE: tmp = self.calcMaxDischargeCurrentReferringToCellVoltage() if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: - discharge_limits.update( - {tmp: discharge_limits[tmp] + ", Cell Voltage"} - ) + # do not add string, if global limitation is applied + if charge_limits["tmp"] != "Max Battery Charge Current": + discharge_limits.update( + {tmp: discharge_limits[tmp] + ", Cell Voltage"} + ) + else: + pass else: discharge_limits.update({tmp: "Cell Voltage"}) @@ -648,7 +672,11 @@ def manage_charge_current(self) -> None: tmp = self.calcMaxDischargeCurrentReferringToTemperature() if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: - discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"}) + # do not add string, if global limitation is applied + if charge_limits["tmp"] != "Max Battery Charge Current": + discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"}) + else: + pass else: discharge_limits.update({tmp: "Temp"}) @@ -656,7 +684,11 @@ def manage_charge_current(self) -> None: tmp = self.calcMaxDischargeCurrentReferringToSoc() if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: - discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"}) + # do not add string, if global limitation is applied + if charge_limits["tmp"] != "Max Battery Charge Current": + discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"}) + else: + pass else: discharge_limits.update({tmp: "SoC"}) From ae4f21af69608fcb9a47bf896fc5e4c3c2a4480e Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 25 Jul 2023 17:24:39 +0200 Subject: [PATCH 073/114] fix small error --- etc/dbus-serialbattery/dbushelper.py | 36 ++++++++++++---------------- etc/dbus-serialbattery/utils.py | 2 +- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 3266665f..794c78e7 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -636,27 +636,21 @@ def publish_dbus(self): # Update TimeToGo item if utils.TIME_TO_GO_ENABLE: - if self.battery.current_avg is not None: - # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py - self._dbusservice["/TimeToGo"] = ( - abs( - int( - self.battery.get_timeToSoc( - # switch value depending on charging/discharging - utils.SOC_LOW_WARNING - if self.battery.current_avg < 0 - else 100, - crntPrctPerSec, - True, - ) - ) - ) - if self.battery.current_avg - and abs(self.battery.current_avg) > 0.1 - else None - ) - else: - self._dbusservice["/TimeToGo"] = None + # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py + time_to_go = self.battery.get_timeToSoc( + # switch value depending on charging/discharging + utils.SOC_LOW_WARNING if self.battery.current_avg < 0 else 100, + crntPrctPerSec, + True, + ) + + # Check that time_to_go is not None and current is not near zero + self._dbusservice["/TimeToGo"] = ( + abs(int(time_to_go)) + if time_to_go is not None + and abs(self.battery.current_avg) > 0.1 + else None + ) # Update TimeToSoc items if len(utils.TIME_TO_SOC_POINTS) > 0: diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 081ee930..f2d1699b 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230723dev" +DRIVER_VERSION = "1.0.20230725dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 697178b5a24af5cfcf7ae0cd20e8ae04d627dafc Mon Sep 17 00:00:00 2001 From: "Strawder, Paul" Date: Thu, 27 Jul 2023 14:11:35 +0200 Subject: [PATCH 074/114] bugfix: LLTJBD BMS SOC different in Xiaoxiang app and dbus-serialbattery --- etc/dbus-serialbattery/bms/lltjbd.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 63de584b..35d3b6fd 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -217,6 +217,7 @@ def __init__(self, port, baud, address): self.soc_to_set = None self.factory_mode = False self.writable = False + self.cycle_capacity = None # degree_sign = u'\N{DEGREE SIGN}' BATTERYTYPE = "LLT/JBD" @@ -253,6 +254,11 @@ def get_settings(self): self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT with self.eeprom(writable=False): + cycle_cap = self.read_serial_data_llt(readCmd(REG_CYCLE_CAP)) + if cycle_cap: + self.cycle_capacity = float( + unpack_from(">H", cycle_cap)[0] / 100.0 + ) charge_over_current = self.read_serial_data_llt(readCmd(REG_CHGOC)) if charge_over_current: self.max_battery_charge_current = float( @@ -402,7 +408,9 @@ def read_gen_data(self): ) = unpack_from(">HhHHHHhHHBBBBB", gen_data) self.voltage = voltage / 100 self.current = current / 100 - self.soc = round(100 * capacity_remain / capacity, 2) + if not self.cycle_capacity: + self.cycle_capacity = capacity + self.soc = round(100 * capacity_remain / self.cycle_capacity, 2) self.capacity_remain = capacity_remain / 100 self.capacity = capacity / 100 self.to_cell_bits(balance, balance2) From adeef9d55d6aeb2249dff9b3bde2737401749854 Mon Sep 17 00:00:00 2001 From: "Strawder, Paul" Date: Thu, 27 Jul 2023 14:15:36 +0200 Subject: [PATCH 075/114] black formatting --- etc/dbus-serialbattery/bms/lltjbd.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 35d3b6fd..b0cfa10d 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -256,9 +256,7 @@ def get_settings(self): with self.eeprom(writable=False): cycle_cap = self.read_serial_data_llt(readCmd(REG_CYCLE_CAP)) if cycle_cap: - self.cycle_capacity = float( - unpack_from(">H", cycle_cap)[0] / 100.0 - ) + self.cycle_capacity = float(unpack_from(">H", cycle_cap)[0] / 100.0) charge_over_current = self.read_serial_data_llt(readCmd(REG_CHGOC)) if charge_over_current: self.max_battery_charge_current = float( From a453f6848c18b7f5624a05290bebae3b2d53fcf1 Mon Sep 17 00:00:00 2001 From: Paul Strawder Date: Thu, 27 Jul 2023 16:23:03 +0200 Subject: [PATCH 076/114] JDB BMS - Control FETs for charge, discharge and disable / enable balancer (#761) * feature: Allow to control charge / discharge FET * feature: Allow to enable / disable balancer --- etc/dbus-serialbattery/bms/lltjbd.py | 147 ++++++++++++++++++++++- etc/dbus-serialbattery/bms/lltjbd_ble.py | 11 ++ 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index b0cfa10d..7bdcec5b 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -2,7 +2,7 @@ from battery import Protection, Battery, Cell from utils import is_bit_set, read_serial_data, logger import utils -from struct import unpack_from +from struct import unpack_from, pack import struct # Protocol registers @@ -68,8 +68,10 @@ REG_CELL_V_DELAYS = 0x3D REG_CHGOC_DELAYS = 0x3E REG_DSGOC_DELAYS = 0x3F -REG_GPSOFF = 0x40 -REG_GPSOFF_TIME = 0x41 +# Cut-off voltage turns off GPS protection board +REG_GPS_OFF = 0x40 +# Cut-off voltage delay for GPS protection board +REG_GPS_OFF_TIME = 0x41 REG_CAP_90 = 0x42 REG_CAP_70 = 0x43 REG_CAP_50 = 0x44 @@ -141,6 +143,27 @@ CMD_EXIT_FACTORY_MODE = b"\x00\x00" CMD_EXIT_AND_SAVE_FACTORY_MODE = b"\x28\x28" +# Weak current switch function +FUNC_SW_EN = 0x0001 # bit 0 +# Load lock function used to disconnect the load when short circuit is required to recover +FUNC_LOAD_EN = 0x0002 # bit 1 +# Enable balancer function +FUNC_BALANCE_EN = 0x0004 # bit 2 +# Charge balance, only turn on balance when charging +FUNC_BALANCE_CHARGING_ONLY = 0x0008 # bit 3 +# LED power indicator function +FUNC_LED = 0x0010 # bit 4 +# Compatible with LED modes +FUNC_LED_NUM = 0x0020 # bit 5 +# With history recording +FUNC_RTC = 0x0040 # bit 6 +# whether it is necessary to set the range when it is currently used for FCC update +FUNC_EDV = 0x0080 # bit 7 +# Additional GPS protection board is connected +FUNC_GPS_EN = 0x0100 # bit 8 +# Enable onboard buzzer / GPS protection board buzzer? +FUNC_BUZZER_EN = 0x0200 # bit 9 + def checksum(payload): return (0x10000 - sum(payload)) % 0x10000 @@ -217,6 +240,9 @@ def __init__(self, port, baud, address): self.soc_to_set = None self.factory_mode = False self.writable = False + self.trigger_force_disable_discharge = None + self.trigger_force_disable_charge = None + self.trigger_disable_balancer = None self.cycle_capacity = None # degree_sign = u'\N{DEGREE SIGN}' @@ -267,6 +293,9 @@ def get_settings(self): self.max_battery_discharge_current = float( unpack_from(">h", discharge_over_current)[0] / -100.0 ) + func_config = self.read_serial_data_llt(readCmd(REG_FUNC_CONFIG)) + if func_config: + self.func_config = func_config return True @@ -292,7 +321,119 @@ def write_soc(self): pack_voltage = struct.pack(">H", int(self.voltage * 10)) self.read_serial_data_llt(writeCmd(REG_CAP_100, pack_voltage)) + def force_charging_off_callback(self, path, value): + if value is None: + return False + + if value == 0: + self.trigger_force_disable_charge = False + return True + + if value == 1: + self.trigger_force_disable_charge = True + return True + + return False + + def force_discharging_off_callback(self, path, value): + if value is None: + return False + + if value == 0: + self.trigger_force_disable_discharge = False + return True + + if value == 1: + self.trigger_force_disable_discharge = True + return True + + return False + + def write_charge_discharge_mos(self): + if ( + self.trigger_force_disable_charge is None + and self.trigger_force_disable_discharge is None + ): + return False + + charge_disabled = 0 if self.charge_fet else 1 + if self.trigger_force_disable_charge is not None and self.control_allow_charge: + charge_disabled = 1 if self.trigger_force_disable_charge else 0 + logger.info( + f"write force disable charging: {'true' if self.trigger_force_disable_charge else 'false'}" + ) + self.trigger_force_disable_charge = None + + discharge_disabled = 0 if self.discharge_fet else 1 + if ( + self.trigger_force_disable_discharge is not None + and self.control_allow_discharge + ): + discharge_disabled = 1 if self.trigger_force_disable_discharge else 0 + logger.info( + f"write force disable discharging: {'true' if self.trigger_force_disable_discharge else 'false'}" + ) + self.trigger_force_disable_discharge = None + + mosdata = pack(">BB", 0, charge_disabled | (discharge_disabled << 1)) + + reply = self.read_serial_data_llt(writeCmd(REG_CTRL_MOSFET, mosdata)) + + if reply is False: + logger.error("write force disable charge/discharge failed") + return False + + def turn_balancing_off_callback(self, path, value): + if value is None: + return False + + if value == 0: + self.trigger_disable_balancer = False + return True + + if value == 1: + self.trigger_disable_balancer = True + return True + + return False + + def write_balancer(self): + if self.trigger_disable_balancer is None: + return False + + disable_balancer = self.trigger_disable_balancer + logger.info( + f"write disable balancer: {'true' if self.trigger_disable_balancer else 'false'}" + ) + self.trigger_disable_balancer = None + new_func_config = None + + with self.eeprom(): + func_config = self.read_serial_data_llt(readCmd(REG_FUNC_CONFIG)) + if func_config: + self.func_config = unpack_from(">H", func_config)[0] + balancer_enabled = self.func_config & FUNC_BALANCE_EN + # Balance is enabled, force disable OR balancer is disabled and force enable + if (balancer_enabled != 0 and disable_balancer) or ( + balancer_enabled == 0 and not disable_balancer + ): + new_func_config = self.func_config ^ FUNC_BALANCE_EN + + if new_func_config: + new_func_config_bytes = pack(">H", new_func_config) + with self.eeprom(writable=True): + reply = self.read_serial_data_llt( + writeCmd(REG_FUNC_CONFIG, new_func_config_bytes) + ) + if reply is False: + logger.error("write force disable balancer failed") + return False + + return True + def refresh_data(self): + self.write_charge_discharge_mos() + self.write_balancer() result = self.read_gen_data() result = result and self.read_cell_data() return result diff --git a/etc/dbus-serialbattery/bms/lltjbd_ble.py b/etc/dbus-serialbattery/bms/lltjbd_ble.py index de995492..27d7e927 100644 --- a/etc/dbus-serialbattery/bms/lltjbd_ble.py +++ b/etc/dbus-serialbattery/bms/lltjbd_ble.py @@ -179,5 +179,16 @@ def read_serial_data_llt(self, command): if not bat.test_connection(): logger.error(">>> ERROR: Unable to connect") else: + # Allow to change charge / discharge FET + bat.control_allow_charge = True + bat.control_allow_discharge = True + + bat.trigger_disable_balancer = True + bat.trigger_force_disable_charge = True + bat.trigger_force_disable_discharge = True + bat.refresh_data() + bat.trigger_disable_balancer = False + bat.trigger_force_disable_charge = False + bat.trigger_force_disable_discharge = False bat.refresh_data() bat.get_settings() From 1bf3b7ac87cff110b2b2d17a9f4b3c8a1aa8de2b Mon Sep 17 00:00:00 2001 From: Paul Strawder Date: Thu, 27 Jul 2023 16:33:07 +0200 Subject: [PATCH 077/114] bugfix: Cycle Capacity is in 10 mAh Fixes SoC with factor 100 * 100% percentage --- etc/dbus-serialbattery/bms/lltjbd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 7bdcec5b..3fcf5032 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -282,7 +282,7 @@ def get_settings(self): with self.eeprom(writable=False): cycle_cap = self.read_serial_data_llt(readCmd(REG_CYCLE_CAP)) if cycle_cap: - self.cycle_capacity = float(unpack_from(">H", cycle_cap)[0] / 100.0) + self.cycle_capacity = float(unpack_from(">H", cycle_cap)[0]) charge_over_current = self.read_serial_data_llt(readCmd(REG_CHGOC)) if charge_over_current: self.max_battery_charge_current = float( From b4b9552ca603683ca62eef7197f6dfb01b4791a9 Mon Sep 17 00:00:00 2001 From: Paul Strawder Date: Thu, 27 Jul 2023 20:48:27 +0200 Subject: [PATCH 078/114] JBD BMS show balancer state in GUI page IO (#763) --- etc/dbus-serialbattery/bms/lltjbd.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 3fcf5032..847b255b 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -295,7 +295,8 @@ def get_settings(self): ) func_config = self.read_serial_data_llt(readCmd(REG_FUNC_CONFIG)) if func_config: - self.func_config = func_config + self.func_config = unpack_from(">H", func_config)[0] + self.balance_fet = (self.func_config & FUNC_BALANCE_EN) != 0 return True @@ -428,6 +429,9 @@ def write_balancer(self): if reply is False: logger.error("write force disable balancer failed") return False + else: + self.func_config = new_func_config + self.balance_fet = (self.func_config & FUNC_BALANCE_EN) != 0 return True From c7d2ad1fec305d7bc5608db5b93607e207dcf08d Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 27 Aug 2023 19:45:51 +0200 Subject: [PATCH 079/114] Bump version --- etc/dbus-serialbattery/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index f2d1699b..34caa057 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230725dev" +DRIVER_VERSION = "1.0.20230827dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 4e93d6a3b709d29af0eeec1abb1415552d69a901 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 27 Aug 2023 19:56:25 +0200 Subject: [PATCH 080/114] Fix typos --- etc/dbus-serialbattery/battery.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index a7feaa9d..6bc6152d 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -659,7 +659,7 @@ def manage_charge_current(self) -> None: if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: # do not add string, if global limitation is applied - if charge_limits["tmp"] != "Max Battery Charge Current": + if discharge_limits["tmp"] != "Max Battery Discharge Current": discharge_limits.update( {tmp: discharge_limits[tmp] + ", Cell Voltage"} ) @@ -673,7 +673,7 @@ def manage_charge_current(self) -> None: if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: # do not add string, if global limitation is applied - if charge_limits["tmp"] != "Max Battery Charge Current": + if discharge_limits["tmp"] != "Max Battery Discharge Current": discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"}) else: pass @@ -685,7 +685,7 @@ def manage_charge_current(self) -> None: if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: # do not add string, if global limitation is applied - if charge_limits["tmp"] != "Max Battery Charge Current": + if discharge_limits["tmp"] != "Max Battery Discharge Current": discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"}) else: pass From 47cb3bae8b329c40960c046158e6f96de8d1724c Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 2 Sep 2023 21:42:28 +0200 Subject: [PATCH 081/114] Smaller fixes - fixes https://github.com/Louisvdw/dbus-serialbattery/issues/792#issuecomment-1703147692 --- etc/dbus-serialbattery/battery.py | 12 ++++++------ etc/dbus-serialbattery/config.default.ini | 4 ++-- etc/dbus-serialbattery/dbushelper.py | 2 +- etc/dbus-serialbattery/utils.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 6bc6152d..e3e73401 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -576,7 +576,7 @@ def manage_charge_current(self) -> None: if self.max_battery_charge_current != tmp: if tmp in charge_limits: # do not add string, if global limitation is applied - if charge_limits["tmp"] != "Max Battery Charge Current": + if charge_limits[tmp] != "Max Battery Charge Current": charge_limits.update( {tmp: charge_limits[tmp] + ", Cell Voltage"} ) @@ -590,7 +590,7 @@ def manage_charge_current(self) -> None: if self.max_battery_charge_current != tmp: if tmp in charge_limits: # do not add string, if global limitation is applied - if charge_limits["tmp"] != "Max Battery Charge Current": + if charge_limits[tmp] != "Max Battery Charge Current": charge_limits.update({tmp: charge_limits[tmp] + ", Temp"}) else: pass @@ -602,7 +602,7 @@ def manage_charge_current(self) -> None: if self.max_battery_charge_current != tmp: if tmp in charge_limits: # do not add string, if global limitation is applied - if charge_limits["tmp"] != "Max Battery Charge Current": + if charge_limits[tmp] != "Max Battery Charge Current": charge_limits.update({tmp: charge_limits[tmp] + ", SoC"}) else: pass @@ -659,7 +659,7 @@ def manage_charge_current(self) -> None: if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: # do not add string, if global limitation is applied - if discharge_limits["tmp"] != "Max Battery Discharge Current": + if discharge_limits[tmp] != "Max Battery Discharge Current": discharge_limits.update( {tmp: discharge_limits[tmp] + ", Cell Voltage"} ) @@ -673,7 +673,7 @@ def manage_charge_current(self) -> None: if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: # do not add string, if global limitation is applied - if discharge_limits["tmp"] != "Max Battery Discharge Current": + if discharge_limits[tmp] != "Max Battery Discharge Current": discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"}) else: pass @@ -685,7 +685,7 @@ def manage_charge_current(self) -> None: if self.max_battery_discharge_current != tmp: if tmp in discharge_limits: # do not add string, if global limitation is applied - if discharge_limits["tmp"] != "Max Battery Discharge Current": + if discharge_limits[tmp] != "Max Battery Discharge Current": discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"}) else: pass diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 491dcb8e..8bfd9ff8 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -75,11 +75,11 @@ LINEAR_RECALCULATION_ON_PERC_CHANGE = 5 ; it switches back to max voltage. ; Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to ; float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is -; once below 90% +; once below 80% ; OR ; The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float ; voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of -; 55.2V again if max cell difference is above 0.080V or SoC below 90%. +; 55.2V again if max cell difference is above 0.080V or SoC below 80%. ; Charge voltage control management enable (True/False). CVCM_ENABLE = True diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 794c78e7..d8d2b77b 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -635,7 +635,7 @@ def publish_dbus(self): ) # Update TimeToGo item - if utils.TIME_TO_GO_ENABLE: + if utils.TIME_TO_GO_ENABLE and crntPrctPerSec is not None: # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py time_to_go = self.battery.get_timeToSoc( # switch value depending on charging/discharging diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 34caa057..3e6cfa58 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future -DRIVER_VERSION = "1.0.20230827dev" +DRIVER_VERSION = "1.0.20230902dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From ff4c88617191e52a81c14d1130a9c98ab088936f Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 4 Sep 2023 11:56:57 +0200 Subject: [PATCH 082/114] Removed comments from utils.py This should make more clear that there are no values to change --- etc/dbus-serialbattery/utils.py | 172 +------------------------------- 1 file changed, 4 insertions(+), 168 deletions(-) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 3e6cfa58..4cc98732 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,11 +37,13 @@ def _get_list_from_config( ) -# Constants - Need to dynamically get them in future +# Constants DRIVER_VERSION = "1.0.20230902dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" +# save config values to constants + # --------- Battery Current limits --------- MAX_BATTERY_CHARGE_CURRENT = float(config["DEFAULT"]["MAX_BATTERY_CHARGE_CURRENT"]) MAX_BATTERY_DISCHARGE_CURRENT = float( @@ -49,11 +51,9 @@ def _get_list_from_config( ) # --------- Cell Voltages --------- -# Description: Cell min/max voltages which are used to calculate the min/max battery voltage -# Example: 16 cells * 3.45V/cell = 55.2V max charge voltage. 16 cells * 2.90V = 46.4V min discharge voltage MIN_CELL_VOLTAGE = float(config["DEFAULT"]["MIN_CELL_VOLTAGE"]) MAX_CELL_VOLTAGE = float(config["DEFAULT"]["MAX_CELL_VOLTAGE"]) -# Max voltage can seen as absorption voltage + FLOAT_CELL_VOLTAGE = float(config["DEFAULT"]["FLOAT_CELL_VOLTAGE"]) if FLOAT_CELL_VOLTAGE > MAX_CELL_VOLTAGE: FLOAT_CELL_VOLTAGE = MAX_CELL_VOLTAGE @@ -66,23 +66,12 @@ def _get_list_from_config( ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) -# Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS) -# Has to be higher as the MAX_CELL_VOLTAGE BULK_CELL_VOLTAGE = float(config["DEFAULT"]["BULK_CELL_VOLTAGE"]) if BULK_CELL_VOLTAGE < MAX_CELL_VOLTAGE: BULK_CELL_VOLTAGE = MAX_CELL_VOLTAGE logger.error( ">>> ERROR: BULK_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) -# Specify after how many days the bulk voltage should be reached again -# The timer is reset when the bulk voltage is reached -# Leave empty if you don't want to use this -# Example: Value is set to 15 -# day 1: bulk reached once -# day 16: bulk reached twice -# day 31: bulk not reached since it's very cloudy -# day 34: bulk reached since the sun came out -# day 49: bulk reached again, since last time it took 3 days to reach bulk voltage BULK_AFTER_DAYS = ( int(config["DEFAULT"]["BULK_AFTER_DAYS"]) if config["DEFAULT"]["BULK_AFTER_DAYS"] != "" @@ -90,93 +79,31 @@ def _get_list_from_config( ) # --------- BMS disconnect behaviour --------- -# Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the -# BMS on purpose, then you have to restart the driver/system to reset the block. -# False: Charge and discharge is not blocked on BMS communication loss -# True: Charge and discharge is blocked on BMS communication loss, it's unblocked when connection is established -# again or the driver/system is restarted BLOCK_ON_DISCONNECT = "True" == config["DEFAULT"]["BLOCK_ON_DISCONNECT"] # --------- Charge mode --------- -# Choose the mode for voltage / current limitations (True / False) -# False is a step mode: This is the default with limitations on hard boundary steps -# True is a linear mode: -# For CCL and DCL the values between the steps are calculated for smoother values (by WaldemarFech) -# For CVL max battery voltage is calculated dynamically in order that the max cell voltage is not exceeded LINEAR_LIMITATION_ENABLE = "True" == config["DEFAULT"]["LINEAR_LIMITATION_ENABLE"] - -# Specify in seconds how often the penalty should be recalculated LINEAR_RECALCULATION_EVERY = int(config["DEFAULT"]["LINEAR_RECALCULATION_EVERY"]) -# Specify in percent when the linear values should be recalculated immediately -# Example: 5 for a immediate change, when the value changes by more than 5% LINEAR_RECALCULATION_ON_PERC_CHANGE = int( config["DEFAULT"]["LINEAR_RECALCULATION_ON_PERC_CHANGE"] ) - # --------- Charge Voltage limitation (affecting CVL) --------- -# Description: Limit max charging voltage (MAX_CELL_VOLTAGE * cell count), switch from max voltage to float -# voltage (FLOAT_CELL_VOLTAGE * cell count) and back -# False: Max charging voltage is always kept -# True: Max charging voltage is reduced based on charge mode -# Step mode: After max voltage is reached for MAX_VOLTAGE_TIME_SEC it switches to float voltage. After -# SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT it switches back to max voltage. -# Linear mode: After max voltage is reachend and cell voltage difference is smaller or equal to -# CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL it switches to float voltage after 300 (fixed) -# additional seconds. -# After cell voltage difference is greater or equal to CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT -# OR -# SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT -# it switches back to max voltage. -# Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to -# float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is -# once below 90% -# OR -# The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float -# voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of -# 55.2V again if max cell difference is above 0.080V or SoC below 90%. -# Charge voltage control management enable (True/False). CVCM_ENABLE = "True" == config["DEFAULT"]["CVCM_ENABLE"] - -# -- CVL reset based on cell voltage diff (linear mode) -# Specify cell voltage diff where CVL limit is kept until diff is equal or lower CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL = float( config["DEFAULT"]["CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL"] ) -# Specify cell voltage diff where CVL limit is reset to max voltage, if value get above -# the cells are considered as imbalanced, if the cell diff exceeds 5% of the nominal cell voltage -# e.g. 3.2 V * 5 / 100 = 0.160 V CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = float( config["DEFAULT"]["CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT"] ) -# -- CVL reset based on SoC option (step mode & linear mode) -# Specify how long the max voltage should be kept -# Step mode: If reached then switch to float voltage -# Linear mode: If cells are balanced keep max voltage for further MAX_VOLTAGE_TIME_SEC seconds MAX_VOLTAGE_TIME_SEC = int(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"]) -# Specify SoC where CVL limit is reset to max voltage -# Step mode: If SoC gets below -# Linear mode: If cells are unbalanced or if SoC gets below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = int( config["DEFAULT"]["SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT"] ) - - -# --------- Cell Voltage Current limitation (affecting CCL/DCL) --------- -# Description: Maximal charge / discharge current will be in-/decreased depending on min and max cell voltages -# Example: 18 cells * 3.55V/cell = 63.9V max charge voltage -# 18 cells * 2.70V/cell = 48.6V min discharge voltage -# But in reality not all cells reach the same voltage at the same time. The (dis)charge current -# will be (in-/)decreased, if even ONE SINGLE BATTERY CELL reaches the limits - -# Charge current control management referring to cell-voltage enable (True/False). CCCM_CV_ENABLE = "True" == config["DEFAULT"]["CCCM_CV_ENABLE"] -# Discharge current control management referring to cell-voltage enable (True/False). DCCM_CV_ENABLE = "True" == config["DEFAULT"]["DCCM_CV_ENABLE"] -# Set steps to reduce battery current -# The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True CELL_VOLTAGES_WHILE_CHARGING = _get_list_from_config( "DEFAULT", "CELL_VOLTAGES_WHILE_CHARGING", lambda v: float(v) ) @@ -203,18 +130,10 @@ def _get_list_from_config( lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v), ) - # --------- Temperature limitation (affecting CCL/DCL) --------- -# Description: Maximal charge / discharge current will be in-/decreased depending on temperature -# Example: The temperature limit will be monitored to control the currents. If there are two temperature senors, -# then the worst case will be calculated and the more secure lower current will be set. -# Charge current control management referring to temperature enable (True/False). CCCM_T_ENABLE = "True" == config["DEFAULT"]["CCCM_T_ENABLE"] -# Charge current control management referring to temperature enable (True/False). DCCM_T_ENABLE = "True" == config["DEFAULT"]["DCCM_T_ENABLE"] -# Set steps to reduce battery current -# The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True TEMPERATURE_LIMITS_WHILE_CHARGING = _get_list_from_config( "DEFAULT", "TEMPERATURE_LIMITS_WHILE_CHARGING", lambda v: float(v) ) @@ -233,22 +152,14 @@ def _get_list_from_config( lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v), ) - # --------- SOC limitation (affecting CCL/DCL) --------- -# Description: Maximal charge / discharge current will be increased / decreased depending on State of Charge, -# see CC_SOC_LIMIT1 etc. -# Example: The SoC limit will be monitored to control the currents. -# Charge current control management enable (True/False). CCCM_SOC_ENABLE = "True" == config["DEFAULT"]["CCCM_SOC_ENABLE"] -# Discharge current control management enable (True/False). DCCM_SOC_ENABLE = "True" == config["DEFAULT"]["DCCM_SOC_ENABLE"] -# Charge current soc limits CC_SOC_LIMIT1 = float(config["DEFAULT"]["CC_SOC_LIMIT1"]) CC_SOC_LIMIT2 = float(config["DEFAULT"]["CC_SOC_LIMIT2"]) CC_SOC_LIMIT3 = float(config["DEFAULT"]["CC_SOC_LIMIT3"]) -# Charge current limits CC_CURRENT_LIMIT1 = MAX_BATTERY_CHARGE_CURRENT * float( config["DEFAULT"]["CC_CURRENT_LIMIT1_FRACTION"] ) @@ -259,12 +170,10 @@ def _get_list_from_config( config["DEFAULT"]["CC_CURRENT_LIMIT3_FRACTION"] ) -# Discharge current soc limits DC_SOC_LIMIT1 = float(config["DEFAULT"]["DC_SOC_LIMIT1"]) DC_SOC_LIMIT2 = float(config["DEFAULT"]["DC_SOC_LIMIT2"]) DC_SOC_LIMIT3 = float(config["DEFAULT"]["DC_SOC_LIMIT3"]) -# Discharge current limits DC_CURRENT_LIMIT1 = MAX_BATTERY_DISCHARGE_CURRENT * float( config["DEFAULT"]["DC_CURRENT_LIMIT1_FRACTION"] ) @@ -275,114 +184,53 @@ def _get_list_from_config( config["DEFAULT"]["DC_CURRENT_LIMIT3_FRACTION"] ) - # --------- Time-To-Go --------- -# Description: Calculates the time to go shown in the GUI TIME_TO_GO_ENABLE = "True" == config["DEFAULT"]["TIME_TO_GO_ENABLE"] # --------- Time-To-Soc --------- -# Description: Calculates the time to a specific SoC -# Example: TIME_TO_SOC_POINTS = 50, 25, 15, 0 -# 6h 24m remaining until 50% SoC -# 17h 36m remaining until 25% SoC -# 22h 5m remaining until 15% SoC -# 28h 48m remaining until 0% SoC -# Set of SoC percentages to report on dbus and MQTT. The more you specify the more it will impact system performance. -# [Valid values 0-100, comma separated list. More that 20 intervals are not recommended] -# Example: TIME_TO_SOC_POINTS = 100, 95, 90, 85, 75, 50, 25, 20, 10, 0 -# Leave empty to disable TIME_TO_SOC_POINTS = _get_list_from_config( "DEFAULT", "TIME_TO_SOC_POINTS", lambda v: int(v) ) -# Specify TimeToSoc value type [Valid values 1, 2, 3] -# 1 Seconds -# 2 Time string d h m s -# 3 Both seconds and time string " [d h m s]" TIME_TO_SOC_VALUE_TYPE = int(config["DEFAULT"]["TIME_TO_SOC_VALUE_TYPE"]) -# Specify in seconds how often the TimeToSoc should be recalculated -# Minimum are 5 seconds to prevent CPU overload TIME_TO_SOC_RECALCULATE_EVERY = ( int(config["DEFAULT"]["TIME_TO_SOC_RECALCULATE_EVERY"]) if int(config["DEFAULT"]["TIME_TO_SOC_RECALCULATE_EVERY"]) > 5 else 5 ) -# Include TimeToSoC points when moving away from the SoC point [Valid values True, False] -# These will be as negative time. Disabling this improves performance slightly TIME_TO_SOC_INC_FROM = "True" == config["DEFAULT"]["TIME_TO_SOC_INC_FROM"] - # --------- Additional settings --------- -# Specify one or more BMS types to load else leave empty to try to load all available -# -- Available BMS: -# Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos -# -- Available BMS, but disabled by default (just enter one or more below and it will be enabled): -# ANT, MNB, Sinowealth BMS_TYPE = _get_list_from_config("DEFAULT", "BMS_TYPE", lambda v: str(v)) -# Exclute this serial devices from the driver startup -# Example: /dev/ttyUSB2, /dev/ttyUSB4 EXCLUDED_DEVICES = _get_list_from_config( "DEFAULT", "EXCLUDED_DEVICES", lambda v: str(v) ) -# Enter custom battery names here or change it over the GUI -# Example: -# /dev/ttyUSB0:My first battery -# /dev/ttyUSB0:My first battery, /dev/ttyUSB1:My second battery CUSTOM_BATTERY_NAMES = _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 AUTO_RESET_SOC = "True" == config["DEFAULT"]["AUTO_RESET_SOC"] -# Publish the config settings to the dbus path "/Info/Config/" PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"]) -# Select the format of cell data presented on dbus [Valid values 0,1,2,3] -# 0 Do not publish all the cells (only the min/max cell data as used by the default GX) -# 1 Format: /Voltages/Cell (also available for display on Remote Console) -# 2 Format: /Cell/#/Volts -# 3 Both formats 1 and 2 BATTERY_CELL_DATA_FORMAT = int(config["DEFAULT"]["BATTERY_CELL_DATA_FORMAT"]) -# Simulate Midpoint graph (True/False). MIDPOINT_ENABLE = "True" == config["DEFAULT"]["MIDPOINT_ENABLE"] -# Battery temperature -# Specifiy how the battery temperature is assembled -# 0 Get mean of temp sensor 1 and temp sensor 2 -# 1 Get only temp from temp sensor 1 -# 2 Get only temp from temp sensor 2 TEMP_BATTERY = int(config["DEFAULT"]["TEMP_BATTERY"]) -# Temperature sensor 1 name TEMP_1_NAME = config["DEFAULT"]["TEMP_1_NAME"] - -# Temperature sensor 2 name TEMP_2_NAME = config["DEFAULT"]["TEMP_2_NAME"] - -# Temperature sensor 3 name TEMP_3_NAME = config["DEFAULT"]["TEMP_3_NAME"] - -# Temperature sensor 2 name TEMP_4_NAME = config["DEFAULT"]["TEMP_4_NAME"] - # --------- BMS specific settings --------- - -# -- LltJbd settings -# SoC low levels -# NOTE: SOC_LOW_WARNING is also used to calculate the Time-To-Go even if you are not using a LltJbd BMS SOC_LOW_WARNING = float(config["DEFAULT"]["SOC_LOW_WARNING"]) SOC_LOW_ALARM = float(config["DEFAULT"]["SOC_LOW_ALARM"]) # -- Daly settings -# Battery capacity (amps) if the BMS does not support reading it BATTERY_CAPACITY = float(config["DEFAULT"]["BATTERY_CAPACITY"]) -# Invert Battery Current. Default non-inverted. Set to -1 to invert INVERT_CURRENT_MEASUREMENT = int(config["DEFAULT"]["INVERT_CURRENT_MEASUREMENT"]) # -- ESC GreenMeter and Lipro device settings @@ -396,19 +244,7 @@ def _get_list_from_config( "DEFAULT", "HELTEC_MODBUS_ADDR", lambda v: int(v) ) - # --------- Battery monitor specific settings --------- -# If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported -# from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage, -# since max voltage is never reached. -# Example: -# cell count: 16 -# MAX_CELL_VOLTAGE = 3.45 -# max voltage calculated = 16 * 3.45 = 55.20 -# CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS -# now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures -# 55.05 V the max voltage is never reached for the driver and max voltage is kept forever. -# Set VOLTAGE_DROP to 0.15 VOLTAGE_DROP = float(config["DEFAULT"]["VOLTAGE_DROP"]) From 048db0a52ff1831f22379ed29c275b92efebfc59 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 4 Sep 2023 13:17:48 +0200 Subject: [PATCH 083/114] Updated changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dbac3e2..bfa909be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Current average of the last 5 minutes by @mr-manuel -* Added: Daly BMS: Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit +* Added: Daly BMS - Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Added: LLT/JBD BMS - Discharge / Charge Mosfet and disable / enable balancer switching over remote console/GUI with https://github.com/Louisvdw/dbus-serialbattery/pull/761 by @idstein +* Added: LLT/JBD BMS - Show balancer state in GUI under the IO page with https://github.com/Louisvdw/dbus-serialbattery/pull/763 by @idstein * Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel * Added: Temperature names to dbus and mqtt by @mr-manuel @@ -24,6 +26,8 @@ * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel +* Changed: LLT/JBD - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein +* Changed: LLT/JBD BMS - SOC different in Xiaoxiang app and dbus-serialbattery with https://github.com/Louisvdw/dbus-serialbattery/pull/760 by @idstein * Changed: Make CCL and DCL limiting messages more clear by @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich * Changed: Time-to-Go and Time-to-SoC use the current average of the last 5 minutes for calculation by @mr-manuel From 54055b18dedbeac791d59864b82cf4035b0585f3 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 4 Sep 2023 22:30:48 +0200 Subject: [PATCH 084/114] possible fix for LLT/JBS connection problems https://github.com/Louisvdw/dbus-serialbattery/issues/769 https://github.com/Louisvdw/dbus-serialbattery/issues/777 --- etc/dbus-serialbattery/bms/lltjbd_ble.py | 13 +++++++++---- etc/dbus-serialbattery/reinstall-local.sh | 5 +++++ etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd_ble.py b/etc/dbus-serialbattery/bms/lltjbd_ble.py index 27d7e927..65e1d4b7 100644 --- a/etc/dbus-serialbattery/bms/lltjbd_ble.py +++ b/etc/dbus-serialbattery/bms/lltjbd_ble.py @@ -3,6 +3,7 @@ import atexit import functools import threading +import sys from asyncio import CancelledError from typing import Union, Optional from utils import logger @@ -55,8 +56,14 @@ async def bt_main_loop(self): self.device = await BleakScanner.find_device_by_address( self.address, cb=dict(use_bdaddr=True) ) - except Exception as e: - logger.error(">>> ERROR: Bluetooth stack failed.", e) + + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) self.device = None await asyncio.sleep(0.5) @@ -173,8 +180,6 @@ def read_serial_data_llt(self, command): if __name__ == "__main__": - import sys - bat = LltJbd_Ble("Foo", -1, sys.argv[1]) if not bat.test_connection(): logger.error(">>> ERROR: Unable to connect") diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 48585e3e..4a9cd98d 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -175,6 +175,11 @@ if [ "$length" -gt 0 ]; then opkg update opkg install python3-misc python3-pip pip3 install bleak + # pip3 install bleak==0.20.2 + # pip3 install bleak==0.21.0 + pip3 install dbus-fast==1.87.0 + # pip3 install dbus-fast==1.87.3 + # pip3 install dbus-fast==1.87.4 echo "done." echo diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 4cc98732..8575c1a9 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230902dev" +DRIVER_VERSION = "1.0.20230904dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 1a22ea3bfa9d23a74038be04a967aee0df7a5c0d Mon Sep 17 00:00:00 2001 From: "Strawder, Paul" Date: Tue, 5 Sep 2023 11:18:28 +0200 Subject: [PATCH 085/114] bugfix: LLT/JBD BMS general packet data size check --- etc/dbus-serialbattery/bms/lltjbd.py | 24 ++++++++++++++++-------- requirements.txt | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 847b255b..0d1bbfd9 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -260,11 +260,14 @@ def test_connection(self): # Return True if success, False for failure result = False try: - result = self.get_settings() - # get first data to show in startup log, only if result is true - if result: + # 1) Read name of BMS + # 2) Try read BMS settings + # 3) Refresh general data + result = ( self.read_hardware_data() - self.refresh_data() + and self.get_settings() + and self.refresh_data() + ) except Exception as err: logger.error(f"Unexpected {err=}, {type(err)=}") result = False @@ -438,9 +441,7 @@ def write_balancer(self): def refresh_data(self): self.write_charge_discharge_mos() self.write_balancer() - result = self.read_gen_data() - result = result and self.read_cell_data() - return result + return self.read_gen_data() and self.read_cell_data() def to_protection_bits(self, byte_data): tmp = bin(byte_data)[2:].rjust(13, utils.zero_char) @@ -530,7 +531,7 @@ def to_fet_bits(self, byte_data): def read_gen_data(self): gen_data = self.read_serial_data_llt(self.command_general) # check if connect success - if gen_data is False or len(gen_data) < 27: + if gen_data is False or len(gen_data) < 23: return False ( @@ -567,6 +568,13 @@ def read_gen_data(self): # 0 = MOS, 1 = temp 1, 2 = temp 2 for t in range(self.temp_sensors): + if len(gen_data) < 23 + (2 * t) + 2: + logger.warn( + "Expected %d temperature sensors, but received only %d sensor readings!", + self.temp_sensors, + t, + ) + return True temp1 = unpack_from(">H", gen_data, 23 + (2 * t))[0] self.to_temp(t, utils.kelvin_to_celsius(temp1 / 10)) diff --git a/requirements.txt b/requirements.txt index 6de24eea..f5c4cddd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pyserial==3.5 minimalmodbus==2.0.1 -bleak==0.20.0 \ No newline at end of file +bleak==0.20.2 \ No newline at end of file From b2b465eb6a92ce0898ed3c032b14b0303f50a7b1 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 5 Sep 2023 15:14:34 +0200 Subject: [PATCH 086/114] improved reinstall and disable script --- CHANGELOG.md | 1 + etc/dbus-serialbattery/disable.sh | 14 +++++++++++--- etc/dbus-serialbattery/reinstall-local.sh | 19 +++++++++++++++---- etc/dbus-serialbattery/utils.py | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfa909be..cb9ac501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel * Changed: Improved battery error handling on connection loss by @mr-manuel * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich +* Changed: Improved driver disable script by @md-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel * Changed: LLT/JBD - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein diff --git a/etc/dbus-serialbattery/disable.sh b/etc/dbus-serialbattery/disable.sh index bd0c5d1c..13216cfe 100755 --- a/etc/dbus-serialbattery/disable.sh +++ b/etc/dbus-serialbattery/disable.sh @@ -8,6 +8,8 @@ bash /opt/victronenergy/swupdate-scripts/remount-rw.sh # remove driver from serial starter rm -f /data/conf/serial-starter.d/dbus-serialbattery.conf +# remove serial-starter.d if empty +rmdir /data/conf/serial-starter.d >/dev/null 2>&1 # kill serial starter, to reload changes pkill -f "/opt/victronenergy/serial-starter/serial-starter.sh" @@ -16,14 +18,20 @@ rm -rf /service/dbus-serialbattery.* rm -rf /service/dbus-blebattery.* # kill driver, if running -pkill -f "dbus-serialbattery" -pkill -f "dbus-blebattery" +# serial +pkill -f "supervise dbus-serialbattery.*" +pkill -f "multilog .* /var/log/dbus-serialbattery.*" +pkill -f "python .*/dbus-serialbattery.py /dev/tty.*" +# bluetooth +pkill -f "supervise dbus-blebattery.*" +pkill -f "multilog .* /var/log/dbus-blebattery.*" +pkill -f "python .*/dbus-serialbattery.py .*_Ble" # remove install script from rc.local sed -i "/bash \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local # remove cronjob -sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root +sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root >/dev/null 2>&1 ### needed for upgrading from older versions | start ### diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 4a9cd98d..f1e98645 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -123,6 +123,8 @@ if [ ! -f "$filename" ]; then fi # kill driver, if running. It gets restarted by the service daemon +pkill -f "supervise dbus-serialbattery.*" +pkill -f "multilog .* /var/log/dbus-serialbattery.*" pkill -f "python .*/dbus-serialbattery.py /dev/tty.*" @@ -168,15 +170,21 @@ if [ "$length" -gt 0 ]; then echo "Found $length Bluetooth BMS in the config file!" echo + /etc/init.d/bluetooth stop + echo + # install required packages # TO DO: Check first if packages are already installed echo "Installing required packages to use Bluetooth connection..." opkg update opkg install python3-misc python3-pip - pip3 install bleak - # pip3 install bleak==0.20.2 + + echo + pip3 install bleak==0.20.2 # pip3 install bleak==0.21.0 + + echo pip3 install dbus-fast==1.87.0 # pip3 install dbus-fast==1.87.3 # pip3 install dbus-fast==1.87.4 @@ -184,6 +192,9 @@ if [ "$length" -gt 0 ]; then echo "done." echo + /etc/init.d/bluetooth start + echo + # function to install ble battery install_blebattery_service() { if [ -z "$1" ]; then @@ -253,12 +264,12 @@ if [ "$length" -gt 0 ]; then # grep -qxF "5 0,12 * * * /etc/init.d/bluetooth restart" /var/spool/cron/root || echo "5 0,12 * * * /etc/init.d/bluetooth restart" >> /var/spool/cron/root # remove cronjob - sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root + sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root >/dev/null 2>&1 else # remove cronjob - sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root + sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root >/dev/null 2>&1 echo echo "No Bluetooth battery configuration found in \"/data/etc/dbus-serialbattery/config.ini\"." diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 8575c1a9..040c7643 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230904dev" +DRIVER_VERSION = "1.0.20230905dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 044f06667f89018e7a535935006ae34eaa254051 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 5 Sep 2023 16:41:06 +0200 Subject: [PATCH 087/114] LLT/JBD BMS - Improved error handling and automatical driver restart in case of error. Should fix: - https://github.com/Louisvdw/dbus-serialbattery/issues/730 - https://github.com/Louisvdw/dbus-serialbattery/issues/769 - https://github.com/Louisvdw/dbus-serialbattery/issues/777 --- CHANGELOG.md | 8 +- etc/dbus-serialbattery/bms/lltjbd_ble.py | 126 ++++++++++++++++++---- etc/dbus-serialbattery/reinstall-local.sh | 13 ++- requirements.txt | 3 +- 4 files changed, 126 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9ac501..e56b37c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * Added: Temperature names to dbus and mqtt by @mr-manuel * Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel +* Changed: Daly BMS - Fix readsentence by @transistorgit * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fix daly readsentence by @transistorgit * Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel @@ -26,11 +27,14 @@ * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich * Changed: Improved driver disable script by @md-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel -* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel -* Changed: LLT/JBD - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein +* Changed: JKBMS_BLE BMS - Improved driver by @seidler2547 & @mr-manuel +* Changed: LLT/JBD BMS - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein +* Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/778 with https://github.com/Louisvdw/dbus-serialbattery/pull/798 by @idstein +* Changed: LLT/JBD BMS - Improved error handling and automatical driver restart in case of error. Should fix https://github.com/Louisvdw/dbus-serialbattery/issues/730, https://github.com/Louisvdw/dbus-serialbattery/issues/769 and https://github.com/Louisvdw/dbus-serialbattery/issues/777 by @mr-manuel * Changed: LLT/JBD BMS - SOC different in Xiaoxiang app and dbus-serialbattery with https://github.com/Louisvdw/dbus-serialbattery/pull/760 by @idstein * Changed: Make CCL and DCL limiting messages more clear by @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich +* Changed: Sinowealth BMS - Fix not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel * Changed: Time-to-Go and Time-to-SoC use the current average of the last 5 minutes for calculation by @mr-manuel * Changed: Time-to-SoC calculate only positive points by @mr-manuel * Removed: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/lltjbd_ble.py b/etc/dbus-serialbattery/bms/lltjbd_ble.py index 65e1d4b7..449e7d0b 100644 --- a/etc/dbus-serialbattery/bms/lltjbd_ble.py +++ b/etc/dbus-serialbattery/bms/lltjbd_ble.py @@ -2,12 +2,15 @@ import asyncio import atexit import functools +import os import threading import sys from asyncio import CancelledError +from time import sleep from typing import Union, Optional from utils import logger from bleak import BleakClient, BleakScanner, BLEDevice +from bleak.exc import BleakDBusError from bms.lltjbd import LltJbdProtection, LltJbd BLE_SERVICE_UUID = "0000ff00-0000-1000-8000-00805f9b34fb" @@ -62,25 +65,66 @@ async def bt_main_loop(self): file = exception_traceback.tb_frame.f_code.co_filename line = exception_traceback.tb_lineno logger.error( - f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + f"BleakScanner(): Exception occurred: {repr(exception_object)} of type {exception_type} " + f"in {file} line #{line}" ) self.device = None await asyncio.sleep(0.5) + # allow the bluetooth connection to recover + sleep(5) if not self.device: self.run = False return - async with BleakClient( - self.device, disconnected_callback=self.on_disconnect - ) as client: - self.bt_client = client - self.bt_loop = asyncio.get_event_loop() - self.response_queue = asyncio.Queue() - self.ready_event.set() - while self.run and client.is_connected and self.main_thread.is_alive(): - await asyncio.sleep(0.1) - self.bt_loop = None + try: + async with BleakClient( + self.device, disconnected_callback=self.on_disconnect + ) as client: + self.bt_client = client + self.bt_loop = asyncio.get_event_loop() + self.response_queue = asyncio.Queue() + self.ready_event.set() + while self.run and client.is_connected and self.main_thread.is_alive(): + await asyncio.sleep(0.1) + self.bt_loop = None + + # Exception occurred: TimeoutError() of type + except asyncio.exceptions.TimeoutError: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"BleakClient(): asyncio.exceptions.TimeoutError: {repr(exception_object)} of type {exception_type} " + f"in {file} line #{line}" + ) + # needed? + self.run = False + return + + except TimeoutError: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"BleakClient(): TimeoutError: {repr(exception_object)} of type {exception_type} " + f"in {file} line #{line}" + ) + # needed? + self.run = False + return + + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"BleakClient(): Exception occurred: {repr(exception_object)} of type {exception_type} " + f"in {file} line #{line}" + ) + # needed? + self.run = False + return def background_loop(self): while self.run and self.main_thread.is_alive(): @@ -117,8 +161,13 @@ def test_connection(self): result = super().test_connection() if not result: logger.error("No BMS found at " + self.address) - except Exception as err: - logger.error(f"Unexpected {err=}, {type(err)=}") + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) result = False return result @@ -161,8 +210,23 @@ async def async_read_serial_data_llt(self, command): except asyncio.TimeoutError: logger.error(">>> ERROR: No reply - returning") return False - except Exception as e: - logger.error(">>> ERROR: No reply - returning", e) + except BleakDBusError: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"BleakDBusError: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) + self.reset_bluetooth() + return False + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) + self.reset_bluetooth() return False def read_serial_data_llt(self, command): @@ -172,12 +236,38 @@ def read_serial_data_llt(self, command): data = asyncio.run(self.async_read_serial_data_llt(command)) return self.validate_packet(data) except CancelledError as e: - logger.error(">>> ERROR: No reply - canceled - returning", e) + logger.error(">>> ERROR: No reply - canceled - returning") + logger.error(e) return False - except Exception as e: - logger.error(">>> ERROR: No reply - returning", e) + # except Exception as e: + # logger.error(">>> ERROR: No reply - returning") + # logger.error(e) + # return False + except Exception: + exception_type, exception_object, exception_traceback = sys.exc_info() + file = exception_traceback.tb_frame.f_code.co_filename + line = exception_traceback.tb_lineno + logger.error( + f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) return False + def reset_bluetooth(self): + logger.error("Reset of system Bluetooth daemon triggered") + self.bt_loop = False + + # process kill is needed, since the service/bluetooth driver is probably freezed + # os.system('pkill -f "bluetoothd"') + # stop will not work, if service/bluetooth driver is stuck + os.system("/etc/init.d/bluetooth stop") + sleep(2) + os.system("rfkill block bluetooth") + os.system("rfkill unblock bluetooth") + os.system("/etc/init.d/bluetooth start") + logger.error("System Bluetooth daemon should have been restarted") + sleep(5) + sys.exit(1) + if __name__ == "__main__": bat = LltJbd_Ble("Foo", -1, sys.argv[1]) diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index f1e98645..9e109e8a 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -181,9 +181,16 @@ if [ "$length" -gt 0 ]; then opkg install python3-misc python3-pip echo - pip3 install bleak==0.20.2 - # pip3 install bleak==0.21.0 - + pip3 install bleak + + # # ONLY FOR TESTING if there are version issues + # echo + # echo "Available bleak versions:" + # curl --silent https://api.github.com/repos/hbldh/bleak/releases | grep '"name": "v' | sed "s/ \"name\": \"v//g" | sed "s/\",//g" + # echo + # read -r -p "Specify the bleak version to install: " bleak_version + # pip3 install bleak=="$bleak_version" + # echo echo pip3 install dbus-fast==1.87.0 # pip3 install dbus-fast==1.87.3 diff --git a/requirements.txt b/requirements.txt index f5c4cddd..7a984a6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pyserial==3.5 minimalmodbus==2.0.1 -bleak==0.20.2 \ No newline at end of file +bleak==0.21.0 +dbus-fast==1.94.1 From 8e9a2eefb29b00ebd3a63c30cf18e4ec10a09c46 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 5 Sep 2023 16:41:35 +0200 Subject: [PATCH 088/114] Fixed Building wheel for dbus-fast won't finish on weak systems Fixes https://github.com/Louisvdw/dbus-serialbattery/issues/785 --- CHANGELOG.md | 3 +-- etc/dbus-serialbattery/reinstall-local.sh | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e56b37c5..16c5d936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,7 @@ * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: Daly BMS - Fix readsentence by @transistorgit * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel -* Changed: Fix daly readsentence by @transistorgit -* Changed: Fix Sinowealth not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel +* Changed: Fixed Building wheel for dbus-fast won't finish on weak systems https://github.com/Louisvdw/dbus-serialbattery/issues/785 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel * Changed: Fixed meaningless Time to Go values by @transistorgit * Changed: Fixed typo in `config.ini` sample by @hoschult diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 9e109e8a..7569fbb6 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -177,6 +177,12 @@ if [ "$length" -gt 0 ]; then # TO DO: Check first if packages are already installed echo "Installing required packages to use Bluetooth connection..." + # dbus-fast: skip compiling/building the wheel + # else weak system crash and are not able to install it, + # see https://github.com/Bluetooth-Devices/dbus-fast/issues/237 + # and https://github.com/Louisvdw/dbus-serialbattery/issues/785 + export SKIP_CYTHON=false + opkg update opkg install python3-misc python3-pip @@ -191,10 +197,13 @@ if [ "$length" -gt 0 ]; then # read -r -p "Specify the bleak version to install: " bleak_version # pip3 install bleak=="$bleak_version" # echo - echo - pip3 install dbus-fast==1.87.0 - # pip3 install dbus-fast==1.87.3 - # pip3 install dbus-fast==1.87.4 + # echo + # echo "Available dbus-fast versions:" + # curl --silent https://api.github.com/repos/Bluetooth-Devices/dbus-fast/releases | grep '"name": "v' | sed "s/ \"name\": \"v//g" | sed "s/\",//g" + # echo + # read -r -p "Specify the dbus-fast version to install: " dbus_fast_version + # pip3 install dbus-fast=="$dbus_fast_version" + # echo echo "done." echo From b1880f58593970f9174ecdfded1706aee249df57 Mon Sep 17 00:00:00 2001 From: Samuel Brucksch Date: Sun, 17 Sep 2023 11:29:30 +0200 Subject: [PATCH 089/114] Support for Daly CAN Bus (#169) * support for Daly CAN Bus * fix constructor args * revert port, needs fix * add can filters * comment logger Some changes are still needed to work with the latest version. They will follow in a next PR. --------- Co-authored-by: Samuel Brucksch Co-authored-by: Manuel --- etc/dbus-serialbattery/daly_can.py | 332 +++++++++++++++++++ etc/dbus-serialbattery/dbus-serialbattery.py | 4 + 2 files changed, 336 insertions(+) create mode 100644 etc/dbus-serialbattery/daly_can.py diff --git a/etc/dbus-serialbattery/daly_can.py b/etc/dbus-serialbattery/daly_can.py new file mode 100644 index 00000000..e19b1a7e --- /dev/null +++ b/etc/dbus-serialbattery/daly_can.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function, unicode_literals +from battery import Protection, Battery, Cell +from utils import * +from struct import * +import can + +class DalyCAN(Battery): + + def __init__(self,port,baud): + super(DalyCAN, self).__init__(port,baud) + self.charger_connected = None + self.load_connected = None + self.cell_min_voltage = None + self.cell_max_voltage = None + self.cell_min_no = None + self.cell_max_no = None + self.poll_interval = 1000 + self.poll_step = 0 + self.type = self.BATTERYTYPE + self.bus = None + # command bytes [Priority=18][Command=94][BMS ID=01][Uplink ID=40] + command_base = 0x18940140 + command_soc = 0x18900140 + command_minmax_cell_volts = 0x18910140 + command_minmax_temp = 0x18920140 + command_fet = 0x18930140 + command_status = 0x18940140 + command_cell_volts = 0x18950140 + command_temp = 0x18960140 + command_cell_balance = 0x18970140 + command_alarm = 0x18980140 + + response_base = 0x18944001 + response_soc = 0x18904001 + response_minmax_cell_volts = 0x18914001 + response_minmax_temp = 0x18924001 + response_fet = 0x18934001 + response_status = 0x18944001 + response_cell_volts = 0x18954001 + response_temp = 0x18964001 + response_cell_balance = 0x18974001 + response_alarm = 0x18984001 + + BATTERYTYPE = "Daly_CAN" + LENGTH_CHECK = 4 + LENGTH_POS = 3 + CURRENT_ZERO_CONSTANT = 30000 + TEMP_ZERO_CONSTANT = 40 + + def test_connection(self): + result = False + + # TODO handle errors? + can_filters = [{"can_id":self.response_base, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_soc, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_minmax_cell_volts, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_minmax_temp, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_fet, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_status, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_cell_volts, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_temp, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_cell_balance, "can_mask": 0xFFFFFFF}, + {"can_id":self.response_alarm, "can_mask": 0xFFFFFFF}] + self.bus = can.Bus(interface='socketcan', + channel='can0', + receive_own_messages=False, + can_filters=can_filters) + + result = self.read_status_data(self.bus) + + return result + + def get_settings(self): + self.capacity = BATTERY_CAPACITY + self.max_battery_current = MAX_BATTERY_CURRENT + self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + return True + + def refresh_data(self): + result = False + + result = self.read_soc_data(self.bus) + result = result and self.read_fed_data(self.bus) + if self.poll_step == 0: + # This must be listed in step 0 as get_min_cell_voltage and get_max_cell_voltage in battery.py needs it at first cycle for publish_dbus in dbushelper.py + result = result and self.read_cell_voltage_range_data(self.bus) + elif self.poll_step == 1: + result = result and self.read_alarm_data(self.bus) + elif self.poll_step == 2: + result = result and self.read_cells_volts(self.bus) + elif self.poll_step == 3: + result = result and self.read_temperature_range_data(self.bus) + #else: # A placeholder to remind this is the last step. Add any additional steps before here + # This is last step so reset poll_step + self.poll_step = -1 + + self.poll_step += 1 + + return result + + def read_status_data(self, bus): + status_data = self.read_bus_data_daly(bus, self.command_status) + # check if connection success + if status_data is False: + logger.debug("read_status_data") + return False + + self.cell_count, self.temp_sensors, self.charger_connected, self.load_connected, \ + state, self.cycles = unpack_from('>bb??bhx', status_data) + + self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + + self.hardware_version = "DalyBMS " + str(self.cell_count) + " cells" + logger.info(self.hardware_version) + return True + + def read_soc_data(self, ser): + # Ensure data received is valid + crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1) + crntMaxValid = (MAX_BATTERY_CURRENT * 1.3) + triesValid = 2 + while triesValid > 0: + soc_data = self.read_bus_data_daly(ser, self.command_soc) + # check if connection success + if soc_data is False: + return False + + voltage, tmp, current, soc = unpack_from('>hhhh', soc_data) + current = ((current - self.CURRENT_ZERO_CONSTANT) / -10 * INVERT_CURRENT_MEASUREMENT) + #logger.info("voltage: " + str(voltage) + ", current: " + str(current) + ", soc: " + str(soc)) + if crntMinValid < current < crntMaxValid: + self.voltage = (voltage / 10) + self.current = current + self.soc = (soc / 10) + return True + + logger.warning("read_soc_data - triesValid " + str(triesValid)) + triesValid -= 1 + + return False + + def read_alarm_data(self, ser): + alarm_data = self.read_bus_data_daly(ser, self.command_alarm) + # check if connection success + if alarm_data is False: + logger.warning("read_alarm_data") + return False + + al_volt, al_temp, al_crnt_soc, al_diff, \ + al_mos, al_misc1, al_misc2, al_fault = unpack_from('>bbbbbbbb', alarm_data) + + if al_volt & 48: + # High voltage levels - Alarm + self.voltage_high = 2 + elif al_volt & 15: + # High voltage Warning levels - Pre-alarm + self.voltage_high = 1 + else: + self.voltage_high = 0 + + if al_volt & 128: + # Low voltage level - Alarm + self.voltage_low = 2 + elif al_volt & 64: + # Low voltage Warning level - Pre-alarm + self.voltage_low = 1 + else: + self.voltage_low = 0 + + if al_temp & 2: + # High charge temp - Alarm + self.temp_high_charge = 2 + elif al_temp & 1: + # High charge temp - Pre-alarm + self.temp_high_charge = 1 + else: + self.temp_high_charge = 0 + + if al_temp & 8: + # Low charge temp - Alarm + self.temp_low_charge = 2 + elif al_temp & 4: + # Low charge temp - Pre-alarm + self.temp_low_charge = 1 + else: + self.temp_low_charge = 0 + + + if al_temp & 32: + # High discharge temp - Alarm + self.temp_high_discharge = 2 + elif al_temp & 16: + # High discharge temp - Pre-alarm + self.temp_high_discharge = 1 + else: + self.temp_high_discharge = 0 + + if al_temp & 128: + # Low discharge temp - Alarm + self.temp_low_discharge = 2 + elif al_temp & 64: + # Low discharge temp - Pre-alarm + self.temp_low_discharge = 1 + else: + self.temp_low_discharge = 0 + + #if al_crnt_soc & 2: + # # High charge current - Alarm + # self.current_over = 2 + #elif al_crnt_soc & 1: + # # High charge current - Pre-alarm + # self.current_over = 1 + #else: + # self.current_over = 0 + + #if al_crnt_soc & 8: + # # High discharge current - Alarm + # self.current_over = 2 + #elif al_crnt_soc & 4: + # # High discharge current - Pre-alarm + # self.current_over = 1 + #else: + # self.current_over = 0 + + if al_crnt_soc & 2 or al_crnt_soc & 8: + # High charge/discharge current - Alarm + self.current_over = 2 + elif al_crnt_soc & 1 or al_crnt_soc & 4: + # High charge/discharge current - Pre-alarm + self.current_over = 1 + else: + self.current_over = 0 + + if al_crnt_soc & 128: + # Low SoC - Alarm + self.soc_low = 2 + elif al_crnt_soc & 64: + # Low SoC Warning level - Pre-alarm + self.soc_low = 1 + else: + self.soc_low = 0 + + return True + + def read_cells_volts(self, bus): + if self.cell_count is not None: + cells_volts_data = self.read_bus_data_daly(bus, self.command_cell_volts, 6) + if cells_volts_data is False: + logger.warning("read_cells_volts") + return False + + frameCell = [0, 0, 0] + lowMin = (MIN_CELL_VOLTAGE / 2) + frame = 0 + bufIdx = 0 + + if len(self.cells) != self.cell_count: + # init the numbers of cells + self.cells = [] + for idx in range(self.cell_count): + self.cells.append(Cell(True)) + + while bufIdx < len(cells_volts_data): + frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from('>Bhhh', cells_volts_data, bufIdx) + for idx in range(3): + cellnum = ((frame - 1) * 3) + idx # daly is 1 based, driver 0 based + if cellnum >= self.cell_count: + break + cellVoltage = frameCell[idx] / 1000 + self.cells[cellnum].voltage = None if cellVoltage < lowMin else cellVoltage + bufIdx += 8 + + return True + + def read_cell_voltage_range_data(self, ser): + minmax_data = self.read_bus_data_daly(ser, self.command_minmax_cell_volts) + # check if connection success + if minmax_data is False: + logger.warning("read_cell_voltage_range_data") + return False + + cell_max_voltage,self.cell_max_no,cell_min_voltage, self.cell_min_no = unpack_from('>hbhb', minmax_data) + # Daly cells numbers are 1 based and not 0 based + self.cell_min_no -= 1 + self.cell_max_no -= 1 + # Voltage is returned in mV + self.cell_max_voltage = cell_max_voltage / 1000 + self.cell_min_voltage = cell_min_voltage / 1000 + return True + + def read_temperature_range_data(self, ser): + minmax_data = self.read_bus_data_daly(ser, self.command_minmax_temp) + # check if connection success + if minmax_data is False: + logger.debug("read_temperature_range_data") + return False + + max_temp,max_no,min_temp, min_no = unpack_from('>bbbb', minmax_data) + self.temp1 = min_temp - self.TEMP_ZERO_CONSTANT + self.temp2 = max_temp - self.TEMP_ZERO_CONSTANT + return True + + def read_fed_data(self, ser): + fed_data = self.read_bus_data_daly(ser, self.command_fet) + # check if connection success + if fed_data is False: + logger.debug("read_fed_data") + return False + + status, self.charge_fet, self.discharge_fet, bms_cycles, capacity_remain = unpack_from('>b??BL', fed_data) + self.capacity_remain = capacity_remain / 1000 + return True + + def read_bus_data_daly(self, bus, command, expectedMessageCount = 1): + # TODO handling of error cases + message = can.Message(arbitration_id=command) + bus.send(message, timeout=0.2) + response = bytearray() + + # TODO use async notifier instead of this where we expect a specific frame to be received + # this could end up in a deadlock if a package is not received + count = 0 + for msg in bus: + # print(f"{msg.arbitration_id:X}: {msg.data}") + # logger.info('Frame: ' + ", ".join(hex(b) for b in msg.data)) + response.extend(msg.data) + count += 1 + if count == expectedMessageCount: + break + return response \ No newline at end of file diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 72c3234c..b8f8f284 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -34,6 +34,8 @@ # enabled only if explicitly set in config under "BMS_TYPE" if "ANT" in utils.BMS_TYPE: from bms.ant import ANT +if "Daly_CAN" in utils.BMS_TYPE: + from bms.daly_can import Daly_CAN if "MNB" in utils.BMS_TYPE: from bms.mnb import MNB if "Sinowealth" in utils.BMS_TYPE: @@ -56,6 +58,8 @@ # enabled only if explicitly set in config under "BMS_TYPE" if "ANT" in utils.BMS_TYPE: supported_bms_types.append({"bms": ANT, "baud": 19200}) +if "Daly_CAN" in utils.BMS_TYPE: + supported_bms_types.append({"bms": Daly_CAN, "baud": 9600}) if "MNB" in utils.BMS_TYPE: supported_bms_types.append({"bms": MNB, "baud": 9600}) if "Sinowealth" in utils.BMS_TYPE: 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 090/114] 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"]) From d4aef1cfb09b56d997bffc84670bd0681b770166 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 17 Sep 2023 12:26:56 +0200 Subject: [PATCH 091/114] Added: Daly CAN and JKBMS CAN --- etc/dbus-serialbattery/{ => bms}/daly_can.py | 161 +++++++----- etc/dbus-serialbattery/bms/jkbms_can.py | 253 +++++++++++++++++++ etc/dbus-serialbattery/dbus-serialbattery.py | 12 +- 3 files changed, 360 insertions(+), 66 deletions(-) rename etc/dbus-serialbattery/{ => bms}/daly_can.py (72%) create mode 100644 etc/dbus-serialbattery/bms/jkbms_can.py diff --git a/etc/dbus-serialbattery/daly_can.py b/etc/dbus-serialbattery/bms/daly_can.py similarity index 72% rename from etc/dbus-serialbattery/daly_can.py rename to etc/dbus-serialbattery/bms/daly_can.py index e19b1a7e..41ac3710 100644 --- a/etc/dbus-serialbattery/daly_can.py +++ b/etc/dbus-serialbattery/bms/daly_can.py @@ -5,10 +5,10 @@ from struct import * import can -class DalyCAN(Battery): - def __init__(self,port,baud): - super(DalyCAN, self).__init__(port,baud) +class Daly_Can(Battery): + def __init__(self, port, baud): + super(Daly_Can, self).__init__(port, baud) self.charger_connected = None self.load_connected = None self.cell_min_voltage = None @@ -19,6 +19,7 @@ def __init__(self,port,baud): self.poll_step = 0 self.type = self.BATTERYTYPE self.bus = None + # command bytes [Priority=18][Command=94][BMS ID=01][Uplink ID=40] command_base = 0x18940140 command_soc = 0x18900140 @@ -41,8 +42,8 @@ def __init__(self,port,baud): response_temp = 0x18964001 response_cell_balance = 0x18974001 response_alarm = 0x18984001 - - BATTERYTYPE = "Daly_CAN" + + BATTERYTYPE = "Daly_Can" LENGTH_CHECK = 4 LENGTH_POS = 3 CURRENT_ZERO_CONSTANT = 30000 @@ -52,20 +53,24 @@ def test_connection(self): result = False # TODO handle errors? - can_filters = [{"can_id":self.response_base, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_soc, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_minmax_cell_volts, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_minmax_temp, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_fet, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_status, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_cell_volts, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_temp, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_cell_balance, "can_mask": 0xFFFFFFF}, - {"can_id":self.response_alarm, "can_mask": 0xFFFFFFF}] - self.bus = can.Bus(interface='socketcan', - channel='can0', - receive_own_messages=False, - can_filters=can_filters) + can_filters = [ + {"can_id": self.response_base, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_soc, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_minmax_cell_volts, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_minmax_temp, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_fet, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_status, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_cell_volts, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_temp, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_cell_balance, "can_mask": 0xFFFFFFF}, + {"can_id": self.response_alarm, "can_mask": 0xFFFFFFF}, + ] + self.bus = can.Bus( + interface="socketcan", + channel="can0", + receive_own_messages=False, + can_filters=can_filters, + ) result = self.read_status_data(self.bus) @@ -91,12 +96,12 @@ def refresh_data(self): result = result and self.read_cells_volts(self.bus) elif self.poll_step == 3: result = result and self.read_temperature_range_data(self.bus) - #else: # A placeholder to remind this is the last step. Add any additional steps before here + # else: # A placeholder to remind this is the last step. Add any additional steps before here # This is last step so reset poll_step self.poll_step = -1 self.poll_step += 1 - + return result def read_status_data(self, bus): @@ -106,8 +111,14 @@ def read_status_data(self, bus): logger.debug("read_status_data") return False - self.cell_count, self.temp_sensors, self.charger_connected, self.load_connected, \ - state, self.cycles = unpack_from('>bb??bhx', status_data) + ( + self.cell_count, + self.temp_sensors, + self.charger_connected, + self.load_connected, + state, + self.cycles, + ) = unpack_from(">bb??bhx", status_data) self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count @@ -116,10 +127,10 @@ def read_status_data(self, bus): logger.info(self.hardware_version) return True - def read_soc_data(self, ser): + def read_soc_data(self, ser): # Ensure data received is valid crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1) - crntMaxValid = (MAX_BATTERY_CURRENT * 1.3) + crntMaxValid = MAX_BATTERY_CURRENT * 1.3 triesValid = 2 while triesValid > 0: soc_data = self.read_bus_data_daly(ser, self.command_soc) @@ -127,15 +138,19 @@ def read_soc_data(self, ser): if soc_data is False: return False - voltage, tmp, current, soc = unpack_from('>hhhh', soc_data) - current = ((current - self.CURRENT_ZERO_CONSTANT) / -10 * INVERT_CURRENT_MEASUREMENT) - #logger.info("voltage: " + str(voltage) + ", current: " + str(current) + ", soc: " + str(soc)) + voltage, tmp, current, soc = unpack_from(">hhhh", soc_data) + current = ( + (current - self.CURRENT_ZERO_CONSTANT) + / -10 + * INVERT_CURRENT_MEASUREMENT + ) + # logger.info("voltage: " + str(voltage) + ", current: " + str(current) + ", soc: " + str(soc)) if crntMinValid < current < crntMaxValid: - self.voltage = (voltage / 10) + self.voltage = voltage / 10 self.current = current - self.soc = (soc / 10) + self.soc = soc / 10 return True - + logger.warning("read_soc_data - triesValid " + str(triesValid)) triesValid -= 1 @@ -148,12 +163,20 @@ def read_alarm_data(self, ser): logger.warning("read_alarm_data") return False - al_volt, al_temp, al_crnt_soc, al_diff, \ - al_mos, al_misc1, al_misc2, al_fault = unpack_from('>bbbbbbbb', alarm_data) + ( + al_volt, + al_temp, + al_crnt_soc, + al_diff, + al_mos, + al_misc1, + al_misc2, + al_fault, + ) = unpack_from(">bbbbbbbb", alarm_data) if al_volt & 48: # High voltage levels - Alarm - self.voltage_high = 2 + self.voltage_high = 2 elif al_volt & 15: # High voltage Warning levels - Pre-alarm self.voltage_high = 1 @@ -171,7 +194,7 @@ def read_alarm_data(self, ser): if al_temp & 2: # High charge temp - Alarm - self.temp_high_charge = 2 + self.temp_high_charge = 2 elif al_temp & 1: # High charge temp - Pre-alarm self.temp_high_charge = 1 @@ -180,17 +203,16 @@ def read_alarm_data(self, ser): if al_temp & 8: # Low charge temp - Alarm - self.temp_low_charge = 2 + self.temp_low_charge = 2 elif al_temp & 4: # Low charge temp - Pre-alarm self.temp_low_charge = 1 else: self.temp_low_charge = 0 - if al_temp & 32: # High discharge temp - Alarm - self.temp_high_discharge = 2 + self.temp_high_discharge = 2 elif al_temp & 16: # High discharge temp - Pre-alarm self.temp_high_discharge = 1 @@ -199,34 +221,34 @@ def read_alarm_data(self, ser): if al_temp & 128: # Low discharge temp - Alarm - self.temp_low_discharge = 2 + self.temp_low_discharge = 2 elif al_temp & 64: # Low discharge temp - Pre-alarm self.temp_low_discharge = 1 else: self.temp_low_discharge = 0 - #if al_crnt_soc & 2: + # if al_crnt_soc & 2: # # High charge current - Alarm - # self.current_over = 2 - #elif al_crnt_soc & 1: + # self.current_over = 2 + # elif al_crnt_soc & 1: # # High charge current - Pre-alarm # self.current_over = 1 - #else: + # else: # self.current_over = 0 - #if al_crnt_soc & 8: + # if al_crnt_soc & 8: # # High discharge current - Alarm - # self.current_over = 2 - #elif al_crnt_soc & 4: + # self.current_over = 2 + # elif al_crnt_soc & 4: # # High discharge current - Pre-alarm # self.current_over = 1 - #else: + # else: # self.current_over = 0 if al_crnt_soc & 2 or al_crnt_soc & 8: # High charge/discharge current - Alarm - self.current_over = 2 + self.current_over = 2 elif al_crnt_soc & 1 or al_crnt_soc & 4: # High charge/discharge current - Pre-alarm self.current_over = 1 @@ -241,7 +263,7 @@ def read_alarm_data(self, ser): self.soc_low = 1 else: self.soc_low = 0 - + return True def read_cells_volts(self, bus): @@ -252,7 +274,7 @@ def read_cells_volts(self, bus): return False frameCell = [0, 0, 0] - lowMin = (MIN_CELL_VOLTAGE / 2) + lowMin = MIN_CELL_VOLTAGE / 2 frame = 0 bufIdx = 0 @@ -262,15 +284,19 @@ def read_cells_volts(self, bus): for idx in range(self.cell_count): self.cells.append(Cell(True)) - while bufIdx < len(cells_volts_data): - frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from('>Bhhh', cells_volts_data, bufIdx) - for idx in range(3): + while bufIdx < len(cells_volts_data): + frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from( + ">Bhhh", cells_volts_data, bufIdx + ) + for idx in range(3): cellnum = ((frame - 1) * 3) + idx # daly is 1 based, driver 0 based if cellnum >= self.cell_count: break cellVoltage = frameCell[idx] / 1000 - self.cells[cellnum].voltage = None if cellVoltage < lowMin else cellVoltage - bufIdx += 8 + self.cells[cellnum].voltage = ( + None if cellVoltage < lowMin else cellVoltage + ) + bufIdx += 8 return True @@ -281,7 +307,12 @@ def read_cell_voltage_range_data(self, ser): logger.warning("read_cell_voltage_range_data") return False - cell_max_voltage,self.cell_max_no,cell_min_voltage, self.cell_min_no = unpack_from('>hbhb', minmax_data) + ( + cell_max_voltage, + self.cell_max_no, + cell_min_voltage, + self.cell_min_no, + ) = unpack_from(">hbhb", minmax_data) # Daly cells numbers are 1 based and not 0 based self.cell_min_no -= 1 self.cell_max_no -= 1 @@ -297,7 +328,7 @@ def read_temperature_range_data(self, ser): logger.debug("read_temperature_range_data") return False - max_temp,max_no,min_temp, min_no = unpack_from('>bbbb', minmax_data) + max_temp, max_no, min_temp, min_no = unpack_from(">bbbb", minmax_data) self.temp1 = min_temp - self.TEMP_ZERO_CONSTANT self.temp2 = max_temp - self.TEMP_ZERO_CONSTANT return True @@ -309,18 +340,24 @@ def read_fed_data(self, ser): logger.debug("read_fed_data") return False - status, self.charge_fet, self.discharge_fet, bms_cycles, capacity_remain = unpack_from('>b??BL', fed_data) + ( + status, + self.charge_fet, + self.discharge_fet, + bms_cycles, + capacity_remain, + ) = unpack_from(">b??BL", fed_data) self.capacity_remain = capacity_remain / 1000 return True - def read_bus_data_daly(self, bus, command, expectedMessageCount = 1): + def read_bus_data_daly(self, bus, command, expectedMessageCount=1): # TODO handling of error cases message = can.Message(arbitration_id=command) bus.send(message, timeout=0.2) response = bytearray() # TODO use async notifier instead of this where we expect a specific frame to be received - # this could end up in a deadlock if a package is not received + # this could end up in a deadlock if a package is not received count = 0 for msg in bus: # print(f"{msg.arbitration_id:X}: {msg.data}") @@ -328,5 +365,5 @@ def read_bus_data_daly(self, bus, command, expectedMessageCount = 1): response.extend(msg.data) count += 1 if count == expectedMessageCount: - break - return response \ No newline at end of file + break + return response diff --git a/etc/dbus-serialbattery/bms/jkbms_can.py b/etc/dbus-serialbattery/bms/jkbms_can.py new file mode 100644 index 00000000..809c5aaa --- /dev/null +++ b/etc/dbus-serialbattery/bms/jkbms_can.py @@ -0,0 +1,253 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function, unicode_literals +from battery import Protection, Battery, Cell +from utils import * +from struct import * +import can +import time + +CAN_BUS_TYPE = "socketcan" + + +class Jkbms_CAN(Battery): + def __init__(self, port, baud): + super(Jkbms_CAN, self).__init__(port, baud) + self._can_bus = False + self.cell_count = 1 + self.poll_interval = 1500 + self.type = self.BATTERYTYPE + self.last_error_time = time.time() + self.error_active = False + + def __del__(self): + if self._can_bus: + self._can_bus.shutdown() + self._can_bus = False + logger.debug("bus shutdown") + + BATTERYTYPE = "Jkbms" + + CURRENT_ZERO_CONSTANT = 400 + BATT_STAT = "BATT_STAT" + CELL_VOLT = "CELL_VOLT" + CELL_TEMP = "CELL_TEMP" + ALM_INFO = "ALM_INFO" + + MESSAGES_TO_READ = 100 + + CAN_FRAMES = { + BATT_STAT: 0x02F4, + CELL_VOLT: 0x04F4, + CELL_TEMP: 0x05F4, + ALM_INFO: 0x07F4, + } + + def test_connection(self): + # call a function that will connect to the battery, send a command and retrieve the result. + # The result or call should be unique to this BMS. Battery name or version, etc. + # Return True if success, False for failure + return self.read_status_data() + + def get_settings(self): + # After successful connection get_settings will be call to set up the battery. + # Set the current limits, populate cell count, etc + # Return True if success, False for failure + self.max_battery_current = MAX_BATTERY_CURRENT + self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_voltage = MAX_CELL_VOLTAGE * 16 # self.cell_count + self.min_battery_voltage = MIN_CELL_VOLTAGE * 16 # self.cell_count + + # init the cell array add only missing Cell instances + missing_instances = self.cell_count - len(self.cells) + if missing_instances > 0: + for c in range(missing_instances): + self.cells.append(Cell(False)) + + self.hardware_version = "JKBMS CAN " + str(self.cell_count) + " cells" + return True + + def refresh_data(self): + # call all functions that will refresh the battery data. + # This will be called for every iteration (1 second) + # Return True if success, False for failure + result = self.read_status_data() + + return result + + def read_status_data(self): + status_data = self.read_serial_data_jkbms_CAN() + # check if connection success + if status_data is False: + return False + + return True + + def to_fet_bits(self, byte_data): + tmp = bin(byte_data)[2:].rjust(2, zero_char) + self.charge_fet = is_bit_set(tmp[1]) + self.discharge_fet = is_bit_set(tmp[0]) + + def to_protection_bits(self, byte_data): + tmp = bin(byte_data | 0xFF00000000) + pos = len(tmp) + logger.debug(tmp) + self.protection.cell_overvoltage = 2 if int(tmp[pos - 2 : pos], 2) > 0 else 0 + self.protection.voltage_cell_low = ( + 2 if int(tmp[pos - 4 : pos - 2], 2) > 0 else 0 + ) + self.protection.voltage_high = 2 if int(tmp[pos - 6 : pos - 4], 4) > 0 else 0 + self.protection.voltage_low = 2 if int(tmp[pos - 8 : pos - 6], 2) > 0 else 0 + self.protection.cell_imbalance = 2 if int(tmp[pos - 10 : pos - 8], 2) > 0 else 0 + self.protection.current_under = 2 if int(tmp[pos - 12 : pos - 10], 2) > 0 else 0 + self.protection.current_over = 2 if int(tmp[pos - 14 : pos - 12], 2) > 0 else 0 + + # there is just a BMS and Battery temp alarm (not for charg and discharge) + self.protection.temp_high_charge = ( + 2 if int(tmp[pos - 16 : pos - 14], 2) > 0 else 0 + ) + self.protection.temp_high_discharge = ( + 2 if int(tmp[pos - 16 : pos - 14], 2) > 0 else 0 + ) + self.protection.temp_low_charge = ( + 2 if int(tmp[pos - 18 : pos - 16], 2) > 0 else 0 + ) + self.protection.temp_low_discharge = ( + 2 if int(tmp[pos - 18 : pos - 16], 2) > 0 else 0 + ) + self.protection.temp_high_charge = ( + 2 if int(tmp[pos - 20 : pos - 18], 2) > 0 else 0 + ) + self.protection.temp_high_discharge = ( + 2 if int(tmp[pos - 20 : pos - 18], 2) > 0 else 0 + ) + self.protection.soc_low = 2 if int(tmp[pos - 22 : pos - 20], 2) > 0 else 0 + self.protection.internal_failure = ( + 2 if int(tmp[pos - 24 : pos - 22], 2) > 0 else 0 + ) + self.protection.internal_failure = ( + 2 if int(tmp[pos - 26 : pos - 24], 2) > 0 else 0 + ) + self.protection.internal_failure = ( + 2 if int(tmp[pos - 28 : pos - 26], 2) > 0 else 0 + ) + self.protection.internal_failure = ( + 2 if int(tmp[pos - 30 : pos - 28], 2) > 0 else 0 + ) + + def reset_protection_bits(self): + self.protection.cell_overvoltage = 0 + self.protection.voltage_cell_low = 0 + self.protection.voltage_high = 0 + self.protection.voltage_low = 0 + self.protection.cell_imbalance = 0 + self.protection.current_under = 0 + self.protection.current_over = 0 + + # there is just a BMS and Battery temp alarm (not for charg and discharge) + self.protection.temp_high_charge = 0 + self.protection.temp_high_discharge = 0 + self.protection.temp_low_charge = 0 + self.protection.temp_low_discharge = 0 + self.protection.temp_high_charge = 0 + self.protection.temp_high_discharge = 0 + self.protection.soc_low = 0 + self.protection.internal_failure = 0 + self.protection.internal_failure = 0 + self.protection.internal_failure = 0 + self.protection.internal_failure = 0 + + def read_serial_data_jkbms_CAN(self): + if self._can_bus is False: + logger.debug("Can bus init") + # intit the can interface + try: + self._can_bus = can.interface.Bus( + bustype=CAN_BUS_TYPE, channel=self.port, bitrate=self.baud_rate + ) + except can.CanError as e: + logger.error(e) + + if self._can_bus is None: + return False + + logger.debug("Can bus init done") + + # reset errors after timeout + if ((time.time() - self.last_error_time) > 120.0) and self.error_active == True: + self.error_active = False + self.reset_protection_bits() + + # read msgs until we get one we want + messages_to_read = self.MESSAGES_TO_READ + while messages_to_read > 0: + msg = self._can_bus.recv(1) + if msg is None: + logger.info("No CAN Message received") + return False + + if msg is not None: + # print("message received") + messages_to_read -= 1 + # print(messages_to_read) + if msg.arbitration_id == self.CAN_FRAMES[self.BATT_STAT]: + voltage = unpack_from(" self.cell_count: + self.cell_count = max_cell_cnt + self.get_settings() + + for c_nr in range(len(self.cells)): + self.cells[c_nr].balance = False + + if self.cell_count == len(self.cells): + self.cells[max_cell_nr - 1].voltage = max_cell_volt + self.cells[max_cell_nr - 1].balance = True + + self.cells[min_cell_nr - 1].voltage = min_cell_volt + self.cells[min_cell_nr - 1].balance = True + + elif msg.arbitration_id == self.CAN_FRAMES[self.CELL_TEMP]: + max_temp = unpack_from(" Date: Sun, 17 Sep 2023 16:06:14 +0200 Subject: [PATCH 092/114] added CAN bms to installation script optimized CAN drivers --- CHANGELOG.md | 3 + etc/dbus-serialbattery/bms/daly_can.py | 65 +++++---- etc/dbus-serialbattery/bms/jkbms_brn.py | 33 ++++- etc/dbus-serialbattery/bms/jkbms_can.py | 56 +++++--- etc/dbus-serialbattery/config.default.ini | 9 ++ etc/dbus-serialbattery/dbus-serialbattery.py | 30 ++-- etc/dbus-serialbattery/disable.sh | 7 +- etc/dbus-serialbattery/reinstall-local.sh | 143 ++++++++++++++++++- etc/dbus-serialbattery/uninstall.sh | 3 +- etc/dbus-serialbattery/utils.py | 2 +- 10 files changed, 281 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c5d936..4137b7a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,12 @@ * Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Current average of the last 5 minutes by @mr-manuel * Added: Daly BMS - Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit +* Added: Daly BMS connect via CAN (experimental, some limits apply) with https://github.com/Louisvdw/dbus-serialbattery/pull/169 by @SamuelBrucksch and @mr-manuel * Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel * Added: Implement callback function for update by @seidler2547 +* Added: JKBMS BLE - Automatic SOC reset with https://github.com/Louisvdw/dbus-serialbattery/pull/736 by @ArendsM * Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel +* Added: JKBMS BMS connect via CAN (experimental, some limits apply) by @IrisCrimson and @mr-manuel * Added: LLT/JBD BMS - Discharge / Charge Mosfet and disable / enable balancer switching over remote console/GUI with https://github.com/Louisvdw/dbus-serialbattery/pull/761 by @idstein * Added: LLT/JBD BMS - Show balancer state in GUI under the IO page with https://github.com/Louisvdw/dbus-serialbattery/pull/763 by @idstein * Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/daly_can.py b/etc/dbus-serialbattery/bms/daly_can.py index 41ac3710..5b4927ef 100644 --- a/etc/dbus-serialbattery/bms/daly_can.py +++ b/etc/dbus-serialbattery/bms/daly_can.py @@ -1,14 +1,26 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import ( + BATTERY_CAPACITY, + INVERT_CURRENT_MEASUREMENT, + logger, + MAX_BATTERY_CHARGE_CURRENT, + MAX_BATTERY_DISCHARGE_CURRENT, + MAX_CELL_VOLTAGE, + MIN_CELL_VOLTAGE, +) +from struct import unpack_from import can +""" +https://github.com/Louisvdw/dbus-serialbattery/pull/169 +""" + class Daly_Can(Battery): - def __init__(self, port, baud): - super(Daly_Can, self).__init__(port, baud) + def __init__(self, port, baud, address): + super(Daly_Can, self).__init__(port, baud, address) self.charger_connected = None self.load_connected = None self.cell_min_voltage = None @@ -18,7 +30,7 @@ def __init__(self, port, baud): self.poll_interval = 1000 self.poll_step = 0 self.type = self.BATTERYTYPE - self.bus = None + self.can_bus = None # command bytes [Priority=18][Command=94][BMS ID=01][Uplink ID=40] command_base = 0x18940140 @@ -65,37 +77,38 @@ def test_connection(self): {"can_id": self.response_cell_balance, "can_mask": 0xFFFFFFF}, {"can_id": self.response_alarm, "can_mask": 0xFFFFFFF}, ] - self.bus = can.Bus( + self.can_bus = can.Bus( interface="socketcan", - channel="can0", + channel=self.port, receive_own_messages=False, can_filters=can_filters, ) - result = self.read_status_data(self.bus) + result = self.read_status_data(self.can_bus) return result def get_settings(self): self.capacity = BATTERY_CAPACITY - self.max_battery_current = MAX_BATTERY_CURRENT + self.max_battery_current = MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT return True def refresh_data(self): result = False - result = self.read_soc_data(self.bus) - result = result and self.read_fed_data(self.bus) + result = self.read_soc_data(self.can_bus) + result = result and self.read_fed_data(self.can_bus) if self.poll_step == 0: - # This must be listed in step 0 as get_min_cell_voltage and get_max_cell_voltage in battery.py needs it at first cycle for publish_dbus in dbushelper.py - result = result and self.read_cell_voltage_range_data(self.bus) + # This must be listed in step 0 as get_min_cell_voltage and get_max_cell_voltage in battery.py + # needs it at first cycle for publish_dbus in dbushelper.py + result = result and self.read_cell_voltage_range_data(self.can_bus) elif self.poll_step == 1: - result = result and self.read_alarm_data(self.bus) + result = result and self.read_alarm_data(self.can_bus) elif self.poll_step == 2: - result = result and self.read_cells_volts(self.bus) + result = result and self.read_cells_volts(self.can_bus) elif self.poll_step == 3: - result = result and self.read_temperature_range_data(self.bus) + result = result and self.read_temperature_range_data(self.can_bus) # else: # A placeholder to remind this is the last step. Add any additional steps before here # This is last step so reset poll_step self.poll_step = -1 @@ -104,8 +117,8 @@ def refresh_data(self): return result - def read_status_data(self, bus): - status_data = self.read_bus_data_daly(bus, self.command_status) + def read_status_data(self, can_bus): + status_data = self.read_bus_data_daly(can_bus, self.command_status) # check if connection success if status_data is False: logger.debug("read_status_data") @@ -130,7 +143,7 @@ def read_status_data(self, bus): def read_soc_data(self, ser): # Ensure data received is valid crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1) - crntMaxValid = MAX_BATTERY_CURRENT * 1.3 + crntMaxValid = MAX_BATTERY_CHARGE_CURRENT * 1.3 triesValid = 2 while triesValid > 0: soc_data = self.read_bus_data_daly(ser, self.command_soc) @@ -266,9 +279,11 @@ def read_alarm_data(self, ser): return True - def read_cells_volts(self, bus): + def read_cells_volts(self, can_bus): if self.cell_count is not None: - cells_volts_data = self.read_bus_data_daly(bus, self.command_cell_volts, 6) + cells_volts_data = self.read_bus_data_daly( + can_bus, self.command_cell_volts, 6 + ) if cells_volts_data is False: logger.warning("read_cells_volts") return False @@ -350,16 +365,16 @@ def read_fed_data(self, ser): self.capacity_remain = capacity_remain / 1000 return True - def read_bus_data_daly(self, bus, command, expectedMessageCount=1): + def read_bus_data_daly(self, can_bus, command, expectedMessageCount=1): # TODO handling of error cases message = can.Message(arbitration_id=command) - bus.send(message, timeout=0.2) + can_bus.send(message, timeout=0.2) response = bytearray() # TODO use async notifier instead of this where we expect a specific frame to be received # this could end up in a deadlock if a package is not received count = 0 - for msg in bus: + for msg in can_bus: # print(f"{msg.arbitration_id:X}: {msg.data}") # logger.info('Frame: ' + ", ".join(hex(b) for b in msg.data)) response.extend(msg.data) diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 1f19a881..9f3f80ed 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -291,7 +291,12 @@ 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, awaitresponse: bool + self, + address, + vals: bytearray, + length: int, + bleakC: BleakClient, + awaitresponse: bool, ): frame = bytearray(20) frame[0] = 0xAA # start sequence @@ -428,7 +433,7 @@ async def enable_charging(self, c): def jk_float_to_hex_little(self, val: float): intval = int(val * 1000) - hexval = f'{intval:0>8X}' + hexval = f"{intval:0>8X}" return bytearray.fromhex(hexval)[::-1] async def reset_soc_jk(self, c): @@ -436,15 +441,31 @@ async def reset_soc_jk(self, c): # 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) + 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) + 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.") diff --git a/etc/dbus-serialbattery/bms/jkbms_can.py b/etc/dbus-serialbattery/bms/jkbms_can.py index 809c5aaa..2021771d 100644 --- a/etc/dbus-serialbattery/bms/jkbms_can.py +++ b/etc/dbus-serialbattery/bms/jkbms_can.py @@ -1,18 +1,31 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import ( + is_bit_set, + logger, + MAX_BATTERY_CHARGE_CURRENT, + MAX_BATTERY_DISCHARGE_CURRENT, + MAX_CELL_VOLTAGE, + MIN_CELL_VOLTAGE, + zero_char, +) +from struct import unpack_from import can import time -CAN_BUS_TYPE = "socketcan" +""" +https://github.com/Louisvdw/dbus-serialbattery/compare/dev...IrisCrimson:dbus-serialbattery:jkbms_can +# Restrictions seen from code: +- +""" -class Jkbms_CAN(Battery): - def __init__(self, port, baud): - super(Jkbms_CAN, self).__init__(port, baud) - self._can_bus = False + +class Jkbms_Can(Battery): + def __init__(self, port, baud, address): + super(Jkbms_Can, self).__init__(port, baud, address) + self.can_bus = False self.cell_count = 1 self.poll_interval = 1500 self.type = self.BATTERYTYPE @@ -20,12 +33,13 @@ def __init__(self, port, baud): self.error_active = False def __del__(self): - if self._can_bus: - self._can_bus.shutdown() - self._can_bus = False + if self.can_bus: + self.can_bus.shutdown() + self.can_bus = False logger.debug("bus shutdown") - BATTERYTYPE = "Jkbms" + BATTERYTYPE = "Jkbms_Can" + CAN_BUS_TYPE = "socketcan" CURRENT_ZERO_CONSTANT = 400 BATT_STAT = "BATT_STAT" @@ -52,10 +66,10 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - self.max_battery_current = MAX_BATTERY_CURRENT + self.max_battery_current = MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT - self.max_battery_voltage = MAX_CELL_VOLTAGE * 16 # self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * 16 # self.cell_count + self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count # init the cell array add only missing Cell instances missing_instances = self.cell_count - len(self.cells) @@ -157,30 +171,30 @@ def reset_protection_bits(self): self.protection.internal_failure = 0 def read_serial_data_jkbms_CAN(self): - if self._can_bus is False: + if self.can_bus is False: logger.debug("Can bus init") # intit the can interface try: - self._can_bus = can.interface.Bus( - bustype=CAN_BUS_TYPE, channel=self.port, bitrate=self.baud_rate + self.can_bus = can.interface.Bus( + bustype=self.CAN_BUS_TYPE, channel=self.port, bitrate=self.baud_rate ) except can.CanError as e: logger.error(e) - if self._can_bus is None: + if self.can_bus is None: return False logger.debug("Can bus init done") # reset errors after timeout - if ((time.time() - self.last_error_time) > 120.0) and self.error_active == True: + if ((time.time() - self.last_error_time) > 120.0) and self.error_active is True: self.error_active = False self.reset_protection_bits() # read msgs until we get one we want messages_to_read = self.MESSAGES_TO_READ while messages_to_read > 0: - msg = self._can_bus.recv(1) + msg = self.can_bus.recv(1) if msg is None: logger.info("No CAN Message received") return False diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 4ae1371e..7b703bbc 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -36,6 +36,15 @@ BULK_AFTER_DAYS = ; 3 BMS: Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22 BLUETOOTH_BMS = +; --------- CAN BMS --------- +; Description: Specify the CAN port(s) where the BMS is connected to. Leave empty to disable +; -- Available CAN BMS: +; Daly_Can, Jkbms_Can +; Example: +; can0 +; can0, can8, can9 +CAN_PORT = + ; --------- BMS disconnect behaviour --------- ; Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the ; BMS on purpose, then you have to restart the driver/system to reset the block. diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index e3235642..cf6909c2 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -34,10 +34,6 @@ # enabled only if explicitly set in config under "BMS_TYPE" if "ANT" in utils.BMS_TYPE: from bms.ant import ANT -if "Daly_Can" in utils.BMS_TYPE: - from bms.daly_can import Daly_Can -if "Jkbms_Can" in utils.BMS_TYPE: - from bms.jkbms_can import Jkbms_Can if "MNB" in utils.BMS_TYPE: from bms.mnb import MNB if "Sinowealth" in utils.BMS_TYPE: @@ -60,10 +56,6 @@ # enabled only if explicitly set in config under "BMS_TYPE" if "ANT" in utils.BMS_TYPE: supported_bms_types.append({"bms": ANT, "baud": 19200}) -if "Daly_Can" in utils.BMS_TYPE: - supported_bms_types.append({"bms": Daly_Can, "baud": 9600}) -if "Jkbms_Can" in utils.BMS_TYPE: - supported_bms_types.append({"bms": Jkbms_Can, "baud": 250000}) if "MNB" in utils.BMS_TYPE: supported_bms_types.append({"bms": MNB, "baud": 9600}) if "Sinowealth" in utils.BMS_TYPE: @@ -177,6 +169,28 @@ def get_port() -> str: if testbms.test_connection(): logger.info("Connection established to " + testbms.__class__.__name__) battery = testbms + elif port.startswith("can"): + """ + Import CAN classes only, if it's a can port, else the driver won't start due to missing python modules + This prevent problems when using the driver only with a serial connection + """ + from bms.daly_can import Daly_Can + from bms.jkbms_can import Jkbms_Can + + # only try CAN BMS on CAN port + supported_bms_types = [ + {"bms": Daly_Can, "baud": 250000}, + {"bms": Jkbms_Can, "baud": 250000}, + ] + + expected_bms_types = [ + battery_type + for battery_type in supported_bms_types + if battery_type["bms"].__name__ in utils.BMS_TYPE + or len(utils.BMS_TYPE) == 0 + ] + + battery = get_battery(port) else: battery = get_battery(port) diff --git a/etc/dbus-serialbattery/disable.sh b/etc/dbus-serialbattery/disable.sh index 13216cfe..3beacfad 100755 --- a/etc/dbus-serialbattery/disable.sh +++ b/etc/dbus-serialbattery/disable.sh @@ -16,6 +16,7 @@ pkill -f "/opt/victronenergy/serial-starter/serial-starter.sh" # remove services rm -rf /service/dbus-serialbattery.* rm -rf /service/dbus-blebattery.* +rm -rf /service/dbus-canbattery.* # kill driver, if running # serial @@ -25,7 +26,11 @@ pkill -f "python .*/dbus-serialbattery.py /dev/tty.*" # bluetooth pkill -f "supervise dbus-blebattery.*" pkill -f "multilog .* /var/log/dbus-blebattery.*" -pkill -f "python .*/dbus-serialbattery.py .*_Ble" +pkill -f "python .*/dbus-serialbattery.py .*_Ble.*" +# can +pkill -f "supervise dbus-canbattery.*" +pkill -f "multilog .* /var/log/dbus-canbattery.*" +pkill -f "python .*/dbus-serialbattery.py can.*" # remove install script from rc.local sed -i "/bash \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 7569fbb6..1532cf16 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -144,8 +144,8 @@ IFS="," read -r -a bms_array <<< "$bluetooth_bms_clean" #declare -p bms_array # readarray -td, bms_array <<< "$bluetooth_bms_clean,"; unset 'bms_array[-1]'; declare -p bms_array; -length=${#bms_array[@]} -# echo $length +bluetooth_length=${#bms_array[@]} +# echo $bluetooth_length # stop all dbus-blebattery services, if at least one exists if [ -d "/service/dbus-blebattery.0" ]; then @@ -164,10 +164,10 @@ if [ -d "/service/dbus-blebattery.0" ]; then fi -if [ "$length" -gt 0 ]; then +if [ "$bluetooth_length" -gt 0 ]; then echo - echo "Found $length Bluetooth BMS in the config file!" + echo "Found $bluetooth_length Bluetooth BMS in the config file!" echo /etc/init.d/bluetooth stop @@ -266,7 +266,7 @@ if [ "$length" -gt 0 ]; then # install_blebattery_service 0 Jkbms_Ble C8:47:8C:00:00:00 # install_blebattery_service 1 Jkbms_Ble C8:47:8C:00:00:11 - for (( i=0; i "/service/dbus-canbattery.$1/log/run" + chmod 755 "/service/dbus-canbattery.$1/log/run" + + { + echo "#!/bin/sh" + echo "exec 2>&1" + echo "echo" + echo "python /opt/victronenergy/dbus-serialbattery/dbus-serialbattery.py $1" + } > "/service/dbus-canbattery.$1/run" + chmod 755 "/service/dbus-canbattery.$1/run" + } + + # Example + # install_canbattery_service can0 + # install_canbattery_service can9 + + for (( i=0; i Bluetooth in the remote console/GUI to prevent reconnects every minute." +echo " 2. Make sure to disable Bluetooth in \"Settings -> Bluetooth\" in the remote console/GUI to prevent reconnects every minute." echo echo " 3. Re-run \"/data/etc/dbus-serialbattery/reinstall-local.sh\", if the Bluetooth BMS were not added to the \"config.ini\" before." echo @@ -327,6 +446,16 @@ echo " ATTENTION!" echo " If you changed the default connection PIN of your BMS, then you have to pair the BMS first using OS tools like the \"bluetoothctl\"." echo " See https://wiki.debian.org/BluetoothUser#Using_bluetoothctl for more details." echo +echo "CAN battery connection: There are a few more steps to complete installation." +echo +echo " 1. Please add the CAN port to the config file \"/data/etc/dbus-serialbattery/config.ini\" by adding \"CAN_PORT\" = " +echo " Example with 1 CAN port: CAN_PORT = can0" +echo " Example with 3 CAN port: CAN_PORT = can0, can8, can9" +echo +echo " 2. Make sure to select a profile with 250 kbit/s in \"Settings -> Services -> VE.Can port -> CAN-bus profile\" in the remote console/GUI." +echo +echo " 3. Re-run \"/data/etc/dbus-serialbattery/reinstall-local.sh\", if the CAN port was not added to the \"config.ini\" before." +echo echo "CUSTOM SETTINGS: If you want to add custom settings, then check the settings you want to change in \"/data/etc/dbus-serialbattery/config.default.ini\"" echo " and add them to \"/data/etc/dbus-serialbattery/config.ini\" to persist future driver updates." echo diff --git a/etc/dbus-serialbattery/uninstall.sh b/etc/dbus-serialbattery/uninstall.sh index 94100a9d..9bec2518 100755 --- a/etc/dbus-serialbattery/uninstall.sh +++ b/etc/dbus-serialbattery/uninstall.sh @@ -19,12 +19,13 @@ rm -rf /opt/victronenergy/dbus-serialbattery # uninstall modules -read -r -p "Do you want to uninstall bleak, python3-pip and python3-modules? If you don't know just press enter. [y/N] " response +read -r -p "Do you want to uninstall bleak, python-can, python3-pip and python3-modules? If you don't know just press enter. [y/N] " response echo response=${response,,} # tolower if [[ $response =~ ^(y) ]]; then echo "Uninstalling modules..." pip3 uninstall bleak + pip3 uninstall python-can opkg remove python3-pip python3-modules echo "done." echo diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 2bb73355..27541381 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230905dev" +DRIVER_VERSION = "1.0.20230917dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 7be087f48eb7f99e84d181e52b1eae53960736cb Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 19 Sep 2023 11:46:50 +0200 Subject: [PATCH 093/114] smaller fixes --- etc/dbus-serialbattery/bms/mnb.py | 5 ++--- etc/dbus-serialbattery/dbus-serialbattery.py | 3 +++ etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/etc/dbus-serialbattery/bms/mnb.py b/etc/dbus-serialbattery/bms/mnb.py index a9d38853..ac95608d 100644 --- a/etc/dbus-serialbattery/bms/mnb.py +++ b/etc/dbus-serialbattery/bms/mnb.py @@ -2,9 +2,8 @@ # # MNB is disabled by default # can be enabled by specifying it in the BMS_TYPE setting in the "config.ini" -# https://github.com/Louisvdw/dbus-serialbattery/commit/65241cbff36feb861ff43dbbcfb2b495f14a01ce -# remove duplicate MNB lines -# https://github.com/Louisvdw/dbus-serialbattery/commit/23afec33c2fd87fd4d4c53516f0a25f290643c82 +# https://github.com/Louisvdw/dbus-serialbattery/issues/590 +# https://community.victronenergy.com/comments/231924/view.html from battery import Protection, Battery, Cell from utils import logger diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index cf6909c2..9119e850 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -72,6 +72,9 @@ def main(): + # NameError: free variable 'expected_bms_types' referenced before assignment in enclosing scope + global expected_bms_types + def poll_battery(loop): helper.publish_battery(loop) return True diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 27541381..e9220e2a 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230917dev" +DRIVER_VERSION = "1.0.20230919dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From b4a254857fbb6cb94cb76d93546e1bf053ecf990 Mon Sep 17 00:00:00 2001 From: Meik Arends Date: Wed, 20 Sep 2023 18:13:22 +0000 Subject: [PATCH 094/114] Trigger JK BLE SOC reset when using Step Mode --- etc/dbus-serialbattery/battery.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index f93d8a56..afed9ebc 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -539,6 +539,8 @@ def manage_charge_voltage_step(self) -> None: if utils.MAX_VOLTAGE_TIME_SEC < tDiff: self.allow_max_voltage = False self.max_voltage_start_time = None + # Assume battery SOC ist 100% at this stage + self.trigger_soc_reset() else: pass From 1d2a6c8c8403ff8fed566577597da34f95a2832e Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 21 Sep 2023 08:32:05 +0200 Subject: [PATCH 095/114] Moved trigger_soc_reset() --- etc/dbus-serialbattery/battery.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index afed9ebc..9a7ab60d 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -404,6 +404,7 @@ def manage_charge_voltage_linear(self) -> None: # to make it restart persistent self.bulk_last_reached = current_time if self.control_voltage: + # check if battery changed from bulk/absoprtion to float if not self.charge_mode.startswith("Float"): self.transition_start_time = current_time self.initial_control_voltage = self.control_voltage @@ -539,8 +540,6 @@ def manage_charge_voltage_step(self) -> None: if utils.MAX_VOLTAGE_TIME_SEC < tDiff: self.allow_max_voltage = False self.max_voltage_start_time = None - # Assume battery SOC ist 100% at this stage - self.trigger_soc_reset() else: pass @@ -552,6 +551,10 @@ def manage_charge_voltage_step(self) -> None: ) else: + # check if battery changed from bulk/absoprtion to float + if not self.charge_mode.startswith("Float"): + # Assume battery SOC ist 100% at this stage + self.trigger_soc_reset() self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count self.charge_mode = "Float" # reset bulk when going into float From 17e84e214a8cb3efd73fbae212090b939ca98165 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 21 Sep 2023 11:39:38 +0200 Subject: [PATCH 096/114] fixes LLT/JBD SOC > 100% https://github.com/Louisvdw/dbus-serialbattery/issues/769 --- etc/dbus-serialbattery/bms/lltjbd.py | 3 ++- etc/dbus-serialbattery/utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 0d1bbfd9..5c658fac 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -552,7 +552,8 @@ def read_gen_data(self): ) = unpack_from(">HhHHHHhHHBBBBB", gen_data) self.voltage = voltage / 100 self.current = current / 100 - if not self.cycle_capacity: + # https://github.com/Louisvdw/dbus-serialbattery/issues/769#issuecomment-1720805325 + if not self.cycle_capacity or self.cycle_capacity < capacity_remain: self.cycle_capacity = capacity self.soc = round(100 * capacity_remain / self.cycle_capacity, 2) self.capacity_remain = capacity_remain / 100 diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index e9220e2a..69481ca9 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230919dev" +DRIVER_VERSION = "1.0.20230921dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 36ff1b05d7257a2902c81deb290d64fe90c50441 Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 27 Sep 2023 10:15:07 +0200 Subject: [PATCH 097/114] changed VOLTAGE_DROP behaviour --- CHANGELOG.md | 8 ++++++-- etc/dbus-serialbattery/battery.py | 11 +++-------- etc/dbus-serialbattery/config.default.ini | 18 ++++++++++-------- etc/dbus-serialbattery/dbushelper.py | 4 +++- etc/dbus-serialbattery/utils.py | 2 +- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4137b7a3..e360eb27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## v1.0.x + * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Current average of the last 5 minutes by @mr-manuel @@ -18,6 +19,7 @@ * Added: Temperature names to dbus and mqtt by @mr-manuel * Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel +* Changed: `VOLTAGE_DROP` now behaves differently. Before it reduced the voltage for the check, now the voltage for the charger is increased in order to get the target voltage on the BMS by @mr-manuel * Changed: Daly BMS - Fix readsentence by @transistorgit * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fixed Building wheel for dbus-fast won't finish on weak systems https://github.com/Louisvdw/dbus-serialbattery/issues/785 by @mr-manuel @@ -31,8 +33,10 @@ * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: JKBMS_BLE BMS - Improved driver by @seidler2547 & @mr-manuel * Changed: LLT/JBD BMS - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein +* Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/730 by @mr-manuel +* Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/769 by @mr-manuel * Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/778 with https://github.com/Louisvdw/dbus-serialbattery/pull/798 by @idstein -* Changed: LLT/JBD BMS - Improved error handling and automatical driver restart in case of error. Should fix https://github.com/Louisvdw/dbus-serialbattery/issues/730, https://github.com/Louisvdw/dbus-serialbattery/issues/769 and https://github.com/Louisvdw/dbus-serialbattery/issues/777 by @mr-manuel +* Changed: LLT/JBD BMS - Improved error handling and automatical driver restart in case of error. Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/777 by @mr-manuel * Changed: LLT/JBD BMS - SOC different in Xiaoxiang app and dbus-serialbattery with https://github.com/Louisvdw/dbus-serialbattery/pull/760 by @idstein * Changed: Make CCL and DCL limiting messages more clear by @mr-manuel * Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich @@ -44,7 +48,7 @@ ## v1.0.20230531 -### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` gets lost. The changes in the `config.ini` will persists future updates. +### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` get lost. The changes in the `config.ini` will persists future updates. * Added: `self.unique_identifier` to the battery class. Used to identify a BMS when multiple BMS are connected - planned for future use by @mr-manuel * Added: Alert is triggered, when BMS communication is lost by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 9a7ab60d..d2352719 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -321,7 +321,7 @@ def manage_charge_voltage_linear(self) -> None: if self.max_voltage_start_time is None: # start timer, if max voltage is reached and cells are balanced if ( - self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum + self.max_battery_voltage <= voltageSum and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL and self.allow_max_voltage ): @@ -355,9 +355,7 @@ def manage_charge_voltage_linear(self) -> None: # regardless of whether we were in absorption mode or not if ( voltageSum - < self.max_battery_voltage - - utils.VOLTAGE_DROP - - measurementToleranceVariation + < self.max_battery_voltage - measurementToleranceVariation ): self.max_voltage_start_time = None @@ -515,10 +513,7 @@ def manage_charge_voltage_step(self) -> None: if self.max_voltage_start_time is None: # check if max voltage is reached and start timer to keep max voltage - if ( - self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum - and self.allow_max_voltage - ): + if self.max_battery_voltage <= voltageSum and self.allow_max_voltage: # example 2 self.max_voltage_start_time = current_time diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 7b703bbc..dca4576d 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -296,16 +296,18 @@ LIPRO_CELL_COUNT = 15 HELTEC_MODBUS_ADDR = 1 -; --------- Battery monitor specific settings --------- -; If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported -; from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage, -; since max voltage is never reached. +; --------- Voltage drop --------- +; If you have a voltage drop between the BMS and the charger because of wire size or length +; then you can specify the voltage drop here. The driver will then add the voltage drop +; to the calculated CVL to compensate. ; Example: ; cell count: 16 ; MAX_CELL_VOLTAGE = 3.45 ; max voltage calculated = 16 * 3.45 = 55.20 -; CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS -; now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures -; 55.05 V the max voltage is never reached for the driver and max voltage is kept forever. -; Set VOLTAGE_DROP to 0.15 +; CVL is set to 55.20 V and the battery is now charged until the charger reaches 55.20 V. +; The BMS now measures 55.05 V since there is a voltage drop of 0.15 V on the cable. +; Since the dbus-serialbattery reads the voltage of 55.05 V from the BMS the max voltage +; of 55.20 V is never reached and max voltage is kept forever. +; By setting the VOLTAGE_DROP to 0.15 V the voltage on the charger is increased and the +; target voltage on the BMS is reached. VOLTAGE_DROP = 0.00 diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index d8d2b77b..42c3189f 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -482,7 +482,9 @@ def publish_dbus(self): self._dbusservice["/System/Temperature4Name"] = utils.TEMP_4_NAME # Voltage control - self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage + self._dbusservice["/Info/MaxChargeVoltage"] = round( + self.battery.control_voltage + utils.VOLTAGE_DROP, 2 + ) # Charge control self._dbusservice[ diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 69481ca9..019abe33 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -38,7 +38,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230921dev" +DRIVER_VERSION = "1.0.20230927dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From d37721cac19be6b2f8e51a3c6d01b5adce5768b4 Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 27 Sep 2023 10:24:46 +0200 Subject: [PATCH 098/114] Fix JKBMS not starting if BMS manuf. date is empty --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/jkbms.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e360eb27..06f08f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ * Changed: Improved battery voltage handling in linear absorption mode by @ogurevich * Changed: Improved driver disable script by @md-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel +* Changed: JKBMS - Driver do not start if manufacturer date in BMS is empty https://github.com/Louisvdw/dbus-serialbattery/issues/823 by @mr-manuel * Changed: JKBMS_BLE BMS - Improved driver by @seidler2547 & @mr-manuel * Changed: LLT/JBD BMS - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein * Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/730 by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index 74b221b8..f02fdeab 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -175,11 +175,14 @@ def read_status_data(self): self.custom_field = tmp if tmp != "Input Us" else None # production date - offset = cellbyte_count + 164 - tmp = unpack_from(">4s", self.get_data(status_data, b"\xB5", offset, 4))[ - 0 - ].decode() - self.production = "20" + tmp + "01" if tmp and tmp != "" else None + try: + offset = cellbyte_count + 164 + tmp = unpack_from(">4s", self.get_data(status_data, b"\xB5", offset, 4))[ + 0 + ].decode() + self.production = "20" + tmp + "01" if tmp and tmp != "" else None + except UnicodeEncodeError: + self.production = None offset = cellbyte_count + 174 self.version = unpack_from( From 3907fb21ea1bf2b67f77f75860231bf77fdbdd5d Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 27 Sep 2023 11:00:57 +0200 Subject: [PATCH 099/114] corrected bulk, absorption and soc reset terms --- CHANGELOG.md | 10 ++- etc/dbus-serialbattery/battery.py | 93 ++++++++++++----------- etc/dbus-serialbattery/bms/jkbms.py | 4 +- etc/dbus-serialbattery/config.default.ini | 20 ++--- etc/dbus-serialbattery/dbushelper.py | 4 +- etc/dbus-serialbattery/utils.py | 14 ++-- 6 files changed, 80 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06f08f61..68d1f9ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Breaking changes + +* Driver version greater or equal to `v1.0.20230629beta` and smaller or equal to `v1.0.20230926beta`: + + With `v1.0.20230927beta` the following values changed names: + * `BULK_CELL_VOLTAGE` -> `SOC_RESET_VOLTAGE` + * `BULK_AFTER_DAYS` -> `SOC_RESET_AFTER_DAYS` + ## v1.0.x * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel @@ -14,7 +22,7 @@ * Added: JKBMS BMS connect via CAN (experimental, some limits apply) by @IrisCrimson and @mr-manuel * Added: LLT/JBD BMS - Discharge / Charge Mosfet and disable / enable balancer switching over remote console/GUI with https://github.com/Louisvdw/dbus-serialbattery/pull/761 by @idstein * Added: LLT/JBD BMS - Show balancer state in GUI under the IO page with https://github.com/Louisvdw/dbus-serialbattery/pull/763 by @idstein -* Added: Load to bulk voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel +* Added: Load to SOC reset voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel * Added: Save custom name and make it restart persistant by @mr-manuel * Added: Temperature names to dbus and mqtt by @mr-manuel * Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index d2352719..6a229e34 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -106,9 +106,9 @@ def init_values(self): self.cells: List[Cell] = [] self.control_charging = None self.control_voltage = None - self.bulk_requested = False - self.bulk_last_reached = 0 - self.bulk_battery_voltage = None + self.soc_reset_requested = False + self.soc_reset_last_reached = 0 + self.soc_reset_battery_voltage = None self.max_battery_voltage = None self.min_battery_voltage = None self.allow_max_voltage = True @@ -244,34 +244,36 @@ def manage_charge_voltage(self) -> None: self.charge_mode = "Keep always max voltage" def prepare_voltage_management(self) -> None: - bulk_last_reached_days_ago = ( + soc_reset_last_reached_days_ago = ( 0 - if self.bulk_last_reached == 0 - else (((int(time()) - self.bulk_last_reached) / 60 / 60 / 24)) + if self.soc_reset_last_reached == 0 + else (((int(time()) - self.soc_reset_last_reached) / 60 / 60 / 24)) ) - # set bulk_requested to True, if the days are over + # set soc_reset_requested to True, if the days are over # it gets set to False once the bulk voltage was reached once if ( - utils.BULK_AFTER_DAYS is not False - and self.bulk_requested is False + utils.SOC_RESET_AFTER_DAYS is not False + and self.soc_reset_requested is False and self.allow_max_voltage and ( - self.bulk_last_reached == 0 - or utils.BULK_AFTER_DAYS < bulk_last_reached_days_ago + self.soc_reset_last_reached == 0 + or utils.SOC_RESET_AFTER_DAYS < soc_reset_last_reached_days_ago ) ): """ logger.info( - f"set bulk_requested to True: first time (0) or {utils.BULK_AFTER_DAYS}" - + f" < {round(bulk_last_reached_days_ago, 2)}" + f"set soc_reset_requested to True: first time (0) or {utils.SOC_RESET_AFTER_DAYS}" + + f" < {round(soc_reset_last_reached_days_ago, 2)}" ) """ - self.bulk_requested = True + self.soc_reset_requested = True - self.bulk_battery_voltage = round(utils.BULK_CELL_VOLTAGE * self.cell_count, 2) + self.soc_reset_battery_voltage = round( + utils.SOC_RESET_VOLTAGE * self.cell_count, 2 + ) - if self.bulk_requested: - self.max_battery_voltage = self.bulk_battery_voltage + if self.soc_reset_requested: + self.max_battery_voltage = self.soc_reset_battery_voltage else: self.max_battery_voltage = round( utils.MAX_CELL_VOLTAGE * self.cell_count, 2 @@ -302,19 +304,19 @@ def manage_charge_voltage_linear(self) -> None: # calculate penalty sum to prevent single cell overcharge by using current cell voltage if ( - self.max_battery_voltage != self.bulk_battery_voltage + self.max_battery_voltage != self.soc_reset_battery_voltage and voltage > utils.MAX_CELL_VOLTAGE ): # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second foundHighCellVoltage = True penaltySum += voltage - utils.MAX_CELL_VOLTAGE elif ( - self.max_battery_voltage == self.bulk_battery_voltage - and voltage > utils.BULK_CELL_VOLTAGE + self.max_battery_voltage == self.soc_reset_battery_voltage + and voltage > utils.SOC_RESET_VOLTAGE ): # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second foundHighCellVoltage = True - penaltySum += voltage - utils.BULK_CELL_VOLTAGE + penaltySum += voltage - utils.SOC_RESET_VOLTAGE voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage() @@ -377,30 +379,32 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode = ( "Bulk dynamic" - # if self.max_voltage_start_time is None # remove this line after testing - if self.max_battery_voltage == self.bulk_battery_voltage + if self.max_voltage_start_time is None else "Absorption dynamic" ) + if self.max_battery_voltage == self.soc_reset_battery_voltage: + self.charge_mode += " & SoC Reset" + elif self.allow_max_voltage: self.control_voltage = round(self.max_battery_voltage, 3) self.charge_mode = ( - "Bulk" - # if self.max_voltage_start_time is None # remove this line after testing - if self.max_battery_voltage == self.bulk_battery_voltage - else "Absorption" + "Bulk" if self.max_voltage_start_time is None else "Absorption" ) + if self.max_battery_voltage == self.soc_reset_battery_voltage: + self.charge_mode += " & SoC Reset" + else: floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3) chargeMode = "Float" # reset bulk when going into float - if self.bulk_requested: - # logger.info("set bulk_requested to False") - self.bulk_requested = False - # IDEA: Save "bulk_last_reached" in the dbus path com.victronenergy.settings + if self.soc_reset_requested: + # logger.info("set soc_reset_requested to False") + self.soc_reset_requested = False + # IDEA: Save "soc_reset_last_reached" in the dbus path com.victronenergy.settings # to make it restart persistent - self.bulk_last_reached = current_time + self.soc_reset_last_reached = current_time if self.control_voltage: # check if battery changed from bulk/absoprtion to float if not self.charge_mode.startswith("Float"): @@ -465,16 +469,16 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode_debug += ( f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}" ) - bulk_days_ago = round( - (current_time - self.bulk_last_reached) / 60 / 60 / 24, 2 + soc_reset_days_ago = round( + (current_time - self.soc_reset_last_reached) / 60 / 60 / 24, 2 ) - bulk_in_days = round(utils.BULK_AFTER_DAYS - bulk_days_ago, 2) - self.charge_mode_debug += "\nbulk_last_reached: " + str( + soc_reset_in_days = round(utils.SOC_RESET_AFTER_DAYS - soc_reset_days_ago, 2) + self.charge_mode_debug += "\nsoc_reset_last_reached: " + str( "Never" - if self.bulk_last_reached == 0 - else str(bulk_days_ago) + if self.soc_reset_last_reached == 0 + else str(soc_reset_days_ago) + " days ago - next in " - + str(bulk_in_days) + + str(soc_reset_in_days) + "days" ) # """ @@ -545,6 +549,9 @@ def manage_charge_voltage_step(self) -> None: "Bulk" if self.max_voltage_start_time is None else "Absorption" ) + if self.max_battery_voltage == self.soc_reset_battery_voltage: + self.charge_mode += " & SoC Reset" + else: # check if battery changed from bulk/absoprtion to float if not self.charge_mode.startswith("Float"): @@ -553,10 +560,10 @@ def manage_charge_voltage_step(self) -> None: self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count self.charge_mode = "Float" # reset bulk when going into float - if self.bulk_requested: - # logger.info("set bulk_requested to False") - self.bulk_requested = False - self.bulk_last_reached = current_time + if self.soc_reset_requested: + # logger.info("set soc_reset_requested to False") + self.soc_reset_requested = False + self.soc_reset_last_reached = current_time self.charge_mode += " (Step Mode)" diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index f02fdeab..e432deb6 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -284,8 +284,8 @@ def to_protection_bits(self, byte_data): # MOSFET temperature alarm self.protection.temp_high_internal = 2 if is_bit_set(tmp[pos - 1]) else 0 # charge over voltage alarm - # TODO: check if "self.bulk_requested is False" works, - # else use "self.bulk_last_reached < int(time()) - (60 * 60)" + # TODO: check if "self.soc_reset_requested is False" works, + # else use "self.soc_reset_last_reached < int(time()) - (60 * 60)" self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0 # discharge under voltage alarm self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0 diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index dca4576d..d8ae2c4c 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -13,19 +13,19 @@ MAX_CELL_VOLTAGE = 3.450 ; Float voltage (can be seen as resting voltage) FLOAT_CELL_VOLTAGE = 3.375 -; Bulk voltage (may be needed to reset the SoC to 100% once in a while for some BMS) +; SOC reset voltage (may be needed to reset the SoC to 100% once in a while for some BMS) ; Has to be higher as the MAX_CELL_VOLTAGE -BULK_CELL_VOLTAGE = 3.650 -; Specify after how many days the bulk voltage should be reached again -; The timer is reset when the bulk voltage is reached +SOC_RESET_VOLTAGE = 3.650 +; Specify after how many days the soc reset voltage should be reached again +; The timer is reset when the soc reset voltage is reached ; Leave empty if you don't want to use this ; Example: Value is set to 15 -; day 1: bulk reached once -; day 16: bulk reached twice -; day 31: bulk not reached since it's very cloudy -; day 34: bulk reached since the sun came out -; day 49: bulk reached again, since last time it took 3 days to reach bulk voltage -BULK_AFTER_DAYS = +; day 1: soc reset reached once +; day 16: soc reset reached twice +; day 31: soc reset not reached since it's very cloudy +; day 34: soc reset reached since the sun came out +; day 49: soc reset reached again, since last time it took 3 days to reach soc reset voltage +SOC_RESET_AFTER_DAYS = ; --------- Bluetooth BMS --------- ; Description: List the Bluetooth BMS here that you want to install diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 42c3189f..88118de6 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -522,8 +522,8 @@ def publish_dbus(self): self._dbusservice["/Alarms/HighVoltage"] = ( self.battery.protection.voltage_high if ( - self.battery.bulk_requested is False - and self.battery.bulk_last_reached < int(time()) - (60 * 30) + self.battery.soc_reset_requested is False + and self.battery.soc_reset_last_reached < int(time()) - (60 * 30) ) else 0 ) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 019abe33..c38cbd9f 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -66,15 +66,15 @@ def _get_list_from_config( ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) -BULK_CELL_VOLTAGE = float(config["DEFAULT"]["BULK_CELL_VOLTAGE"]) -if BULK_CELL_VOLTAGE < MAX_CELL_VOLTAGE: - BULK_CELL_VOLTAGE = MAX_CELL_VOLTAGE +SOC_RESET_VOLTAGE = float(config["DEFAULT"]["SOC_RESET_VOLTAGE"]) +if SOC_RESET_VOLTAGE < MAX_CELL_VOLTAGE: + SOC_RESET_VOLTAGE = MAX_CELL_VOLTAGE logger.error( - ">>> ERROR: BULK_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." + ">>> ERROR: SOC_RESET_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration." ) -BULK_AFTER_DAYS = ( - int(config["DEFAULT"]["BULK_AFTER_DAYS"]) - if config["DEFAULT"]["BULK_AFTER_DAYS"] != "" +SOC_RESET_AFTER_DAYS = ( + int(config["DEFAULT"]["SOC_RESET_AFTER_DAYS"]) + if config["DEFAULT"]["SOC_RESET_AFTER_DAYS"] != "" else False ) From 64ef409d709aaf5fe1a2a7f62c30ae22f72cf056 Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 27 Sep 2023 11:08:45 +0200 Subject: [PATCH 100/114] fix typo --- etc/dbus-serialbattery/bms/jkbms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index e432deb6..99569e36 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -181,7 +181,7 @@ def read_status_data(self): 0 ].decode() self.production = "20" + tmp + "01" if tmp and tmp != "" else None - except UnicodeEncodeError: + except UnicodeDecodeError: self.production = None offset = cellbyte_count + 174 From dbdefc7b097faa88d2f6e43d367bd2f0a67d96b6 Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 27 Sep 2023 13:11:17 +0200 Subject: [PATCH 101/114] add JKBMS_BLE debugging data --- etc/dbus-serialbattery/bms/jkbms_brn.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 9f3f80ed..b1227d09 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -200,6 +200,8 @@ def decode_cellinfo_jk02(self): for t in TRANSLATE_CELL_INFO: self.translate(fb, t, self.bms_status, f32s=has32s) self.decode_warnings(fb) + logging.debug("decode_cellinfo_jk02(): self.frame_buffer") + logging.debug(self.frame_buffer) logging.debug(self.bms_status) def decode_settings_jk02(self): @@ -255,6 +257,10 @@ def set_callback(self, callback): self._new_data_callback = callback def assemble_frame(self, data: bytearray): + logging.debug( + f"--> assemble_frame() -> self.frame_buffer (before extend) -> lenght: {len(self.frame_buffer)}" + ) + logging.debug(self.frame_buffer) if len(self.frame_buffer) > MAX_RESPONSE_SIZE: logging.info( "data dropped because it alone was longer than max frame length" @@ -267,6 +273,10 @@ def assemble_frame(self, data: bytearray): self.frame_buffer.extend(data) + logging.debug( + f"--> assemble_frame() -> self.frame_buffer (after extend) -> lenght: {len(self.frame_buffer)}" + ) + logging.debug(self.frame_buffer) if len(self.frame_buffer) >= MIN_RESPONSE_SIZE: # check crc; always at position 300, independent of # actual frame-lentgh, so crc up to 299 @@ -282,6 +292,8 @@ def assemble_frame(self, data: bytearray): def ncallback(self, sender: int, data: bytearray): logging.debug(f"--> NEW PACKAGE! lenght: {len(data)}") + logging.debug("ncallback(): data") + logging.debug(data) self.assemble_frame(data) def crc(self, arr: bytearray, length: int) -> int: From dd61ed0b15812675da0aef8dbb29347ab63d5236 Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 27 Sep 2023 13:39:12 +0200 Subject: [PATCH 102/114] fix small error --- etc/dbus-serialbattery/dbushelper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 88118de6..5e47fef4 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -482,8 +482,10 @@ def publish_dbus(self): self._dbusservice["/System/Temperature4Name"] = utils.TEMP_4_NAME # Voltage control - self._dbusservice["/Info/MaxChargeVoltage"] = round( - self.battery.control_voltage + utils.VOLTAGE_DROP, 2 + self._dbusservice["/Info/MaxChargeVoltage"] = ( + round(self.battery.control_voltage + utils.VOLTAGE_DROP, 2) + if self.battery.control_voltage is not None + else None ) # Charge control From 8d8e793e5bddbd0dd5309727a8752ebb25980a4e Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 9 Oct 2023 08:22:04 +0200 Subject: [PATCH 103/114] added logging to config --- CHANGELOG.md | 1 + etc/dbus-serialbattery/config.default.ini | 7 +++++++ etc/dbus-serialbattery/utils.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d1f9ce..db26b426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ## v1.0.x * Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel +* Added: Configure logging level in `config.ini` by @mr-manuel * Added: Create unique identifier, if not provided from BMS by @mr-manuel * Added: Current average of the last 5 minutes by @mr-manuel * Added: Daly BMS - Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index d8ae2c4c..7317902e 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -1,5 +1,12 @@ [DEFAULT] +; --------- Set logging level --------- +; ERROR: Only errors are logged +; WARNING: Errors and warnings are logged +; INFO: Errors, warnings and info messages are logged +; DEBUG: Errors, warnings, info and debug messages are logged +LOGGING = INFO + ; --------- Battery Current limits --------- MAX_BATTERY_CHARGE_CURRENT = 50.0 MAX_BATTERY_DISCHARGE_CURRENT = 60.0 diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index c38cbd9f..0d85998b 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -13,7 +13,6 @@ # Logging logging.basicConfig() logger = logging.getLogger("SerialBattery") -logger.setLevel(logging.INFO) PATH_CONFIG_DEFAULT = "config.default.ini" PATH_CONFIG_USER = "config.ini" @@ -38,10 +37,20 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20230927dev" +DRIVER_VERSION = "1.0.20231009dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" +# get logging level from config file +if config["DEFAULT"]["LOGGING"] == "ERROR": + logging.basicConfig(level=logging.ERROR) +elif config["DEFAULT"]["LOGGING"] == "WARNING": + logging.basicConfig(level=logging.WARNING) +elif config["DEFAULT"]["LOGGING"] == "DEBUG": + logging.basicConfig(level=logging.DEBUG) +else: + logging.basicConfig(level=logging.INFO) + # save config values to constants # --------- Battery Current limits --------- From fbd6094a29f22d95a94cbc82230b0eb7dcb7432d Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 9 Oct 2023 09:32:47 +0200 Subject: [PATCH 104/114] add sleep before starting driver prevents lot of timeouts after reinstalling the driver, since the restart is now much faster than before --- etc/dbus-serialbattery/dbus-serialbattery.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 9119e850..e4bde35e 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -195,6 +195,9 @@ def get_port() -> str: battery = get_battery(port) else: + # wait some seconds to be sure that the serial connection is ready + # else the error throw a lot of timeouts + sleep(16) battery = get_battery(port) # exit if no battery could be found From 0671ae0a0063ac9fb42dd45359923a7410889daa Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 9 Oct 2023 12:15:06 +0200 Subject: [PATCH 105/114] changed post install info --- etc/dbus-serialbattery/config.default.ini | 16 +++++++++------- etc/dbus-serialbattery/reinstall-local.sh | 10 ++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 7317902e..26d91374 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -35,21 +35,23 @@ SOC_RESET_VOLTAGE = 3.650 SOC_RESET_AFTER_DAYS = ; --------- Bluetooth BMS --------- -; Description: List the Bluetooth BMS here that you want to install +; Description: Specify the Bluetooth BMS and it's MAC address that you want to install. Leave emty to disable ; -- Available Bluetooth BMS: ; Jkbms_Ble, LltJbd_Ble -; Example: -; 1 BMS: Jkbms_Ble C8:47:8C:00:00:00 -; 3 BMS: Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22 +; Example for one BMS: +; BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00 +; Example for multiple BMS: +; BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22 BLUETOOTH_BMS = ; --------- CAN BMS --------- ; Description: Specify the CAN port(s) where the BMS is connected to. Leave empty to disable ; -- Available CAN BMS: ; Daly_Can, Jkbms_Can -; Example: -; can0 -; can0, can8, can9 +; Example for one CAN port: +; CAN_PORT = can0 +; Example for multiple CAN ports: +; CAN_PORT = can0, can8, can9 CAN_PORT = ; --------- BMS disconnect behaviour --------- diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index 1532cf16..ffb4b25a 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -433,9 +433,8 @@ echo "SERIAL battery connection: The installation is complete. You don't have to echo echo "BLUETOOTH battery connection: There are a few more steps to complete installation." echo -echo " 1. Please add the Bluetooth BMS to the config file \"/data/etc/dbus-serialbattery/config.ini\" by adding \"BLUETOOTH_BMS\" = " -echo " Example with 1 BMS: BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00" -echo " Example with 3 BMS: BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22" +echo " 1. Add your Bluetooth BMS to the config file \"/data/etc/dbus-serialbattery/config.ini\"." +echo " Check the default config file \"/data/etc/dbus-serialbattery/config.default.ini\" for more informations." echo " If your Bluetooth BMS are nearby you can show the MAC address with \"bluetoothctl devices\"." echo echo " 2. Make sure to disable Bluetooth in \"Settings -> Bluetooth\" in the remote console/GUI to prevent reconnects every minute." @@ -448,9 +447,8 @@ echo " See https://wiki.debian.org/BluetoothUser#Using_bluetoothctl for more echo echo "CAN battery connection: There are a few more steps to complete installation." echo -echo " 1. Please add the CAN port to the config file \"/data/etc/dbus-serialbattery/config.ini\" by adding \"CAN_PORT\" = " -echo " Example with 1 CAN port: CAN_PORT = can0" -echo " Example with 3 CAN port: CAN_PORT = can0, can8, can9" +echo " 1. Add your CAN port to the config file \"/data/etc/dbus-serialbattery/config.ini\"." +echo " Check the default config file \"/data/etc/dbus-serialbattery/config.default.ini\" for more informations." echo echo " 2. Make sure to select a profile with 250 kbit/s in \"Settings -> Services -> VE.Can port -> CAN-bus profile\" in the remote console/GUI." echo From d0b1f25cc64b951df66e6604d54f57e0bfd8368f Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 9 Oct 2023 20:24:57 +0200 Subject: [PATCH 106/114] fix error --- etc/dbus-serialbattery/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 0d85998b..9acf65e0 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,19 +37,19 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20231009dev" +DRIVER_VERSION = "1.0.20231010dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" # get logging level from config file if config["DEFAULT"]["LOGGING"] == "ERROR": - logging.basicConfig(level=logging.ERROR) + logger.setLevel(logging.ERROR) elif config["DEFAULT"]["LOGGING"] == "WARNING": - logging.basicConfig(level=logging.WARNING) + logger.setLevel(logging.WARNING) elif config["DEFAULT"]["LOGGING"] == "DEBUG": - logging.basicConfig(level=logging.DEBUG) + logger.setLevel(logging.DEBUG) else: - logging.basicConfig(level=logging.INFO) + logger.setLevel(logging.INFO) # save config values to constants From 7c824db1099b1956e2c90625c677c220f0df9b41 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 10 Oct 2023 13:36:17 +0200 Subject: [PATCH 107/114] Daly BMS fixed embedded null byte https://github.com/Louisvdw/dbus-serialbattery/issues/837 --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/daly.py | 2 +- etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db26b426..aff026a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel * Changed: `VOLTAGE_DROP` now behaves differently. Before it reduced the voltage for the check, now the voltage for the charger is increased in order to get the target voltage on the BMS by @mr-manuel * Changed: Daly BMS - Fix readsentence by @transistorgit +* Changed: Daly BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/837 by @mr-manuel * Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel * Changed: Fixed Building wheel for dbus-fast won't finish on weak systems https://github.com/Louisvdw/dbus-serialbattery/issues/785 by @mr-manuel * Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index d68eac78..afd5c064 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -537,7 +537,7 @@ def read_battery_code(self, ser): self.custom_field = sub( " +", " ", - (battery_code.strip()), + (battery_code.replace("\x00", " ").strip()), ) return True diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 9acf65e0..e1ea26f1 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,7 +37,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20231010dev" +DRIVER_VERSION = "1.0.20231010dev2" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 631dfc96395a840c3b533ccb76d9d62b507966e3 Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 24 Oct 2023 08:11:55 +0200 Subject: [PATCH 108/114] added info for SoC reset to default config file --- etc/dbus-serialbattery/config.default.ini | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index 26d91374..af546e91 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -20,8 +20,18 @@ MAX_CELL_VOLTAGE = 3.450 ; Float voltage (can be seen as resting voltage) FLOAT_CELL_VOLTAGE = 3.375 -; SOC reset voltage (may be needed to reset the SoC to 100% once in a while for some BMS) -; Has to be higher as the MAX_CELL_VOLTAGE +; --------- SOC reset voltage --------- +; Description: May be needed to reset the SoC to 100% once in a while for some BMS, because of SoC drift. +; Specify the cell voltage where the SoC should be reset to 100% by the BMS. +; - JKBMS: SoC is reset to 100% if one cell reaches OVP (over voltage protection) voltage +; As you have to adopt this value to your system, I reccomend to start with +; OVP voltage - 0.030 (see Example). +; - Try to increase (add) by 0.005 in steps, if the system does not switch to float mode, even if +; the target voltage SOC_RESET_VOLTAGE * CELL_COUNT is reached. +; - Try to decrease (lower) by 0.005 in steps, if the system hits the OVP too fast, before all +; cells could be balanced and the system goes into protection mode multiple times. +; Example: If OVP is 3.650, then start with 3.620 and increase/decrease by 0.005 +; Note: The value has to be higher as the MAX_CELL_VOLTAGE SOC_RESET_VOLTAGE = 3.650 ; Specify after how many days the soc reset voltage should be reached again ; The timer is reset when the soc reset voltage is reached From 7c38c442cdc135d72d2e5f3d78154c9e3e2f0b22 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 2 Nov 2023 09:26:11 +0100 Subject: [PATCH 109/114] fix for #716 https://github.com/Louisvdw/dbus-serialbattery/issues/716 --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/jkbms_brn.py | 53 ++++++++++++++++++++++--- etc/dbus-serialbattery/utils.py | 2 +- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aff026a4..596f9612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ * Changed: Improved driver disable script by @md-manuel * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: JKBMS - Driver do not start if manufacturer date in BMS is empty https://github.com/Louisvdw/dbus-serialbattery/issues/823 by @mr-manuel +* Changed: JKBMS_BLE BMS - Fixed MOSFET Temperature for HW 11 by @jensbehrens & @mr-manuel * Changed: JKBMS_BLE BMS - Improved driver by @seidler2547 & @mr-manuel * Changed: LLT/JBD BMS - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein * Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/730 by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index b1227d09..bfd74b2d 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -57,7 +57,7 @@ ] -TRANSLATE_CELL_INFO = [ +TRANSLATE_CELL_INFO_16S = [ [["cell_info", "voltages", 32], 6, " 0 + if has32s: + self.bms_max_cell_count = 32 + self.translate_cell_info = TRANSLATE_CELL_INFO_32S + else: + self.bms_max_cell_count = 16 + self.translate_cell_info = TRANSLATE_CELL_INFO_16S + # iterative implementation maybe later due to referencing def translate(self, fb, translation, o, f32s=False, i=0): if i == len(translation[0]) - 1: @@ -196,8 +238,8 @@ 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: + has32s = self.bms_max_cell_count == 32 + for t in self.translate_cell_info: self.translate(fb, t, self.bms_status, f32s=has32s) self.decode_warnings(fb) logging.debug("decode_cellinfo_jk02(): self.frame_buffer") @@ -213,15 +255,16 @@ def decode_settings_jk02(self): def decode(self): # check what kind of info the frame contains info_type = self.frame_buffer[4] + self.get_bms_max_cell_count() if info_type == 0x01: logging.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): + for i, t in enumerate(self.translate_cell_info): if t[0][-2] == "voltages" or t[0][-2] == "voltages": - TRANSLATE_CELL_INFO[i][0][-1] = ccount + self.translate_cell_info[i][0][-1] = ccount self.bms_status["last_update"] = time() elif info_type == 0x02: diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index e1ea26f1..2f10a953 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,7 +37,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20231010dev2" +DRIVER_VERSION = "1.0.20231102dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 770781a7ce8a10adfb68078b7f835c1a197277b7 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 2 Nov 2023 16:35:04 +0100 Subject: [PATCH 110/114] fix for #716 and JKBMS model recognition https://github.com/Louisvdw/dbus-serialbattery/issues/716 --- etc/dbus-serialbattery/bms/jkbms_brn.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index bfd74b2d..856fe0fc 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -56,7 +56,6 @@ [["settings", "balancing_switch"], 126, "4?"], ] - TRANSLATE_CELL_INFO_16S = [ [["cell_info", "voltages", 32], 6, " 0 - if has32s: + + # old check to recognize 32s + # what does this check validate? + # unfortunately does not work on every system + # has32s = fb[189] == 0x00 and fb[189 + 32] > 0 + + # check where data starts + # for 32s it's at fb[70] + if fb[70] == 255 and fb[71] == 255: self.bms_max_cell_count = 32 self.translate_cell_info = TRANSLATE_CELL_INFO_32S + # for 16s it's at fb[54] else: self.bms_max_cell_count = 16 self.translate_cell_info = TRANSLATE_CELL_INFO_16S From f892231a46ba6bbfe7ba8c9fdc66e0bffe60fd25 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 2 Nov 2023 16:36:56 +0100 Subject: [PATCH 111/114] optimized logging --- CHANGELOG.md | 1 + etc/dbus-serialbattery/bms/jkbms_brn.py | 82 +++++++++++++++---------- etc/dbus-serialbattery/utils.py | 6 +- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 596f9612..30e52815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ * Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel * Changed: JKBMS - Driver do not start if manufacturer date in BMS is empty https://github.com/Louisvdw/dbus-serialbattery/issues/823 by @mr-manuel * Changed: JKBMS_BLE BMS - Fixed MOSFET Temperature for HW 11 by @jensbehrens & @mr-manuel +* Changed: JKBMS_BLE BMS - Fixed recognition of newer models where no data is shown by @mr-manuel * Changed: JKBMS_BLE BMS - Improved driver by @seidler2547 & @mr-manuel * Changed: LLT/JBD BMS - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein * Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/730 by @mr-manuel diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 856fe0fc..7ecc3fc8 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -2,11 +2,16 @@ from bleak import BleakScanner, BleakClient from time import sleep, time import asyncio -import logging import threading -logging.basicConfig(level=logging.INFO) +# if used as standalone script then use custom logger +# else import logger from utils +if __name__ == "__main__": + import logging + logger = logging.basicConfig(level=logging.DEBUG) +else: + from utils import logger # zero means parse all incoming data (every second) CELL_INFO_REFRESH_S = 0 @@ -138,18 +143,27 @@ def __init__(self, addr): async def scanForDevices(self): devices = await BleakScanner.discover() for d in devices: - logging.debug(d) + logger.debug(d) # check where the bms data starts and # if the bms is a 16s or 32s type def get_bms_max_cell_count(self): fb = self.frame_buffer + logger.debug(self.frame_buffer) # old check to recognize 32s # what does this check validate? # unfortunately does not work on every system # has32s = fb[189] == 0x00 and fb[189 + 32] > 0 + # logger can be removed after releasing next stable + # current version v1.0.20231102dev + logger.debug(f"fb[38]: {fb[36]}.{fb[37]}.{fb[38]}.{fb[39]}.{fb[40]}") + logger.debug(f"fb[54]: {fb[52]}.{fb[53]}.{fb[54]}.{fb[55]}.{fb[56]}") + logger.debug(f"fb[70]: {fb[68]}.{fb[69]}.{fb[70]}.{fb[71]}.{fb[72]}") + logger.debug(f"fb[134]: {fb[132]}.{fb[133]}.{fb[134]}.{fb[135]}.{fb[136]}") + logger.debug(f"fb[144]: {fb[142]}.{fb[143]}.{fb[144]}.{fb[145]}.{fb[146]}") + # check where data starts # for 32s it's at fb[70] if fb[70] == 255 and fb[71] == 255: @@ -160,6 +174,8 @@ def get_bms_max_cell_count(self): self.bms_max_cell_count = 16 self.translate_cell_info = TRANSLATE_CELL_INFO_16S + logger.debug(f"bms_max_cell_count recognized: {self.bms_max_cell_count}") + # iterative implementation maybe later due to referencing def translate(self, fb, translation, o, f32s=False, i=0): if i == len(translation[0]) - 1: @@ -249,22 +265,22 @@ def decode_cellinfo_jk02(self): for t in self.translate_cell_info: self.translate(fb, t, self.bms_status, f32s=has32s) self.decode_warnings(fb) - logging.debug("decode_cellinfo_jk02(): self.frame_buffer") - logging.debug(self.frame_buffer) - logging.debug(self.bms_status) + logger.debug("decode_cellinfo_jk02(): self.frame_buffer") + logger.debug(self.frame_buffer) + logger.debug(self.bms_status) def decode_settings_jk02(self): fb = self.frame_buffer for t in TRANSLATE_SETTINGS: self.translate(fb, t, self.bms_status) - logging.debug(self.bms_status) + logger.debug(self.bms_status) def decode(self): # check what kind of info the frame contains info_type = self.frame_buffer[4] self.get_bms_max_cell_count() if info_type == 0x01: - logging.info("Processing frame with settings info") + logger.debug("Processing frame with settings info") if protocol_version == PROTOCOL_VERSION_JK02: self.decode_settings_jk02() # adapt translation table for cell array lengths @@ -280,7 +296,7 @@ def decode(self): or time() - self.last_cell_info > CELL_INFO_REFRESH_S ): self.last_cell_info = time() - logging.info("processing frame with battery cell info") + logger.debug("processing frame with battery cell info") if protocol_version == PROTOCOL_VERSION_JK02: self.decode_cellinfo_jk02() self.bms_status["last_update"] = time() @@ -294,7 +310,7 @@ def decode(self): self.waiting_for_response = "" elif info_type == 0x03: - logging.info("processing frame with device info") + logger.debug("processing frame with device info") if protocol_version == PROTOCOL_VERSION_JK02: self.decode_device_info_jk02() self.bms_status["last_update"] = time() @@ -307,12 +323,12 @@ def set_callback(self, callback): self._new_data_callback = callback def assemble_frame(self, data: bytearray): - logging.debug( + logger.debug( f"--> assemble_frame() -> self.frame_buffer (before extend) -> lenght: {len(self.frame_buffer)}" ) - logging.debug(self.frame_buffer) + logger.debug(self.frame_buffer) if len(self.frame_buffer) > MAX_RESPONSE_SIZE: - logging.info( + logger.debug( "data dropped because it alone was longer than max frame length" ) self.frame_buffer = [] @@ -323,27 +339,27 @@ def assemble_frame(self, data: bytearray): self.frame_buffer.extend(data) - logging.debug( + logger.debug( f"--> assemble_frame() -> self.frame_buffer (after extend) -> lenght: {len(self.frame_buffer)}" ) - logging.debug(self.frame_buffer) + logger.debug(self.frame_buffer) if len(self.frame_buffer) >= MIN_RESPONSE_SIZE: # check crc; always at position 300, independent of # actual frame-lentgh, so crc up to 299 ccrc = self.crc(self.frame_buffer, 300 - 1) rcrc = self.frame_buffer[300 - 1] - logging.debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}") + logger.debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}") if ccrc == rcrc: - logging.debug("great success! frame complete and sane, lets decode") + logger.debug("great success! frame complete and sane, lets decode") self.decode() self.frame_buffer = [] if self._new_data_callback is not None: self._new_data_callback() def ncallback(self, sender: int, data: bytearray): - logging.debug(f"--> NEW PACKAGE! lenght: {len(data)}") - logging.debug("ncallback(): data") - logging.debug(data) + logger.debug(f"--> NEW PACKAGE! lenght: {len(data)}") + logger.debug("ncallback(): data") + logger.debug(data) self.assemble_frame(data) def crc(self, arr: bytearray, length: int) -> int: @@ -381,7 +397,7 @@ async def write_register( frame[17] = 0x00 frame[18] = 0x00 frame[19] = self.crc(frame, len(frame) - 1) - logging.debug("Write register: " + str(address) + " " + str(frame)) + logger.debug("Write register: " + str(address) + " " + str(frame)) await bleakC.write_gatt_char(CHAR_HANDLE, frame, response=awaitresponse) if awaitresponse: await asyncio.sleep(5) @@ -391,7 +407,7 @@ async def request_bt(self, rtype: str, client): while self.waiting_for_response != "" and time() - timeout < 10: await asyncio.sleep(1) - logging.debug(self.waiting_for_response) + logger.debug(self.waiting_for_response) if rtype == "cell_info": cmd = COMMAND_CELL_INFO @@ -415,16 +431,16 @@ def connect_and_scrape(self): # self.bt_thread async def asy_connect_and_scrape(self): - logging.debug( + logger.debug( "--> asy_connect_and_scrape(): Connect and scrape on address: " + self.address ) self.run = True while self.run and self.main_thread.is_alive(): # autoreconnect client = BleakClient(self.address) - logging.debug("--> asy_connect_and_scrape(): btloop") + logger.debug("--> asy_connect_and_scrape(): btloop") try: - logging.debug("--> asy_connect_and_scrape(): reconnect") + logger.debug("--> asy_connect_and_scrape(): reconnect") await client.connect() self.bms_status["model_nbr"] = ( await client.read_gatt_char(MODEL_NBR_UUID) @@ -443,7 +459,7 @@ async def asy_connect_and_scrape(self): await asyncio.sleep(0.01) except Exception as err: self.run = False - logging.info( + logger.info( f"--> asy_connect_and_scrape(): error while connecting to bt: {err}" ) finally: @@ -452,19 +468,19 @@ async def asy_connect_and_scrape(self): try: await client.disconnect() except Exception as err: - logging.info( + logger.info( f"--> asy_connect_and_scrape(): error while disconnecting: {err}" ) - logging.info("--> asy_connect_and_scrape(): Exit") + logger.info("--> asy_connect_and_scrape(): Exit") def start_scraping(self): self.main_thread = threading.current_thread() if self.is_running(): - logging.info("screaping thread already running") + logger.debug("screaping thread already running") return self.bt_thread.start() - logging.info( + logger.debug( "scraping thread started -> main thread id: " + str(self.main_thread.ident) + " scraping thread: " @@ -529,7 +545,7 @@ async def reset_soc_jk(self, c): True, ) - logging.info("JK BMS SOC reset finished.") + logger.info("JK BMS SOC reset finished.") if __name__ == "__main__": @@ -537,9 +553,9 @@ async def reset_soc_jk(self, c): jk = Jkbms_Brn(sys.argv[1]) if not jk.test_connection(): - logging.error(">>> ERROR: Unable to connect") + logger.error(">>> ERROR: Unable to connect") else: jk.start_scraping() while True: - logging.debug(jk.get_status()) + logger.debug(jk.get_status()) sleep(5) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 2f10a953..a609225a 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -42,11 +42,11 @@ def _get_list_from_config( degree_sign = "\N{DEGREE SIGN}" # get logging level from config file -if config["DEFAULT"]["LOGGING"] == "ERROR": +if config["DEFAULT"]["LOGGING"].upper() == "ERROR": logger.setLevel(logging.ERROR) -elif config["DEFAULT"]["LOGGING"] == "WARNING": +elif config["DEFAULT"]["LOGGING"].upper() == "WARNING": logger.setLevel(logging.WARNING) -elif config["DEFAULT"]["LOGGING"] == "DEBUG": +elif config["DEFAULT"]["LOGGING"].upper() == "DEBUG": logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) From fb9b213581cc01fd27a1c163d2d150048f1991a0 Mon Sep 17 00:00:00 2001 From: Manuel Date: Fri, 3 Nov 2023 21:30:20 +0100 Subject: [PATCH 112/114] fix JKBMS recognition --- etc/dbus-serialbattery/bms/jkbms_brn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 7ecc3fc8..5c118cf7 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -166,7 +166,7 @@ def get_bms_max_cell_count(self): # check where data starts # for 32s it's at fb[70] - if fb[70] == 255 and fb[71] == 255: + if fb[70] == 255: self.bms_max_cell_count = 32 self.translate_cell_info = TRANSLATE_CELL_INFO_32S # for 16s it's at fb[54] From a12a27c5718b4b9038adfb2ae6114447ae6bc1d6 Mon Sep 17 00:00:00 2001 From: Manuel Date: Fri, 3 Nov 2023 21:30:45 +0100 Subject: [PATCH 113/114] added debugging --- etc/dbus-serialbattery/bms/daly.py | 20 +++++++++++++------- etc/dbus-serialbattery/bms/jkbms.py | 2 ++ etc/dbus-serialbattery/bms/jkbms_brn.py | 9 ++++++--- etc/dbus-serialbattery/bms/lltjbd.py | 6 +++--- etc/dbus-serialbattery/dbus-serialbattery.py | 4 +++- etc/dbus-serialbattery/utils.py | 6 +++++- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index afd5c064..0c033254 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -404,7 +404,7 @@ def read_cells_volts(self, ser): for idx in range(self.cell_count): self.cells.append(Cell(True)) - # logger.warning("data " + bytes(cells_volts_data).hex()) + # logger.warning("data " + bytearray_to_string(cells_volts_data)) # from each of the received sentences, read up to 3 voltages for i in range(sentences_expected): @@ -526,7 +526,7 @@ def read_battery_code(self, ser): return False battery_code = "" - # logger.warning("data " + bytes(cells_volts_data).hex()) + # logger.warning("data " + utils.bytearray_to_string(cells_volts_data)) for i in range(5): nr, part = unpack_from(">B7s", data, i * 8) if nr != i + 1: @@ -720,7 +720,7 @@ def read_sentence(self, ser, expected_reply, timeout=0.5): reply = ser.read_until(b"\xA5") if not reply or b"\xA5" not in reply: logger.debug( - f"read_sentence {bytes(expected_reply).hex()}: no sentence start received" + f"read_sentence {utils.bytearray_to_string(expected_reply)}: no sentence start received" ) return False @@ -732,21 +732,27 @@ def read_sentence(self, ser, expected_reply, timeout=0.5): toread = ser.inWaiting() time_run = time() - time_start if time_run > timeout: - logger.debug(f"read_sentence {bytes(expected_reply).hex()}: timeout") + logger.debug( + f"read_sentence {utils.bytearray_to_string(expected_reply)}: timeout" + ) return False reply += ser.read(12) _, id, cmd, length = unpack_from(">BBBB", reply) - # logger.info(f"reply: {bytes(reply).hex()}") # debug + # logger.info(f"reply: {utils.bytearray_to_string(reply)}") # debug if id != 1 or length != 8 or cmd != expected_reply[0]: - logger.debug(f"read_sentence {bytes(expected_reply).hex()}: wrong header") + logger.debug( + f"read_sentence {utils.bytearray_to_string(expected_reply)}: wrong header" + ) return False chk = unpack_from(">B", reply, 12)[0] if sum(reply[:12]) & 0xFF != chk: - logger.debug(f"read_sentence {bytes(expected_reply).hex()}: wrong checksum") + logger.debug( + f"read_sentence {utils.bytearray_to_string(expected_reply)}: wrong checksum" + ) return False return reply[4:12] diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py index 99569e36..0a391d39 100644 --- a/etc/dbus-serialbattery/bms/jkbms.py +++ b/etc/dbus-serialbattery/bms/jkbms.py @@ -343,6 +343,8 @@ def read_serial_data_jkbms(self, command: str) -> bool: s = sum(data[0:-4]) + logger.debug("bytearray: " + utils.bytearray_to_string(data)) + if start == 0x4E57 and end == 0x68 and s == crc_lo: return data[10 : length - 7] elif s != crc_lo: diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index 5c118cf7..b4f6fb38 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -10,8 +10,12 @@ import logging logger = logging.basicConfig(level=logging.DEBUG) + + def bytearray_to_string(data): + return "".join("\\x" + format(byte, "02x") for byte in data) + else: - from utils import logger + from utils import bytearray_to_string, logger # zero means parse all incoming data (every second) CELL_INFO_REFRESH_S = 0 @@ -358,8 +362,7 @@ def assemble_frame(self, data: bytearray): def ncallback(self, sender: int, data: bytearray): logger.debug(f"--> NEW PACKAGE! lenght: {len(data)}") - logger.debug("ncallback(): data") - logger.debug(data) + logger.debug("ncallback(): " + bytearray_to_string(data)) self.assemble_frame(data) def crc(self, arr: bytearray, length: int) -> int: diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py index 5c658fac..24399a40 100644 --- a/etc/dbus-serialbattery/bms/lltjbd.py +++ b/etc/dbus-serialbattery/bms/lltjbd.py @@ -610,13 +610,13 @@ def read_hardware_data(self): @staticmethod def validate_packet(data): - if not data: - return False - if data is False: return False start, op, status, payload_length = unpack_from("BBBB", data) + + logger.debug("bytearray: " + utils.bytearray_to_string(data)) + if start != 0xDD: logger.error( ">>> ERROR: Invalid response packet. Expected begin packet character 0xDD" diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index e4bde35e..69c90437 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -96,7 +96,9 @@ def get_battery(_port) -> Union[Battery, None]: "Testing " + test["bms"].__name__ + ( - ' at address "' + f"\\x{bytes(test['address']).hex()}" + '"' + ' at address "' + + utils.bytearray_to_string(test["address"]) + + '"' if "address" in test else "" ) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index a609225a..19b01a24 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,7 +37,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20231102dev" +DRIVER_VERSION = "1.0.20231103dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -318,6 +318,10 @@ def kelvin_to_celsius(kelvin_temp): return kelvin_temp - 273.1 +def bytearray_to_string(data): + return "".join("\\x" + format(byte, "02x") for byte in data) + + def format_value(value, prefix, suffix): return ( None From 04c74562f6d920c065c9e71197d92c3d8cce9683 Mon Sep 17 00:00:00 2001 From: Manuel Date: Fri, 17 Nov 2023 22:18:36 +0100 Subject: [PATCH 114/114] fixes #716 https://github.com/Louisvdw/dbus-serialbattery/issues/716 --- etc/dbus-serialbattery/bms/jkbms_brn.py | 16 ++++++++-------- etc/dbus-serialbattery/utils.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py index b4f6fb38..f0c29ee1 100644 --- a/etc/dbus-serialbattery/bms/jkbms_brn.py +++ b/etc/dbus-serialbattery/bms/jkbms_brn.py @@ -65,7 +65,7 @@ def bytearray_to_string(data): [["settings", "balancing_switch"], 126, "4?"], ] -TRANSLATE_CELL_INFO_16S = [ +TRANSLATE_CELL_INFO_24S = [ [["cell_info", "voltages", 32], 6, " 0: self.bms_max_cell_count = 32 self.translate_cell_info = TRANSLATE_CELL_INFO_32S - # for 16s it's at fb[54] + # if BMS has a max of 24s the data ends at fb[219] else: - self.bms_max_cell_count = 16 - self.translate_cell_info = TRANSLATE_CELL_INFO_16S + self.bms_max_cell_count = 24 + self.translate_cell_info = TRANSLATE_CELL_INFO_24S logger.debug(f"bms_max_cell_count recognized: {self.bms_max_cell_count}") diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 19b01a24..df4ce2ab 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,7 +37,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.0.20231103dev" +DRIVER_VERSION = "1.0.20231117dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}"