From 2f354486a259517a9d7461e932082087615802f3 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 8 Jun 2023 09:23:24 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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