From 1e6d4100e1d1360b579dd0caeae93ff51f0d839a Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 14:14:46 +0200
Subject: [PATCH 01/17] Improved JBD BMS soc calculation
 https://github.com/Louisvdw/dbus-serialbattery/pull/439

---
 CHANGELOG.md                     | 1 +
 etc/dbus-serialbattery/lltjbd.py | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 391dfecb..e6243b61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@
 * Changed: Fixed black lint errors
 * Changed: Fixed cell balancing background for cells 17-24
 * Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018
+* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439
 * Changed: Logging to get relevant data
 * Changed: Moved ble part to `installble.sh`
 * Changed: Optimized installation scripts
diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py
index 2a3d536c..a8222fd6 100644
--- a/etc/dbus-serialbattery/lltjbd.py
+++ b/etc/dbus-serialbattery/lltjbd.py
@@ -138,13 +138,14 @@ def read_gen_data(self):
             balance2,
             protection,
             version,
-            self.soc,
+            soc,
             fet,
             self.cell_count,
             self.temp_sensors,
         ) = unpack_from(">HhHHHHhHHBBBBB", gen_data)
         self.voltage = voltage / 100
         self.current = current / 100
+        self.soc = 100 * capacity_remain / capacity
         self.capacity_remain = capacity_remain / 100
         self.capacity = capacity / 100
         self.to_cell_bits(balance, balance2)

From 922b82c42599f5c19b7df23e5f7d56d119e9d82c Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 14:20:39 +0200
Subject: [PATCH 02/17] Fix for #397
 https://github.com/Louisvdw/dbus-serialbattery/pull/484

---
 CHANGELOG.md                    |  3 ++-
 etc/dbus-serialbattery/daly.py  | 32 ++++++++++++++++++++------------
 etc/dbus-serialbattery/utils.py |  6 ++++--
 3 files changed, 26 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6243b61..12d2ee32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,11 +40,12 @@
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351
+* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/397 by @transistorgit
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421
 * Changed: Fixed black lint errors
 * Changed: Fixed cell balancing background for cells 17-24
 * Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018
-* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439
+* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 by @aaronreek
 * Changed: Logging to get relevant data
 * Changed: Moved ble part to `installble.sh`
 * Changed: Optimized installation scripts
diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py
index ae96c723..492b0500 100644
--- a/etc/dbus-serialbattery/daly.py
+++ b/etc/dbus-serialbattery/daly.py
@@ -20,7 +20,7 @@ def __init__(self, port, baud, address):
 
     # 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"
-    cellvolt_buffer = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+    cellvolt_buffer = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x82"
     command_soc = b"\x90"
     command_minmax_cell_volts = b"\x91"
     command_minmax_temp = b"\x92"
@@ -31,7 +31,7 @@ def __init__(self, port, baud, address):
     command_cell_balance = b"\x97"
     command_alarm = b"\x98"
     BATTERYTYPE = "Daly"
-    LENGTH_CHECK = 4
+    LENGTH_CHECK = 1
     LENGTH_POS = 3
     CURRENT_ZERO_CONSTANT = 30000
     TEMP_ZERO_CONSTANT = 40
@@ -246,13 +246,16 @@ def read_cells_volts(self, ser):
             buffer[1] = self.command_address[0]  # Always serial 40 or 80
             buffer[2] = self.command_cell_volts[0]
 
-            maxFrame = int(self.cell_count / 3) + 1
+            if (int(self.cell_count) % 3) == 0:
+                maxFrame = (int(self.cell_count / 3))
+            else:
+                maxFrame = (int(self.cell_count / 3) + 1)
             lenFixed = (
-                maxFrame * 12
-            )  # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved
+                maxFrame * 13
+            )  # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + chksum
 
             cells_volts_data = read_serialport_data(
-                ser, buffer, self.LENGTH_POS, self.LENGTH_CHECK, lenFixed
+                ser, buffer, self.LENGTH_POS, 0, lenFixed
             )
             if cells_volts_data is False:
                 logger.warning("read_cells_volts")
@@ -269,14 +272,19 @@ 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())
+
             while (
                 bufIdx < len(cells_volts_data) - 4
             ):  # we at least need 4 bytes to extract the identifiers
                 b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx)
                 if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08:
-                    frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from(
-                        ">Bhhh", cells_volts_data, bufIdx + 4
+                    frame, frameCell[0], frameCell[1], frameCell[2], _, chk = unpack_from(
+                        ">BhhhBB", cells_volts_data, bufIdx + 4
                     )
+                    if sum(cells_volts_data[bufIdx:bufIdx+12]) & 0xFF != chk:
+                        logger.warning("bad cell voltages checksum")
+                        return False
                     for idx in range(3):
                         cellnum = (
                             (frame - 1) * 3
@@ -287,9 +295,9 @@ def read_cells_volts(self, ser):
                         self.cells[cellnum].voltage = (
                             None if cellVoltage < lowMin else cellVoltage
                         )
-                    bufIdx += 10  # BBBBBhhh -> 11 byte
-                bufIdx += 1
-
+                    bufIdx += 13  # BBBBBhhhBB -> 13 byte
+                else:
+                    logger.warning("bad cell voltages header")
         return True
 
     def read_cell_voltage_range_data(self, ser):
@@ -359,7 +367,7 @@ def read_serial_data_daly(self, ser, command):
         start, flag, command_ret, length = unpack_from("BBBB", data)
         checksum = sum(data[:-1]) & 0xFF
 
-        if start == 165 and length == 8 and checksum == data[12]:
+        if start == 165 and length == 8 and len(data)>12 and checksum == data[12]:
             return data[4 : length + 4]
         else:
             logger.error(">>> ERROR: Incorrect Reply")
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index 5bd66408..7cf6eac6 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -441,8 +441,10 @@ def read_serialport_data(
 
         count = 0
         data = bytearray(res)
-        while len(data) <= length + length_check:
-            res = ser.read(length + length_check)
+
+        packetlen = length_fixed if length_fixed is not None else length_pos + length_byte_size + length + length_check
+        while len(data) < packetlen:
+            res = ser.read(packetlen - len(data))
             data.extend(res)
             # logger.info('serial data length ' + str(len(data)))
             sleep(0.005)

From 23356f6fbe4d6668e685a49e989b71c97264e304 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 14:20:57 +0200
Subject: [PATCH 03/17] small fixes

---
 .github/workflows/release.yml     | 1 +
 etc/dbus-serialbattery/battery.py | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0b6a57da..a9ad92fb 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -24,6 +24,7 @@ jobs:
             --exclude restartservice.sh \
             --exclude revov.py \
             --exclude test_max17853.py \
+            --exclude util_max17853.py \
             etc/dbus-serialbattery/
 
       - name: Release
diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index 54417720..1578a8c9 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -828,8 +828,8 @@ def log_settings(self) -> None:
         )
         logger.info(f"> LINEAR LIMITATION ENABLE: {utils.LINEAR_LIMITATION_ENABLE}")
         logger.info(
-            f"> MAX BATTERY CHARGE CURRENT: {utils.MAX_BATTERY_CHARGE_CURRENT}V | "
-            + f"MAX BATTERY DISCHARGE CURRENT: {utils.MAX_BATTERY_DISCHARGE_CURRENT}V"
+            f"> MAX BATTERY CHARGE CURRENT: {utils.MAX_BATTERY_CHARGE_CURRENT}A | "
+            + f"MAX BATTERY DISCHARGE CURRENT: {utils.MAX_BATTERY_DISCHARGE_CURRENT}A"
         )
         logger.info(f"> CVCM:     {utils.CVCM_ENABLE}")
         logger.info(

From 4bd257f157d889378cedaa4e654f2fb7102edff9 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 14:53:15 +0200
Subject: [PATCH 04/17] sort bms imports

---
 etc/dbus-serialbattery/dbus-serialbattery.py | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index fdcc05a5..bb0df250 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -19,28 +19,29 @@
 from utils import logger
 import utils
 from battery import Battery
-from lltjbd import LltJbd
+
+# import battery classes
+from ant import Ant
 from daly import Daly
+from ecs import Ecs
 from ant import Ant
 from jkbms import Jkbms
-
-# from sinowealth import Sinowealth
-from renogy import Renogy
-from ecs import Ecs
 from lifepower import Lifepower
-
+from lltjbd import LltJbd
+from renogy import Renogy
+# from sinowealth import Sinowealth
 
 supported_bms_types = [
-    {"bms": LltJbd, "baud": 9600},
     {"bms": Ant, "baud": 19200},
     {"bms": Daly, "baud": 9600, "address": b"\x40"},
     {"bms": Daly, "baud": 9600, "address": b"\x80"},
+    {"bms": Ecs, "baud": 19200},
     {"bms": Jkbms, "baud": 115200},
-    #    {"bms" : Sinowealth},
     {"bms": Lifepower, "baud": 9600},
+    {"bms": LltJbd, "baud": 9600},
     {"bms": Renogy, "baud": 9600, "address": b"\x30"},
     {"bms": Renogy, "baud": 9600, "address": b"\xF7"},
-    {"bms": Ecs, "baud": 19200},
+    # {"bms": Sinowealth},
 ]
 expected_bms_types = [
     battery_type

From c56a9d107946313b4509c1f0c60d919a32c05636 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 14:56:15 +0200
Subject: [PATCH 05/17] Add support for HLPdata BMS4S
 https://github.com/Louisvdw/dbus-serialbattery/pull/505

---
 etc/dbus-serialbattery/dbus-serialbattery.py  |    3 +-
 etc/dbus-serialbattery/hlpdatabms4s.py        |  240 ++++
 .../hlpdatabms4s_miniterm.py                  | 1189 +++++++++++++++++
 3 files changed, 1431 insertions(+), 1 deletion(-)
 create mode 100644 etc/dbus-serialbattery/hlpdatabms4s.py
 create mode 100644 etc/dbus-serialbattery/hlpdatabms4s_miniterm.py

diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index bb0df250..b1a034bd 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -24,7 +24,7 @@
 from ant import Ant
 from daly import Daly
 from ecs import Ecs
-from ant import Ant
+from hlpdatabms4s import HLPdataBMS4S
 from jkbms import Jkbms
 from lifepower import Lifepower
 from lltjbd import LltJbd
@@ -36,6 +36,7 @@
     {"bms": Daly, "baud": 9600, "address": b"\x40"},
     {"bms": Daly, "baud": 9600, "address": b"\x80"},
     {"bms": Ecs, "baud": 19200},
+    {'bms': HLPdataBMS4S, "baud": 9600},
     {"bms": Jkbms, "baud": 115200},
     {"bms": Lifepower, "baud": 9600},
     {"bms": LltJbd, "baud": 9600},
diff --git a/etc/dbus-serialbattery/hlpdatabms4s.py b/etc/dbus-serialbattery/hlpdatabms4s.py
new file mode 100644
index 00000000..63995357
--- /dev/null
+++ b/etc/dbus-serialbattery/hlpdatabms4s.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+from battery import Protection, Battery, Cell
+from utils import *
+from struct import *
+import serial
+from time import sleep
+
+
+class HLPdataBMS4S(Battery):
+    def __init__(self, port, baud):
+        super(HLPdataBMS4S, self).__init__(port, baud)
+        self.type = self.BATTERYTYPE
+
+    BATTERYTYPE = "HLPdataBMS4S"
+
+    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
+        try:
+            result = self.read_test_data()
+        except Exception as e:
+            # logger.error(e, exc_info=True)
+            pass
+        return result
+
+    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
+        result = False
+        try:
+            result = self.read_settings_data()
+        except Exception as e:
+            # logger.error(e, exc_info=True)
+            pass
+        return result
+
+    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 = False
+        try:
+            result = self.read_status_data()
+        except Exception as e:
+            # logger.error(e, exc_info=True)
+            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)
+        if test_data is False:
+            return False
+        s1 = str(test_data)
+        ix = s1.find("BMS4S")
+        if ix > 0:
+            self.hardware_version = s1[ix : len(s1) - 1]
+            self.version = self.hardware_version
+            self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
+            self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+            self.poll_interval = 10000
+            self.control_discharge_current = 1000
+            self.control_charge_current = 1000
+            self.soc = 50
+            self.voltage = 13.2
+            self.current = 0
+            self.min_battery_voltage = 12.0
+            self.max_battery_voltage = 14.4
+
+            if self.cell_count is None:
+                self.cell_count = 4
+                for c in range(self.cell_count):
+                    self.cells.append(Cell(False))
+            return True
+        return False
+
+    def read_settings_data(self):
+        test_data = self.read_serial_data_HLPdataBMS4S(b"ps\n", 3, 700)
+        if test_data is False:
+            return False
+        s = str(test_data)
+        s = s.replace(",", ".")
+        par = get_par("BatterySize= ", s)
+        if par is False:
+            return False
+        self.capacity = int(par)
+        v = get_par("VoltHigh= ", s)
+        if v is False:
+            return False
+        self.max_battery_voltage = float(v) * float(4)
+        v = get_par("VoltLow= ", s)
+        if v is False:
+            return False
+        self.min_battery_voltage = float(v) * float(4)
+
+        return True
+
+    def read_status_data(self):
+        status_data = self.read_serial_data_HLPdataBMS4S(b"m1\n", 0.2, 40)
+        if status_data is False:
+            return False
+        par1 = str(status_data)
+        par = par1.split(",")
+        if len(par) < 8:
+            return False
+        if len(par[0]) < 7:
+            return False
+        p0 = str(par[0])
+        ix = p0.find(".")
+        par0 = p0[ix - 1 : len(p0)]
+
+        # v1,v2,v3,v4,current,soc,chargeoff,loadoff,vbat2,socnow,adj,beep,led,temp1,temp2...
+        # 0  1  2  3  4       5   6         7       8     9      10  11   12  13    14...
+
+        self.voltage = float(par0) + float(par[1]) + float(par[2]) + float(par[3])
+        self.cells[0].voltage = float(par0)
+        self.cells[1].voltage = float(par[1])
+        self.cells[2].voltage = float(par[2])
+        self.cells[3].voltage = float(par[3])
+        self.current = float(par[4])
+        self.soc = int(par[5])
+        self.control_allow_charge = par[6]
+        self.charge_fet = par[6]
+        self.control_allow_discharge = par[7]
+        self.discharge_fet = par[7]
+
+        beep = int(par[11])
+        if beep == 2:
+            self.protection.temp_low_charge = 1
+        else:
+            self.protection.temp_low_charge = 0
+        if beep == 3:
+            self.protection.temp_high_charge = 1
+        else:
+            self.protection.temp_high_charge = 0
+        if beep == 4:
+            self.protection.voltage_low = 2
+        else:
+            self.protection.voltage_low = 0
+        if beep == 5:
+            self.protection.voltage_high = 2
+        else:
+            self.protection.voltage_high = 0
+
+        if len(par) > 13:
+            nb = 0
+            min = int(1000)
+            max = int(-1000)
+            ix = 13
+            while ix < len(par):
+                tmp = par[ix].split(" ")
+                ix += 1
+                if len(tmp) == 2:
+                    name = tmp[0]
+                    temp = int("".join(filter(str.isdigit, tmp[1])))
+                    if name[0] == "b":
+                        nb += 1
+                        if temp > max:
+                            max = temp
+                        if temp < min:
+                            min = temp
+            if nb == 1:
+                self.temp1 = max
+            if nb > 1:
+                self.temp1 = max
+                self.temp2 = min
+
+        return True
+
+    def manage_charge_voltage(self):
+        self.allow_max_voltage = True
+        self.control_voltage = self.max_battery_voltage
+
+    def manage_charge_current(self):
+        self.control_charge_current = 1000
+        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)
+        if data is False:
+            return False
+        return data
+
+
+def read_serial_data2(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)
+            if not ret is False:
+                return ret
+        return False
+
+    except serial.SerialException as e:
+        logger.error(e)
+        return False
+
+
+def read_serialport_data2(ser, command, time, min_len):
+    try:
+        cnt = 0
+        while cnt < 3:
+            cnt += 1
+            ser.flushOutput()
+            ser.flushInput()
+            ser.write(command)
+            sleep(time)
+            res = ser.read(1000)
+            if len(res) >= min_len:
+                return res
+        return False
+
+    except serial.SerialException as e:
+        logger.error(e)
+        return False
+
+
+def get_par(p, s):
+    ix = s.find(p)
+    if ix > 0:
+        ix += len(p)
+        for i in range(ix, len(s)):
+            if s[i] == " " or s[i] == 10 or s[i] == 13:
+                ret = s[ix:i]
+                return ret
+    return False
diff --git a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py
new file mode 100644
index 00000000..741ac75a
--- /dev/null
+++ b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py
@@ -0,0 +1,1189 @@
+#!/usr/bin/env python
+#
+# Very simple serial terminal
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C)2002-2020 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+#
+# Modified to enable managing HLPdataBMS4S in Venus OS
+
+from __future__ import absolute_import
+
+import codecs
+import os
+import sys
+import threading
+
+import serial
+from serial.tools.list_ports import comports
+from serial.tools import hexlify_codec
+from time import sleep
+
+# pylint: disable=wrong-import-order,wrong-import-position
+
+codecs.register(lambda c: hexlify_codec.getregentry() if c == "hexlify" else None)
+
+try:
+    raw_input
+except NameError:
+    # pylint: disable=redefined-builtin,invalid-name
+    raw_input = input  # in python3 it's "raw"
+    unichr = chr
+
+
+def key_description(character):
+    """generate a readable description for a key"""
+    ascii_code = ord(character)
+    if ascii_code < 32:
+        return "Ctrl+{:c}".format(ord("@") + ascii_code)
+    else:
+        return repr(character)
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+class ConsoleBase(object):
+    """OS abstraction for console (input/output codec, no echo)"""
+
+    def __init__(self, miniterm):
+        self.miniterm = miniterm
+        if sys.version_info >= (3, 0):
+            self.byte_output = sys.stdout.buffer
+        else:
+            self.byte_output = sys.stdout
+        self.output = sys.stdout
+
+    def setup(self):
+        """Set console to read single characters, no echo"""
+
+    def cleanup(self):
+        """Restore default console settings"""
+
+    def getkey(self):
+        """Read a single key from the console"""
+        return None
+
+    def write_bytes(self, byte_string):
+        """Write bytes (already encoded)"""
+        self.byte_output.write(byte_string)
+        self.byte_output.flush()
+
+    def write(self, text):
+        """Write string"""
+        self.output.write(text)
+        self.output.flush()
+
+    def cancel(self):
+        """Cancel getkey operation"""
+
+    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
+    # context manager:
+    # switch terminal temporary to normal mode (e.g. to get user input)
+
+    def __enter__(self):
+        self.cleanup()
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        self.setup()
+
+
+if os.name == "nt":  # noqa
+    import msvcrt
+    import ctypes
+    import platform
+
+    class Out(object):
+        """file-like wrapper that uses os.write"""
+
+        def __init__(self, fd):
+            self.fd = fd
+
+        def flush(self):
+            pass
+
+        def write(self, s):
+            os.write(self.fd, s)
+
+    class Console(ConsoleBase):
+        fncodes = {
+            ";": "\x1bOP",  # F1
+            "<": "\x1bOQ",  # F2
+            "=": "\x1bOR",  # F3
+            ">": "\x1bOS",  # F4
+            "?": "\x1b[15~",  # F5
+            "@": "\x1b[17~",  # F6
+            "A": "\x1b[18~",  # F7
+            "B": "\x1b[19~",  # F8
+            "C": "\x1b[20~",  # F9
+            "D": "\x1b[21~",  # F10
+        }
+        navcodes = {
+            "H": "\x1b[A",  # UP
+            "P": "\x1b[B",  # DOWN
+            "K": "\x1b[D",  # LEFT
+            "M": "\x1b[C",  # RIGHT
+            "G": "\x1b[H",  # HOME
+            "O": "\x1b[F",  # END
+            "R": "\x1b[2~",  # INSERT
+            "S": "\x1b[3~",  # DELETE
+            "I": "\x1b[5~",  # PAGE UP
+            "Q": "\x1b[6~",  # PAGE DOWN
+        }
+
+        def __init__(self, miniterm):
+            super(Console, self).__init__(miniterm)
+            self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
+            self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
+            ctypes.windll.kernel32.SetConsoleOutputCP(65001)
+            ctypes.windll.kernel32.SetConsoleCP(65001)
+            # ANSI handling available through SetConsoleMode since Windows 10 v1511
+            # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1
+            if (
+                platform.release() == "10"
+                and int(platform.version().split(".")[2]) > 10586
+            ):
+                ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+                import ctypes.wintypes as wintypes
+
+                if not hasattr(wintypes, "LPDWORD"):  # PY2
+                    wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
+                SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
+                GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
+                GetStdHandle = ctypes.windll.kernel32.GetStdHandle
+                mode = wintypes.DWORD()
+                GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode))
+                if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
+                    SetConsoleMode(
+                        GetStdHandle(-11),
+                        mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+                    )
+                    self._saved_cm = mode
+            self.output = codecs.getwriter("UTF-8")(Out(sys.stdout.fileno()), "replace")
+            # the change of the code page is not propagated to Python, manually fix it
+            sys.stderr = codecs.getwriter("UTF-8")(Out(sys.stderr.fileno()), "replace")
+            sys.stdout = self.output
+            self.output.encoding = "UTF-8"  # needed for input
+
+        def __del__(self):
+            ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
+            ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
+            try:
+                ctypes.windll.kernel32.SetConsoleMode(
+                    ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm
+                )
+            except AttributeError:  # in case no _saved_cm
+                pass
+
+        def getkey(self):
+            while True:
+                z = msvcrt.getwch()
+                if z == unichr(13):
+                    return unichr(10)
+                elif z is unichr(0) or z is unichr(0xE0):
+                    try:
+                        code = msvcrt.getwch()
+                        if z is unichr(0):
+                            return self.fncodes[code]
+                        else:
+                            return self.navcodes[code]
+                    except KeyError:
+                        pass
+                else:
+                    return z
+
+        def cancel(self):
+            # CancelIo, CancelSynchronousIo do not seem to work when using
+            # getwch, so instead, send a key to the window with the console
+            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
+            ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0D, 0)
+
+elif os.name == "posix":
+    import atexit
+    import termios
+    import fcntl
+    import signal
+
+    class Console(ConsoleBase):
+        def __init__(self, miniterm):
+            super(Console, self).__init__(miniterm)
+            self.fd = sys.stdin.fileno()
+            self.old = termios.tcgetattr(self.fd)
+            atexit.register(self.cleanup)
+            signal.signal(signal.SIGINT, self.sigint)
+            if sys.version_info < (3, 0):
+                self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
+            else:
+                self.enc_stdin = sys.stdin
+
+        def setup(self):
+            new = termios.tcgetattr(self.fd)
+            new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
+            new[6][termios.VMIN] = 1
+            new[6][termios.VTIME] = 0
+            termios.tcsetattr(self.fd, termios.TCSANOW, new)
+
+        def getkey(self):
+            c = self.enc_stdin.read(1)
+            if c == unichr(0x7F):
+                c = unichr(8)  # map the BS key (which yields DEL) to backspace
+            return c
+
+        def cancel(self):
+            fcntl.ioctl(self.fd, termios.TIOCSTI, b"\0")
+
+        def cleanup(self):
+            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
+
+        def sigint(self, sig, frame):
+            """signal handler for a clean exit on SIGINT"""
+            self.miniterm.stop()
+            self.cancel()
+
+else:
+    raise NotImplementedError(
+        "Sorry no implementation for your platform ({}) available.".format(sys.platform)
+    )
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+class Transform(object):
+    """do-nothing: forward all data unchanged"""
+
+    def rx(self, text):
+        """text received from serial port"""
+        return text
+
+    def tx(self, text):
+        """text to be sent to serial port"""
+        return text
+
+    def echo(self, text):
+        """text to be sent but displayed on console"""
+        return text
+
+
+class CRLF(Transform):
+    """ENTER sends CR+LF"""
+
+    def tx(self, text):
+        return text.replace("\n", "\r\n")
+
+
+class CR(Transform):
+    """ENTER sends CR"""
+
+    def rx(self, text):
+        return text.replace("\r", "\n")
+
+    def tx(self, text):
+        return text.replace("\n", "\r")
+
+
+class LF(Transform):
+    """ENTER sends LF"""
+
+
+class NoTerminal(Transform):
+    """remove typical terminal control codes from input"""
+
+    REPLACEMENT_MAP = dict(
+        (x, 0x2400 + x) for x in range(32) if unichr(x) not in "\r\n\b\t"
+    )
+    REPLACEMENT_MAP.update(
+        {
+            0x7F: 0x2421,  # DEL
+            0x9B: 0x2425,  # CSI
+        }
+    )
+
+    def rx(self, text):
+        return text.translate(self.REPLACEMENT_MAP)
+
+    echo = rx
+
+
+class NoControls(NoTerminal):
+    """Remove all control codes, incl. CR+LF"""
+
+    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
+    REPLACEMENT_MAP.update(
+        {
+            0x20: 0x2423,  # visual space
+            0x7F: 0x2421,  # DEL
+            0x9B: 0x2425,  # CSI
+        }
+    )
+
+
+class Printable(Transform):
+    """Show decimal code for all non-ASCII characters and replace most control codes"""
+
+    def rx(self, text):
+        r = []
+        for c in text:
+            if " " <= c < "\x7f" or c in "\r\n\b\t":
+                r.append(c)
+            elif c < " ":
+                r.append(unichr(0x2400 + ord(c)))
+            else:
+                r.extend(unichr(0x2080 + ord(d) - 48) for d in "{:d}".format(ord(c)))
+                r.append(" ")
+        return "".join(r)
+
+    echo = rx
+
+
+class Colorize(Transform):
+    """Apply different colors for received and echo"""
+
+    def __init__(self):
+        # XXX make it configurable, use colorama?
+        self.input_color = "\x1b[37m"
+        self.echo_color = "\x1b[31m"
+
+    def rx(self, text):
+        return self.input_color + text
+
+    def echo(self, text):
+        return self.echo_color + text
+
+
+class DebugIO(Transform):
+    """Print what is sent and received"""
+
+    def rx(self, text):
+        sys.stderr.write(" [RX:{!r}] ".format(text))
+        sys.stderr.flush()
+        return text
+
+    def tx(self, text):
+        sys.stderr.write(" [TX:{!r}] ".format(text))
+        sys.stderr.flush()
+        return text
+
+
+# other ideas:
+# - add date/time for each newline
+# - insert newline after: a) timeout b) packet end character
+
+EOL_TRANSFORMATIONS = {
+    "crlf": CRLF,
+    "cr": CR,
+    "lf": LF,
+}
+
+TRANSFORMATIONS = {
+    "direct": Transform,  # no transformation
+    "default": NoTerminal,
+    "nocontrol": NoControls,
+    "printable": Printable,
+    "colorize": Colorize,
+    "debug": DebugIO,
+}
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+def ask_for_port():
+    """\
+    Show a list of ports and ask the user for a choice. To make selection
+    easier on systems with long device names, also allow the input of an
+    index.
+    """
+    sys.stderr.write("\n--- Available ports:\n")
+    ports = []
+    for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
+        sys.stderr.write("--- {:2}: {:20} {!r}\n".format(n, port, desc))
+        ports.append(port)
+    while True:
+        sys.stderr.write("--- Enter port index or full name: ")
+        port = raw_input("")
+        try:
+            index = int(port) - 1
+            if not 0 <= index < len(ports):
+                sys.stderr.write("--- Invalid index!\n")
+                continue
+        except ValueError:
+            pass
+        else:
+            port = ports[index]
+        return port
+
+
+class Miniterm(object):
+    """\
+    Terminal application. Copy data from serial port to console and vice versa.
+    Handle special keys from the console to show menu etc.
+    """
+
+    def __init__(self, serial_instance, echo=False, eol="crlf", filters=()):
+        self.console = Console(self)
+        self.serial = serial_instance
+        self.echo = True
+        self.raw = False
+        self.input_encoding = "UTF-8"
+        self.output_encoding = "UTF-8"
+        self.eol = eol
+        self.filters = filters
+        self.update_transformations()
+        self.exit_character = unichr(0x1D)  # GS/CTRL+]
+        self.menu_character = unichr(0x14)  # Menu: CTRL+T
+        self.alive = None
+        self._reader_alive = None
+        self.receiver_thread = None
+        self.rx_decoder = None
+        self.tx_decoder = None
+        self.tx_encoder = None
+        self.no_write = False
+        self.remove_no_write = False
+
+    def _start_reader(self):
+        """Start reader thread"""
+        self._reader_alive = True
+        # start serial->console thread
+        self.receiver_thread = threading.Thread(target=self.reader, name="rx")
+        self.receiver_thread.daemon = True
+        self.receiver_thread.start()
+
+    def _stop_reader(self):
+        """Stop reader thread only, wait for clean exit of thread"""
+        self._reader_alive = False
+        if hasattr(self.serial, "cancel_read"):
+            self.serial.cancel_read()
+        self.receiver_thread.join()
+
+    def start(self):
+        """start worker threads"""
+        self.alive = True
+        self._start_reader()
+        # enter console->serial loop
+        self.transmitter_thread = threading.Thread(target=self.writer, name="tx")
+        self.transmitter_thread.daemon = True
+        self.transmitter_thread.start()
+        self.console.setup()
+
+    def stop(self):
+        """set flag to stop worker threads"""
+        self.alive = False
+
+    def join(self, transmit_only=False):
+        """wait for worker threads to terminate"""
+        self.transmitter_thread.join()
+        if not transmit_only:
+            if hasattr(self.serial, "cancel_read"):
+                self.serial.cancel_read()
+            self.receiver_thread.join()
+
+    def close(self):
+        self.serial.close()
+
+    def update_transformations(self):
+        """take list of transformation classes and instantiate them for rx and tx"""
+        transformations = [EOL_TRANSFORMATIONS[self.eol]] + [
+            TRANSFORMATIONS[f] for f in self.filters
+        ]
+        self.tx_transformations = [t() for t in transformations]
+        self.rx_transformations = list(reversed(self.tx_transformations))
+
+    def set_rx_encoding(self, encoding, errors="replace"):
+        """set encoding for received data"""
+        self.input_encoding = encoding
+        self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
+
+    def set_tx_encoding(self, encoding, errors="replace"):
+        """set encoding for transmitted data"""
+        self.output_encoding = encoding
+        self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
+
+    def dump_port_settings(self):
+        """Write current settings to sys.stderr"""
+        sys.stderr.write(
+            "\n--- Settings: {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
+                p=self.serial
+            )
+        )
+        sys.stderr.write(
+            "--- RTS: {:8}  DTR: {:8}  BREAK: {:8}\n".format(
+                ("active" if self.serial.rts else "inactive"),
+                ("active" if self.serial.dtr else "inactive"),
+                ("active" if self.serial.break_condition else "inactive"),
+            )
+        )
+        try:
+            sys.stderr.write(
+                "--- CTS: {:8}  DSR: {:8}  RI: {:8}  CD: {:8}\n".format(
+                    ("active" if self.serial.cts else "inactive"),
+                    ("active" if self.serial.dsr else "inactive"),
+                    ("active" if self.serial.ri else "inactive"),
+                    ("active" if self.serial.cd else "inactive"),
+                )
+            )
+        except serial.SerialException:
+            # on RFC 2217 ports, it can happen if no modem state notification was
+            # yet received. ignore this error.
+            pass
+        sys.stderr.write(
+            "--- software flow control: {}\n".format(
+                "active" if self.serial.xonxoff else "inactive"
+            )
+        )
+        sys.stderr.write(
+            "--- hardware flow control: {}\n".format(
+                "active" if self.serial.rtscts else "inactive"
+            )
+        )
+        sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding))
+        sys.stderr.write(
+            "--- serial output encoding: {}\n".format(self.output_encoding)
+        )
+        sys.stderr.write("--- EOL: {}\n".format(self.eol.upper()))
+        sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters)))
+
+    def reader(self):
+        """loop and copy serial->console"""
+        data = b""
+        while self.alive and self._reader_alive:
+            # read all that is there or wait for one byte
+            try:
+                data2 = self.serial.read(self.serial.in_waiting or 1)
+            except:
+                data2 = b""
+            if data2:
+                if self.remove_no_write == True:
+                    self.remove_no_write = False
+                    self.no_write = False
+                    data = data2
+                else:
+                    data += data2
+                if b"\n" in data:
+                    if self.raw:
+                        self.console.write_bytes(data)
+                    else:
+                        text = self.rx_decoder.decode(data)
+                        for transformation in self.rx_transformations:
+                            text = transformation.rx(text)
+                        if b"m1\n" in data:
+                            self.no_write = True
+                        if self.no_write == False:
+                            self.console.write(text)
+                    data = b""
+
+    def writer(self):
+        """\
+        Loop and copy console->serial until self.exit_character character is
+        found. When self.menu_character is found, interpret the next key
+        locally.
+        """
+        menu_active = False
+        try:
+            while self.alive:
+                try:
+                    c = self.console.getkey()
+                    self.remove_no_write = True
+                except KeyboardInterrupt:
+                    c = "\x03"
+                if not self.alive:
+                    break
+                if menu_active:
+                    self.handle_menu_key(c)
+                    menu_active = False
+                elif c == self.menu_character:
+                    menu_active = True  # next char will be for menu
+                elif c == self.exit_character:
+                    self.stop()  # exit app
+                    break
+                else:
+                    # ~ if self.raw:
+                    text = c
+                    for transformation in self.tx_transformations:
+                        text = transformation.tx(text)
+                    self.serial.write(self.tx_encoder.encode(text))
+                    if self.echo:
+                        echo_text = c
+                        for transformation in self.tx_transformations:
+                            echo_text = transformation.echo(echo_text)
+                        self.console.write(echo_text)
+        except:
+            self.alive = False
+            raise
+
+    def handle_menu_key(self, c):
+        """Implement a simple menu / settings"""
+        if c == self.menu_character or c == self.exit_character:
+            # Menu/exit character again -> send itself
+            self.serial.write(self.tx_encoder.encode(c))
+            if self.echo:
+                self.console.write(c)
+        elif c == "\x15":  # CTRL+U -> upload file
+            self.upload_file()
+        elif c in "\x08hH?":  # CTRL+H, h, H, ? -> Show help
+            sys.stderr.write(self.get_help_text())
+        elif c == "\x12":  # CTRL+R -> Toggle RTS
+            self.serial.rts = not self.serial.rts
+            sys.stderr.write(
+                "--- RTS {} ---\n".format("active" if self.serial.rts else "inactive")
+            )
+        elif c == "\x04":  # CTRL+D -> Toggle DTR
+            self.serial.dtr = not self.serial.dtr
+            sys.stderr.write(
+                "--- DTR {} ---\n".format("active" if self.serial.dtr else "inactive")
+            )
+        elif c == "\x02":  # CTRL+B -> toggle BREAK condition
+            self.serial.break_condition = not self.serial.break_condition
+            sys.stderr.write(
+                "--- BREAK {} ---\n".format(
+                    "active" if self.serial.break_condition else "inactive"
+                )
+            )
+        elif c == "\x05":  # CTRL+E -> toggle local echo
+            self.echo = not self.echo
+            sys.stderr.write(
+                "--- local echo {} ---\n".format("active" if self.echo else "inactive")
+            )
+        elif c == "\x06":  # CTRL+F -> edit filters
+            self.change_filter()
+        elif c == "\x0c":  # CTRL+L -> EOL mode
+            modes = list(EOL_TRANSFORMATIONS)  # keys
+            eol = modes.index(self.eol) + 1
+            if eol >= len(modes):
+                eol = 0
+            self.eol = modes[eol]
+            sys.stderr.write("--- EOL: {} ---\n".format(self.eol.upper()))
+            self.update_transformations()
+        elif c == "\x01":  # CTRL+A -> set encoding
+            self.change_encoding()
+        elif c == "\x09":  # CTRL+I -> info
+            self.dump_port_settings()
+        # ~ elif c == '\x01':                       # CTRL+A -> cycle escape mode
+        # ~ elif c == '\x0c':                       # CTRL+L -> cycle linefeed mode
+        elif c in "pP":  # P -> change port
+            self.change_port()
+        elif c in "zZ":  # S -> suspend / open port temporarily
+            self.suspend_port()
+        elif c in "bB":  # B -> change baudrate
+            self.change_baudrate()
+        elif c == "8":  # 8 -> change to 8 bits
+            self.serial.bytesize = serial.EIGHTBITS
+            self.dump_port_settings()
+        elif c == "7":  # 7 -> change to 8 bits
+            self.serial.bytesize = serial.SEVENBITS
+            self.dump_port_settings()
+        elif c in "eE":  # E -> change to even parity
+            self.serial.parity = serial.PARITY_EVEN
+            self.dump_port_settings()
+        elif c in "oO":  # O -> change to odd parity
+            self.serial.parity = serial.PARITY_ODD
+            self.dump_port_settings()
+        elif c in "mM":  # M -> change to mark parity
+            self.serial.parity = serial.PARITY_MARK
+            self.dump_port_settings()
+        elif c in "sS":  # S -> change to space parity
+            self.serial.parity = serial.PARITY_SPACE
+            self.dump_port_settings()
+        elif c in "nN":  # N -> change to no parity
+            self.serial.parity = serial.PARITY_NONE
+            self.dump_port_settings()
+        elif c == "1":  # 1 -> change to 1 stop bits
+            self.serial.stopbits = serial.STOPBITS_ONE
+            self.dump_port_settings()
+        elif c == "2":  # 2 -> change to 2 stop bits
+            self.serial.stopbits = serial.STOPBITS_TWO
+            self.dump_port_settings()
+        elif c == "3":  # 3 -> change to 1.5 stop bits
+            self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
+            self.dump_port_settings()
+        elif c in "xX":  # X -> change software flow control
+            self.serial.xonxoff = c == "X"
+            self.dump_port_settings()
+        elif c in "rR":  # R -> change hardware flow control
+            self.serial.rtscts = c == "R"
+            self.dump_port_settings()
+        elif c in "qQ":
+            self.stop()  # Q -> exit app
+        else:
+            sys.stderr.write(
+                "--- unknown menu character {} --\n".format(key_description(c))
+            )
+
+    def upload_file(self):
+        """Ask user for filename and send its contents"""
+        sys.stderr.write("\n--- File to upload: ")
+        sys.stderr.flush()
+        with self.console:
+            filename = sys.stdin.readline().rstrip("\r\n")
+            if filename:
+                try:
+                    with open(filename, "rb") as f:
+                        sys.stderr.write("--- Sending file {} ---\n".format(filename))
+                        while True:
+                            block = f.read(1024)
+                            if not block:
+                                break
+                            self.serial.write(block)
+                            # Wait for output buffer to drain.
+                            self.serial.flush()
+                            sys.stderr.write(".")  # Progress indicator.
+                    sys.stderr.write("\n--- File {} sent ---\n".format(filename))
+                except IOError as e:
+                    sys.stderr.write(
+                        "--- ERROR opening file {}: {} ---\n".format(filename, e)
+                    )
+
+    def change_filter(self):
+        """change the i/o transformations"""
+        sys.stderr.write("\n--- Available Filters:\n")
+        sys.stderr.write(
+            "\n".join(
+                "---   {:<10} = {.__doc__}".format(k, v)
+                for k, v in sorted(TRANSFORMATIONS.items())
+            )
+        )
+        sys.stderr.write(
+            "\n--- Enter new filter name(s) [{}]: ".format(" ".join(self.filters))
+        )
+        with self.console:
+            new_filters = sys.stdin.readline().lower().split()
+        if new_filters:
+            for f in new_filters:
+                if f not in TRANSFORMATIONS:
+                    sys.stderr.write("--- unknown filter: {!r}\n".format(f))
+                    break
+            else:
+                self.filters = new_filters
+                self.update_transformations()
+        sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters)))
+
+    def change_encoding(self):
+        """change encoding on the serial port"""
+        sys.stderr.write(
+            "\n--- Enter new encoding name [{}]: ".format(self.input_encoding)
+        )
+        with self.console:
+            new_encoding = sys.stdin.readline().strip()
+        if new_encoding:
+            try:
+                codecs.lookup(new_encoding)
+            except LookupError:
+                sys.stderr.write("--- invalid encoding name: {}\n".format(new_encoding))
+            else:
+                self.set_rx_encoding(new_encoding)
+                self.set_tx_encoding(new_encoding)
+        sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding))
+        sys.stderr.write(
+            "--- serial output encoding: {}\n".format(self.output_encoding)
+        )
+
+    def change_baudrate(self):
+        """change the baudrate"""
+        sys.stderr.write("\n--- Baudrate: ")
+        sys.stderr.flush()
+        with self.console:
+            backup = self.serial.baudrate
+            try:
+                self.serial.baudrate = int(sys.stdin.readline().strip())
+            except ValueError as e:
+                sys.stderr.write("--- ERROR setting baudrate: {} ---\n".format(e))
+                self.serial.baudrate = backup
+            else:
+                self.dump_port_settings()
+
+    def change_port(self):
+        """Have a conversation with the user to change the serial port"""
+        with self.console:
+            try:
+                port = ask_for_port()
+            except KeyboardInterrupt:
+                port = None
+        if port and port != self.serial.port:
+            # reader thread needs to be shut down
+            self._stop_reader()
+            # save settings
+            settings = self.serial.getSettingsDict()
+            try:
+                new_serial = serial.serial_for_url(port, do_not_open=True)
+                # restore settings and open
+                new_serial.applySettingsDict(settings)
+                new_serial.rts = self.serial.rts
+                new_serial.dtr = self.serial.dtr
+                new_serial.open()
+                new_serial.break_condition = self.serial.break_condition
+            except Exception as e:
+                sys.stderr.write("--- ERROR opening new port: {} ---\n".format(e))
+                new_serial.close()
+            else:
+                self.serial.close()
+                self.serial = new_serial
+                sys.stderr.write(
+                    "--- Port changed to: {} ---\n".format(self.serial.port)
+                )
+            # and restart the reader thread
+            self._start_reader()
+
+    def suspend_port(self):
+        """\
+        open port temporarily, allow reconnect, exit and port change to get
+        out of the loop
+        """
+        # reader thread needs to be shut down
+        self._stop_reader()
+        self.serial.close()
+        sys.stderr.write("\n--- Port closed: {} ---\n".format(self.serial.port))
+        do_change_port = False
+        while not self.serial.is_open:
+            sys.stderr.write(
+                "--- Quit: {exit} | p: port change | any other key to reconnect ---\n".format(
+                    exit=key_description(self.exit_character)
+                )
+            )
+            k = self.console.getkey()
+            if k == self.exit_character:
+                self.stop()  # exit app
+                break
+            elif k in "pP":
+                do_change_port = True
+                break
+            try:
+                self.serial.open()
+            except Exception as e:
+                sys.stderr.write("--- ERROR opening port: {} ---\n".format(e))
+        if do_change_port:
+            self.change_port()
+        else:
+            # and restart the reader thread
+            self._start_reader()
+            sys.stderr.write("--- Port opened: {} ---\n".format(self.serial.port))
+
+    def get_help_text(self):
+        """return the help text"""
+        # help text, starts with blank line!
+        return """
+--- pySerial ({version}) - miniterm - help
+---
+--- {exit:8} Exit program (alias {menu} Q)
+--- {menu:8} Menu escape key, followed by:
+--- Menu keys:
+---    {menu:7} Send the menu character itself to remote
+---    {exit:7} Send the exit character itself to remote
+---    {info:7} Show info
+---    {upload:7} Upload file (prompt will be shown)
+---    {repr:7} encoding
+---    {filter:7} edit filters
+--- Toggles:
+---    {rts:7} RTS   {dtr:7} DTR   {brk:7} BREAK
+---    {echo:7} echo  {eol:7} EOL
+---
+--- Port settings ({menu} followed by the following):
+---    p          change port
+---    7 8        set data bits
+---    N E O S M  change parity (None, Even, Odd, Space, Mark)
+---    1 2 3      set stop bits (1, 2, 1.5)
+---    b          change baud rate
+---    x X        disable/enable software flow control
+---    r R        disable/enable hardware flow control
+""".format(
+            version=getattr(serial, "VERSION", "unknown version"),
+            exit=key_description(self.exit_character),
+            menu=key_description(self.menu_character),
+            rts=key_description("\x12"),
+            dtr=key_description("\x04"),
+            brk=key_description("\x02"),
+            echo=key_description("\x05"),
+            info=key_description("\x09"),
+            upload=key_description("\x15"),
+            repr=key_description("\x01"),
+            filter=key_description("\x06"),
+            eol=key_description("\x0c"),
+        )
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# default args can be used to override when calling main() from an other script
+# e.g to create a miniterm-my-device.py
+def main(
+    default_port=None,
+    default_baudrate=9600,
+    default_rts=None,
+    default_dtr=None,
+    serial_instance=None,
+):
+    """Command line tool, entry point"""
+
+    import argparse
+
+    parser = argparse.ArgumentParser(
+        description="Miniterm - A simple terminal program for the serial port."
+    )
+
+    parser.add_argument(
+        "port",
+        nargs="?",
+        help='serial port name ("-" to show port list)',
+        default=default_port,
+    )
+
+    parser.add_argument(
+        "baudrate",
+        nargs="?",
+        type=int,
+        help="set baud rate, default: %(default)s",
+        default=default_baudrate,
+    )
+
+    group = parser.add_argument_group("port settings")
+
+    group.add_argument(
+        "--parity",
+        choices=["N", "E", "O", "S", "M"],
+        type=lambda c: c.upper(),
+        help="set parity, one of {N E O S M}, default: N",
+        default="N",
+    )
+
+    group.add_argument(
+        "--rtscts",
+        action="store_true",
+        help="enable RTS/CTS flow control (default off)",
+        default=False,
+    )
+
+    group.add_argument(
+        "--xonxoff",
+        action="store_true",
+        help="enable software flow control (default off)",
+        default=False,
+    )
+
+    group.add_argument(
+        "--rts",
+        type=int,
+        help="set initial RTS line state (possible values: 0, 1)",
+        default=default_rts,
+    )
+
+    group.add_argument(
+        "--dtr",
+        type=int,
+        help="set initial DTR line state (possible values: 0, 1)",
+        default=default_dtr,
+    )
+
+    group.add_argument(
+        "--non-exclusive",
+        dest="exclusive",
+        action="store_false",
+        help="disable locking for native ports",
+        default=True,
+    )
+
+    group.add_argument(
+        "--ask",
+        action="store_true",
+        help="ask again for port when open fails",
+        default=False,
+    )
+
+    group = parser.add_argument_group("data handling")
+
+    group.add_argument(
+        "-e",
+        "--echo",
+        action="store_true",
+        help="enable local echo (default off)",
+        default=False,
+    )
+
+    group.add_argument(
+        "--encoding",
+        dest="serial_port_encoding",
+        metavar="CODEC",
+        help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
+        default="UTF-8",
+    )
+
+    group.add_argument(
+        "-f",
+        "--filter",
+        action="append",
+        metavar="NAME",
+        help="add text transformation",
+        default=[],
+    )
+
+    group.add_argument(
+        "--eol",
+        choices=["CR", "LF", "CRLF"],
+        type=lambda c: c.upper(),
+        help="end of line mode",
+        default="CRLF",
+    )
+
+    group.add_argument(
+        "--raw",
+        action="store_true",
+        help="Do no apply any encodings/transformations",
+        default=False,
+    )
+
+    group = parser.add_argument_group("hotkeys")
+
+    group.add_argument(
+        "--exit-char",
+        type=int,
+        metavar="NUM",
+        help="Unicode of special character that is used to exit the application, default: %(default)s",
+        default=0x1D,
+    )  # GS/CTRL+]
+
+    group.add_argument(
+        "--menu-char",
+        type=int,
+        metavar="NUM",
+        help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
+        default=0x14,
+    )  # Menu: CTRL+T
+
+    group = parser.add_argument_group("diagnostics")
+
+    group.add_argument(
+        "-q",
+        "--quiet",
+        action="store_true",
+        help="suppress non-error messages",
+        default=False,
+    )
+
+    group.add_argument(
+        "--develop",
+        action="store_true",
+        help="show Python traceback on error",
+        default=False,
+    )
+
+    args = parser.parse_args()
+
+    if args.menu_char == args.exit_char:
+        parser.error("--exit-char can not be the same as --menu-char")
+
+    if args.filter:
+        if "help" in args.filter:
+            sys.stderr.write("Available filters:\n")
+            sys.stderr.write(
+                "\n".join(
+                    "{:<10} = {.__doc__}".format(k, v)
+                    for k, v in sorted(TRANSFORMATIONS.items())
+                )
+            )
+            sys.stderr.write("\n")
+            sys.exit(1)
+        filters = args.filter
+    else:
+        filters = ["default"]
+
+    while serial_instance is None:
+        # no port given on command line -> ask user now
+        if args.port is None or args.port == "-":
+            try:
+                args.port = ask_for_port()
+            except KeyboardInterrupt:
+                sys.stderr.write("\n")
+                parser.error("user aborted and port is not given")
+            else:
+                if not args.port:
+                    parser.error("port is not given")
+        try:
+            serial_instance = serial.serial_for_url(
+                args.port,
+                args.baudrate,
+                parity=args.parity,
+                rtscts=args.rtscts,
+                xonxoff=args.xonxoff,
+                do_not_open=True,
+            )
+
+            if not hasattr(serial_instance, "cancel_read"):
+                # enable timeout for alive flag polling if cancel_read is not available
+                serial_instance.timeout = 1
+
+            if args.dtr is not None:
+                if not args.quiet:
+                    sys.stderr.write(
+                        "--- forcing DTR {}\n".format(
+                            "active" if args.dtr else "inactive"
+                        )
+                    )
+                serial_instance.dtr = args.dtr
+            if args.rts is not None:
+                if not args.quiet:
+                    sys.stderr.write(
+                        "--- forcing RTS {}\n".format(
+                            "active" if args.rts else "inactive"
+                        )
+                    )
+                serial_instance.rts = args.rts
+
+            if isinstance(serial_instance, serial.Serial):
+                serial_instance.exclusive = args.exclusive
+
+            serial_instance.open()
+        except serial.SerialException as e:
+            sys.stderr.write("could not open port {!r}: {}\n".format(args.port, e))
+            if args.develop:
+                raise
+            if not args.ask:
+                sys.exit(1)
+            else:
+                args.port = "-"
+        else:
+            break
+
+    miniterm = Miniterm(
+        serial_instance, echo=args.echo, eol=args.eol.lower(), filters=filters
+    )
+    miniterm.exit_character = unichr(args.exit_char)
+    miniterm.menu_character = unichr(args.menu_char)
+    miniterm.raw = args.raw
+    miniterm.set_rx_encoding(args.serial_port_encoding)
+    miniterm.set_tx_encoding(args.serial_port_encoding)
+
+    if not args.quiet:
+        sys.stderr.write(
+            "--- Miniterm on {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n".format(
+                p=miniterm.serial
+            )
+        )
+        sys.stderr.write(
+            "--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n".format(
+                key_description(miniterm.exit_character),
+                key_description(miniterm.menu_character),
+                key_description(miniterm.menu_character),
+                key_description("\x08"),
+            )
+        )
+        sys.stderr.write(
+            "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format(
+                p=miniterm.serial
+            )
+        )
+        sys.stderr.write(
+            "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format(
+                p=miniterm.serial
+            )
+        )
+
+    miniterm.start()
+    try:
+        miniterm.join(True)
+    except KeyboardInterrupt:
+        pass
+    if not args.quiet:
+        sys.stderr.write("\n--- exit ---\n")
+    miniterm.join()
+    miniterm.close()
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+if __name__ == "__main__":
+    main()

From 314bb93f88adf994bd4d0b3bbdf696abf408c416 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 15:00:59 +0200
Subject: [PATCH 06/17] Add support for Seplos BMS
 https://github.com/Louisvdw/dbus-serialbattery/pull/530

---
 etc/dbus-serialbattery/battery.py            |   3 +
 etc/dbus-serialbattery/dbus-serialbattery.py |   2 +
 etc/dbus-serialbattery/seplos.py             | 301 +++++++++++++++++++
 3 files changed, 306 insertions(+)
 create mode 100644 etc/dbus-serialbattery/seplos.py

diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index 1578a8c9..d3399436 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -14,6 +14,9 @@ class Protection(object):
     This class holds Warning and alarm states for different types of Checks
     They are of type integer, 2 represents an Alarm, 1 a Warning, 0 if everything is fine
     """
+    ALARM = 2
+    WARNING = 1
+    OK = 0
 
     def __init__(self):
         self.voltage_high: int = None
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index b1a034bd..991e2984 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -29,6 +29,7 @@
 from lifepower import Lifepower
 from lltjbd import LltJbd
 from renogy import Renogy
+from seplos import Seplos
 # from sinowealth import Sinowealth
 
 supported_bms_types = [
@@ -42,6 +43,7 @@
     {"bms": LltJbd, "baud": 9600},
     {"bms": Renogy, "baud": 9600, "address": b"\x30"},
     {"bms": Renogy, "baud": 9600, "address": b"\xF7"},
+    {"bms": Seplos, "baud": 19200},
     # {"bms": Sinowealth},
 ]
 expected_bms_types = [
diff --git a/etc/dbus-serialbattery/seplos.py b/etc/dbus-serialbattery/seplos.py
new file mode 100644
index 00000000..a5319dcf
--- /dev/null
+++ b/etc/dbus-serialbattery/seplos.py
@@ -0,0 +1,301 @@
+# -*- coding: utf-8 -*-
+from battery import Protection, Battery, Cell
+from utils import *
+
+
+def int_from_hex_ascii(to_decode, signed=False):
+    return int.from_bytes(
+        bytes.fromhex(to_decode.decode("ascii")), byteorder="big", signed=signed
+    )
+
+
+class Seplos(Battery):
+    def __init__(self, port, baud, address=0x00):
+        super(Seplos, self).__init__(port, baud, address)
+        self.type = self.BATTERYTYPE
+        self.poll_interval = 5000
+
+    BATTERYTYPE = "Seplos"
+
+    COMMAND_STATUS = 0x42
+    COMMAND_ALARM = 0x44
+    COMMAND_PROTOCOL_VERSION = 0x4F
+    COMMAND_VENDOR_INFO = 0x51
+
+    @staticmethod
+    def int_from_1byte_hex_ascii(data: bytes, offset: int, signed=False):
+        return int.from_bytes(
+            bytes.fromhex(data[offset : offset + 2].decode("ascii")),
+            byteorder="big",
+            signed=signed,
+        )
+
+    @staticmethod
+    def int_from_2byte_hex_ascii(data: bytes, offset: int, signed=False):
+        return int.from_bytes(
+            bytes.fromhex(data[offset : offset + 4].decode("ascii")),
+            byteorder="big",
+            signed=signed,
+        )
+
+    @staticmethod
+    def get_checksum(frame: bytes) -> int:
+        """implements the Seplos checksum algorithm, returns 4 bytes"""
+        checksum = 0
+        for b in frame:
+            checksum += b
+        checksum %= 0xFFFF
+        checksum ^= 0xFFFF
+        checksum += 1
+        return checksum
+
+    @staticmethod
+    def get_info_length(info: bytes) -> int:
+        """implements the Seplos checksum for the info length"""
+        lenid = len(info)
+        if lenid == 0:
+            return 0
+
+        lchksum = (lenid & 0xF) + ((lenid >> 4) & 0xF) + ((lenid >> 8) & 0xF)
+        lchksum %= 16
+        lchksum ^= 0xF
+        lchksum += 1
+
+        return (lchksum << 12) + lenid
+
+    @staticmethod
+    def encode_cmd(address: int, cid2: int, info: bytes = b"") -> bytes:
+        """encodes a command sent to a battery (cid1=0x46)"""
+        cid1 = 0x46
+
+        info_length = Seplos.get_info_length(info)
+
+        frame = "{:02X}{:02X}{:02X}{:02X}{:04X}".format(
+            0x20, address, cid1, cid2, info_length
+        ).encode()
+        frame += info
+
+        checksum = Seplos.get_checksum(frame)
+        encoded = b"~" + frame + "{:04X}".format(checksum).encode() + b"\r"
+        return encoded
+
+    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
+
+        try:
+            return self.read_status_data()
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            return False
+
+    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
+
+        # Uncomment if BMS does not supply capacity
+        # self.capacity = BATTERY_CAPACITY
+        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+
+        # init the cell array
+        for _ in range(self.cell_count):
+            self.cells.append(Cell(False))
+
+        return True
+
+    def refresh_data(self):
+        # call all functions that will refresh the battery data.
+        # 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
+
+    @staticmethod
+    def decode_alarm_byte(data_byte: int, alarm_bit: int, warn_bit: int):
+        if data_byte & (1 << alarm_bit) != 0:
+            return Protection.ALARM
+        if data_byte & (1 << warn_bit) != 0:
+            return Protection.WARNING
+        return Protection.OK
+
+    def read_alarm_data(self):
+        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:
+            return False
+
+        logger.debug("alarm info raw {}".format(data))
+        return self.decode_alarm_data(bytes.fromhex(data.decode("ascii")))
+
+    def decode_alarm_data(self, data: bytes):
+        logger.debug("alarm info decoded {}".format(data))
+        voltage_alarm_byte = data[30]
+        self.protection.voltage_cell_low = Seplos.decode_alarm_byte(
+            data_byte=voltage_alarm_byte, alarm_bit=3, warn_bit=2
+        )
+        # cell high voltage is actually unused because DBUS does not seem to support it, decoding anyway
+        # c.f. https://github.com/victronenergy/venus/wiki/dbus#battery
+        self.protection.voltage_cell_high = Seplos.decode_alarm_byte(
+            data_byte=voltage_alarm_byte, alarm_bit=1, warn_bit=0
+        )
+        self.protection.voltage_low = Seplos.decode_alarm_byte(
+            data_byte=voltage_alarm_byte, alarm_bit=7, warn_bit=6
+        )
+        self.protection.voltage_high = Seplos.decode_alarm_byte(
+            data_byte=voltage_alarm_byte, alarm_bit=5, warn_bit=4
+        )
+
+        temperature_alarm_byte = data[31]
+        self.protection.temp_low_charge = Seplos.decode_alarm_byte(
+            data_byte=temperature_alarm_byte, alarm_bit=3, warn_bit=2
+        )
+        self.protection.temp_high_charge = Seplos.decode_alarm_byte(
+            data_byte=temperature_alarm_byte, alarm_bit=1, warn_bit=0
+        )
+        self.protection.temp_low_discharge = Seplos.decode_alarm_byte(
+            data_byte=temperature_alarm_byte, alarm_bit=7, warn_bit=6
+        )
+        self.protection.temp_high_discharge = Seplos.decode_alarm_byte(
+            data_byte=temperature_alarm_byte, alarm_bit=5, warn_bit=4
+        )
+
+        current_alarm_byte = data[33]
+        self.protection.current_over = Seplos.decode_alarm_byte(
+            data_byte=current_alarm_byte, alarm_bit=1, warn_bit=0
+        )
+        self.protection.current_under = Seplos.decode_alarm_byte(
+            data_byte=current_alarm_byte, alarm_bit=3, warn_bit=2
+        )
+
+        soc_alarm_byte = data[34]
+        self.protection.soc_low = Seplos.decode_alarm_byte(
+            data_byte=soc_alarm_byte, alarm_bit=3, warn_bit=2
+        )
+
+        switch_byte = data[35]
+        self.discharge_fet = True if switch_byte & 0b01 != 0 else False
+        self.charge_fet = True if switch_byte & 0b10 != 0 else False
+        return True
+
+    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:
+            return False
+
+        cell_count_offset = 4
+        voltage_offset = 6
+        temps_offset = 72
+        self.cell_count = Seplos.int_from_1byte_hex_ascii(
+            data=data, offset=cell_count_offset
+        )
+        if self.cell_count == len(self.cells):
+            for i in range(self.cell_count):
+                voltage = (
+                    Seplos.int_from_2byte_hex_ascii(data, voltage_offset + i * 4) / 1000
+                )
+                self.cells[i].voltage = voltage
+                logger.debug("Voltage cell[{}]={}V".format(i, voltage))
+            for i in range(min(4, self.cell_count)):
+                temp = (
+                    Seplos.int_from_2byte_hex_ascii(data, temps_offset + i * 4) - 2731
+                ) / 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
+        self.temp2 = (
+            Seplos.int_from_2byte_hex_ascii(data, temps_offset + 5 * 4) - 2731
+        ) / 10
+        self.current = (
+            Seplos.int_from_2byte_hex_ascii(data, offset=96, signed=True) / 100
+        )
+        self.voltage = Seplos.int_from_2byte_hex_ascii(data, offset=100) / 100
+        self.capacity_remain = Seplos.int_from_2byte_hex_ascii(data, offset=104) / 100
+        self.capacity = Seplos.int_from_2byte_hex_ascii(data, offset=110) / 100
+        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(
+                self.capacity_remain, self.capacity, self.soc
+            )
+        )
+        logger.debug("Cycles = {}".format(self.cycles))
+        logger.debug(
+            "Environment temp = {}°C ,  Power temp = {}°C".format(
+                self.temp1, self.temp2
+            )
+        )
+        logger.debug("HW:" + self.hardware_version)
+
+        return True
+
+    @staticmethod
+    def is_valid_frame(data: bytes) -> bool:
+        """checks if data contains a valid frame
+        * minimum length is 18 Byte
+        * checksum needs to be valid
+        * also checks for error code as return code in cid2
+        * not checked: lchksum
+        """
+        if len(data) < 18:
+            logger.warning("short read, data={}".format(data))
+            return False
+
+        chksum = Seplos.get_checksum(data[1:-5])
+        if chksum != Seplos.int_from_2byte_hex_ascii(data, -5):
+            logger.warning("checksum error")
+            return False
+
+        cid2 = data[7:9]
+        if cid2 != b"00":
+            logger.warning("command returned with error code {}".format(cid2))
+            return False
+
+        return True
+
+    def read_serial_data_seplos(self, command):
+        logger.debug("read serial data seplos")
+
+        with serial.Serial(self.port, baudrate=self.baud_rate, timeout=1) as ser:
+            ser.flushOutput()
+            ser.flushInput()
+            written = ser.write(command)
+            logger.debug(
+                "wrote {} bytes to serial port {}, command={}".format(
+                    written, self.port, command
+                )
+            )
+
+            data = ser.readline()
+
+            if not Seplos.is_valid_frame(data):
+                return False
+
+            length_pos = 10
+            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)
+            )
+
+            return return_data

From aceeac97c19aeafc755f2a08b573977d0ab451b6 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 15:01:28 +0200
Subject: [PATCH 07/17] change flake8 settings

---
 .flake8                                      | 26 ++++++++++----------
 etc/dbus-serialbattery/dbus-serialbattery.py |  4 +--
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/.flake8 b/.flake8
index 5373f0c2..5e3bb3fe 100644
--- a/.flake8
+++ b/.flake8
@@ -1,19 +1,19 @@
 [flake8]
 max-line-length = 120
 exclude =
-          ./etc/dbus-serialbattery/ant.py,
-          ./etc/dbus-serialbattery/battery_template.py,
-          ./etc/dbus-serialbattery/daly.py,
-          ./etc/dbus-serialbattery/dbus-serialbattery.py,
-          ./etc/dbus-serialbattery/dbushelper.py,
-          ./etc/dbus-serialbattery/ecs.py,
-          ./etc/dbus-serialbattery/lifepower.py,
-          ./etc/dbus-serialbattery/lltjbd.py,
-          ./etc/dbus-serialbattery/minimalmodbus.py,
-          ./etc/dbus-serialbattery/mnb.py,
-          ./etc/dbus-serialbattery/renogy.py,
-          ./etc/dbus-serialbattery/revov.py,
-          ./etc/dbus-serialbattery/sinowealth.py,
+          #./etc/dbus-serialbattery/ant.py,
+          #./etc/dbus-serialbattery/battery_template.py,
+          #./etc/dbus-serialbattery/daly.py,
+          #./etc/dbus-serialbattery/dbus-serialbattery.py,
+          #./etc/dbus-serialbattery/dbushelper.py,
+          #./etc/dbus-serialbattery/ecs.py,
+          #./etc/dbus-serialbattery/lifepower.py,
+          #./etc/dbus-serialbattery/lltjbd.py,
+          #./etc/dbus-serialbattery/minimalmodbus.py,
+          #./etc/dbus-serialbattery/mnb.py,
+          #./etc/dbus-serialbattery/renogy.py,
+          #./etc/dbus-serialbattery/revov.py,
+          #./etc/dbus-serialbattery/sinowealth.py,
           ./etc/dbus-serialbattery/test_max17853.py,
           ./etc/dbus-serialbattery/util_max17853.py,
           ./velib_python
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index 991e2984..f91f0bea 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -4,7 +4,7 @@
 
 from time import sleep
 from dbus.mainloop.glib import DBusGMainLoop
-from threading import Thread
+# from threading import Thread  ## removed with https://github.com/Louisvdw/dbus-serialbattery/pull/582
 import sys
 
 if sys.version_info.major == 2:
@@ -104,7 +104,7 @@ def get_port() -> str:
         This prevent problems when using the driver only with a serial connection
         """
         if port == "Jkbms_Ble":
-            from jkbms_ble import Jkbms_Ble
+            from jkbms_ble import Jkbms_Ble  # noqa: F401 (ignore flake "imported but unused" error)
 
         class_ = eval(port)
         testbms = class_("", 9600, sys.argv[2])

From 64a08f41d8c09200b2bda12b333b928980949a18 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 15:10:31 +0200
Subject: [PATCH 08/17] fix black lint errors

---
 etc/dbus-serialbattery/battery.py            |  1 +
 etc/dbus-serialbattery/daly.py               | 19 ++++++++++++-------
 etc/dbus-serialbattery/dbus-serialbattery.py |  7 +++++--
 etc/dbus-serialbattery/utils.py              |  6 +++++-
 4 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index d3399436..e86c7e2e 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -14,6 +14,7 @@ class Protection(object):
     This class holds Warning and alarm states for different types of Checks
     They are of type integer, 2 represents an Alarm, 1 a Warning, 0 if everything is fine
     """
+
     ALARM = 2
     WARNING = 1
     OK = 0
diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py
index 492b0500..0abeb79e 100644
--- a/etc/dbus-serialbattery/daly.py
+++ b/etc/dbus-serialbattery/daly.py
@@ -247,9 +247,9 @@ def read_cells_volts(self, ser):
             buffer[2] = self.command_cell_volts[0]
 
             if (int(self.cell_count) % 3) == 0:
-                maxFrame = (int(self.cell_count / 3))
+                maxFrame = int(self.cell_count / 3)
             else:
-                maxFrame = (int(self.cell_count / 3) + 1)
+                maxFrame = int(self.cell_count / 3) + 1
             lenFixed = (
                 maxFrame * 13
             )  # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + chksum
@@ -279,10 +279,15 @@ def read_cells_volts(self, ser):
             ):  # we at least need 4 bytes to extract the identifiers
                 b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx)
                 if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08:
-                    frame, frameCell[0], frameCell[1], frameCell[2], _, chk = unpack_from(
-                        ">BhhhBB", cells_volts_data, bufIdx + 4
-                    )
-                    if sum(cells_volts_data[bufIdx:bufIdx+12]) & 0xFF != chk:
+                    (
+                        frame,
+                        frameCell[0],
+                        frameCell[1],
+                        frameCell[2],
+                        _,
+                        chk,
+                    ) = unpack_from(">BhhhBB", cells_volts_data, bufIdx + 4)
+                    if sum(cells_volts_data[bufIdx : bufIdx + 12]) & 0xFF != chk:
                         logger.warning("bad cell voltages checksum")
                         return False
                     for idx in range(3):
@@ -367,7 +372,7 @@ def read_serial_data_daly(self, ser, command):
         start, flag, command_ret, length = unpack_from("BBBB", data)
         checksum = sum(data[:-1]) & 0xFF
 
-        if start == 165 and length == 8 and len(data)>12 and checksum == data[12]:
+        if start == 165 and length == 8 and len(data) > 12 and checksum == data[12]:
             return data[4 : length + 4]
         else:
             logger.error(">>> ERROR: Incorrect Reply")
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index f91f0bea..1fdd7bf1 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -4,6 +4,7 @@
 
 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
 
@@ -30,6 +31,7 @@
 from lltjbd import LltJbd
 from renogy import Renogy
 from seplos import Seplos
+
 # from sinowealth import Sinowealth
 
 supported_bms_types = [
@@ -37,7 +39,7 @@
     {"bms": Daly, "baud": 9600, "address": b"\x40"},
     {"bms": Daly, "baud": 9600, "address": b"\x80"},
     {"bms": Ecs, "baud": 19200},
-    {'bms': HLPdataBMS4S, "baud": 9600},
+    {"bms": HLPdataBMS4S, "baud": 9600},
     {"bms": Jkbms, "baud": 115200},
     {"bms": Lifepower, "baud": 9600},
     {"bms": LltJbd, "baud": 9600},
@@ -104,7 +106,8 @@ def get_port() -> str:
         This prevent problems when using the driver only with a serial connection
         """
         if port == "Jkbms_Ble":
-            from jkbms_ble import Jkbms_Ble  # noqa: F401 (ignore flake "imported but unused" error)
+            # noqa: F401 --> ignore flake "imported but unused" error
+            from jkbms_ble import Jkbms_Ble  # noqa: F401
 
         class_ = eval(port)
         testbms = class_("", 9600, sys.argv[2])
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index 7cf6eac6..04ac14ea 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -442,7 +442,11 @@ def read_serialport_data(
         count = 0
         data = bytearray(res)
 
-        packetlen = length_fixed if length_fixed is not None else length_pos + length_byte_size + length + length_check
+        packetlen = (
+            length_fixed
+            if length_fixed is not None
+            else length_pos + length_byte_size + length + length_check
+        )
         while len(data) < packetlen:
             res = ser.read(packetlen - len(data))
             data.extend(res)

From 31647db46a7f11464ca3564855499e8f30367a60 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 17:17:40 +0200
Subject: [PATCH 09/17] removed wildcard imports

---
 etc/dbus-serialbattery/ant.py              | 24 ++++++++-------
 etc/dbus-serialbattery/battery_template.py | 18 ++++++-----
 etc/dbus-serialbattery/daly.py             | 34 +++++++++++---------
 etc/dbus-serialbattery/lifepower.py        | 24 ++++++++++-----
 etc/dbus-serialbattery/lltjbd.py           | 36 +++++++++++++---------
 etc/dbus-serialbattery/renogy.py           | 24 ++++++++-------
 etc/dbus-serialbattery/sinowealth.py       | 24 +++++++++------
 etc/dbus-serialbattery/utils.py            |  2 +-
 8 files changed, 109 insertions(+), 77 deletions(-)

diff --git a/etc/dbus-serialbattery/ant.py b/etc/dbus-serialbattery/ant.py
index 07b58d11..fde6d612 100644
--- a/etc/dbus-serialbattery/ant.py
+++ b/etc/dbus-serialbattery/ant.py
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
-from battery import Protection, Battery, Cell
-from utils import *
-from struct import *
+from battery import Battery
+from utils import read_serial_data, logger
+import utils
+from struct import unpack_from
 
 
 class Ant(Battery):
@@ -25,8 +26,9 @@ def test_connection(self):
         result = False
         try:
             result = self.read_status_data()
-        except:
-            pass
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
 
         return result
 
@@ -34,8 +36,8 @@ 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_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
         self.version = "ANT BMS V2.0"
         logger.info(self.hardware_version)
         return True
@@ -59,8 +61,8 @@ def read_status_data(self):
         self.current = 0.0 if current == 0 else current / -10
 
         self.cell_count = unpack_from(">b", status_data, 123)[0]
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
         cell_max_no, cell_max_voltage, cell_min_no, cell_min_voltage = unpack_from(
             ">bhbh", status_data, 115
@@ -95,9 +97,9 @@ def read_status_data(self):
         )
         self.protection.voltage_cell_low = (
             2
-            if self.cell_min_voltage < MIN_CELL_VOLTAGE - 0.1
+            if self.cell_min_voltage < utils.MIN_CELL_VOLTAGE - 0.1
             else 1
-            if self.cell_min_voltage < MIN_CELL_VOLTAGE
+            if self.cell_min_voltage < utils.MIN_CELL_VOLTAGE
             else 0
         )
         self.protection.temp_high_charge = (
diff --git a/etc/dbus-serialbattery/battery_template.py b/etc/dbus-serialbattery/battery_template.py
index e921efc0..10b851ee 100644
--- a/etc/dbus-serialbattery/battery_template.py
+++ b/etc/dbus-serialbattery/battery_template.py
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
 from battery import Protection, Battery, Cell
-from utils import *
-from struct import *
+from utils import is_bit_set, read_serial_data, logger
+import utils
+from struct import unpack_from
 
 
 class BatteryTemplate(Battery):
@@ -20,8 +21,9 @@ def test_connection(self):
         result = False
         try:
             result = self.read_status_data()
-        except:
-            pass
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
 
         return result
 
@@ -32,10 +34,10 @@ def get_settings(self):
 
         # Uncomment if BMS does not supply capacity
         # self.capacity = BATTERY_CAPACITY
-        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
         return True
 
     def refresh_data(self):
diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py
index 0abeb79e..d91ae568 100644
--- a/etc/dbus-serialbattery/daly.py
+++ b/etc/dbus-serialbattery/daly.py
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
-from battery import Protection, Battery, Cell
-from utils import *
-from struct import *
+from battery import Battery, Cell
+from utils import open_serial_port, read_serialport_data, logger
+import utils
+from struct import unpack_from
 
 
 class Daly(Battery):
@@ -37,21 +38,26 @@ def __init__(self, port, baud, address):
     TEMP_ZERO_CONSTANT = 40
 
     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
         try:
             ser = open_serial_port(self.port, self.baud_rate)
             if ser is not None:
                 result = self.read_status_data(ser)
                 ser.close()
-        except:
-            pass
+
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
 
         return result
 
     def get_settings(self):
-        self.capacity = BATTERY_CAPACITY
-        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.capacity = utils.BATTERY_CAPACITY
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
         return True
 
     def refresh_data(self):
@@ -95,8 +101,8 @@ def read_status_data(self, ser):
             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.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
         self.hardware_version = "DalyBMS " + str(self.cell_count) + " cells"
         logger.info(self.hardware_version)
@@ -104,8 +110,8 @@ def read_status_data(self, ser):
 
     def read_soc_data(self, ser):
         # Ensure data received is valid
-        crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1)
-        crntMaxValid = MAX_BATTERY_CHARGE_CURRENT * 1.3
+        crntMinValid = -(utils.MAX_BATTERY_DISCHARGE_CURRENT * 2.1)
+        crntMaxValid = utils.MAX_BATTERY_CHARGE_CURRENT * 1.3
         triesValid = 2
         while triesValid > 0:
             soc_data = self.read_serial_data_daly(ser, self.command_soc)
@@ -117,7 +123,7 @@ def read_soc_data(self, ser):
             current = (
                 (current - self.CURRENT_ZERO_CONSTANT)
                 / -10
-                * INVERT_CURRENT_MEASUREMENT
+                * utils.INVERT_CURRENT_MEASUREMENT
             )
             if crntMinValid < current < crntMaxValid:
                 self.voltage = voltage / 10
@@ -262,7 +268,7 @@ def read_cells_volts(self, ser):
                 return False
 
             frameCell = [0, 0, 0]
-            lowMin = MIN_CELL_VOLTAGE / 2
+            lowMin = utils.MIN_CELL_VOLTAGE / 2
             frame = 0
             bufIdx = 0
 
diff --git a/etc/dbus-serialbattery/lifepower.py b/etc/dbus-serialbattery/lifepower.py
index cc3058c0..84c93a37 100644
--- a/etc/dbus-serialbattery/lifepower.py
+++ b/etc/dbus-serialbattery/lifepower.py
@@ -1,8 +1,9 @@
 # -*- 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 read_serial_data, logger
+import utils
+from struct import unpack_from
 import re
 
 
@@ -24,14 +25,21 @@ 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()
+        result = False
+        try:
+            result = self.read_status_data()
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
+
+        return result
 
     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_current = utils.MAX_BATTERY_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
         hardware_version = self.read_serial_data_eg4(self.command_hardware_version)
         if hardware_version:
             # I get some characters that I'm not able to figure out the encoding, probably chinese so I discard it
@@ -89,8 +97,8 @@ def read_status_data(self):
 
         # Cells
         self.cell_count = len(groups[0])
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
         self.cells = [Cell(True) for _ in range(0, self.cell_count)]
         for i, cell in enumerate(self.cells):
diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py
index a8222fd6..29ac7068 100644
--- a/etc/dbus-serialbattery/lltjbd.py
+++ b/etc/dbus-serialbattery/lltjbd.py
@@ -1,7 +1,9 @@
 # -*- coding: utf-8 -*-
 from battery import Protection, Battery, Cell
-from utils import *
-from struct import *
+from utils import is_bit_set, read_serial_data, logger
+import utils
+from struct import unpack_from
+import struct
 
 
 class LltJbdProtection(Protection):
@@ -59,18 +61,22 @@ def __init__(self, port, baud, address):
     LENGTH_POS = 3
 
     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
         try:
             result = self.read_hardware_data()
-        except:
-            pass
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
 
         return result
 
     def get_settings(self):
         self.read_gen_data()
-        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
         return True
 
     def refresh_data(self):
@@ -79,7 +85,7 @@ def refresh_data(self):
         return result
 
     def to_protection_bits(self, byte_data):
-        tmp = bin(byte_data)[2:].rjust(13, zero_char)
+        tmp = bin(byte_data)[2:].rjust(13, utils.zero_char)
 
         self.protection.voltage_high = 2 if is_bit_set(tmp[10]) else 0
         self.protection.voltage_low = 2 if is_bit_set(tmp[9]) else 0
@@ -92,7 +98,7 @@ def to_protection_bits(self, byte_data):
 
         # Software implementations for low soc
         self.protection.soc_low = (
-            2 if self.soc < SOC_LOW_ALARM else 1 if self.soc < SOC_LOW_WARNING else 0
+            2 if self.soc < utils.SOC_LOW_ALARM else 1 if self.soc < utils.SOC_LOW_WARNING else 0
         )
 
         # extra protection flags for LltJbd
@@ -107,17 +113,17 @@ def to_cell_bits(self, byte_data, byte_data_high):
         for c in self.cells:
             self.cells.remove(c)
         # get up to the first 16 cells
-        tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), zero_char)
+        tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), utils.zero_char)
         for bit in reversed(tmp):
             self.cells.append(Cell(is_bit_set(bit)))
         # get any cells above 16
         if self.cell_count > 16:
-            tmp = bin(byte_data_high)[2:].rjust(self.cell_count - 16, zero_char)
+            tmp = bin(byte_data_high)[2:].rjust(self.cell_count - 16, utils.zero_char)
             for bit in reversed(tmp):
                 self.cells.append(Cell(is_bit_set(bit)))
 
     def to_fet_bits(self, byte_data):
-        tmp = bin(byte_data)[2:].rjust(2, zero_char)
+        tmp = bin(byte_data)[2:].rjust(2, utils.zero_char)
         self.charge_fet = is_bit_set(tmp[1])
         self.discharge_fet = is_bit_set(tmp[0])
 
@@ -152,15 +158,15 @@ def read_gen_data(self):
         self.version = float(str(version >> 4 & 0x0F) + "." + str(version & 0x0F))
         self.to_fet_bits(fet)
         self.to_protection_bits(protection)
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
         for t in range(self.temp_sensors):
             temp1 = unpack_from(">H", gen_data, 23 + (2 * t))[0]
             if t == 0:
-                self.to_temp("mos", kelvin_to_celsius(temp1 / 10))
+                self.to_temp("mos", utils.kelvin_to_celsius(temp1 / 10))
             else:
-                self.to_temp(t, kelvin_to_celsius(temp1 / 10))
+                self.to_temp(t, utils.kelvin_to_celsius(temp1 / 10))
 
         return True
 
diff --git a/etc/dbus-serialbattery/renogy.py b/etc/dbus-serialbattery/renogy.py
index fe493da6..ea513f23 100644
--- a/etc/dbus-serialbattery/renogy.py
+++ b/etc/dbus-serialbattery/renogy.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
-from battery import Protection, Battery, Cell
-from utils import *
+from battery import Battery, Cell
+from utils import read_serial_data, unpack, unpack_from, logger
+import utils
 import struct
 
 
@@ -16,7 +17,8 @@ def __init__(self, port, baud, address):
     LENGTH_CHECK = 4
     LENGTH_POS = 2
 
-    # command bytes [Address field][Function code (03 = Read register)][Register Address (2 bytes)][Data Length (2 bytes)][CRC (2 bytes little endian)]
+    # command bytes [Address field][Function code (03 = Read register)]
+    #                   [Register Address (2 bytes)][Data Length (2 bytes)][CRC (2 bytes little endian)]
     command_read = b"\x03"
     # Core data = voltage, temp, current, soc
     command_cell_count = b"\x13\x88\x00\x01"  # Register  5000
@@ -44,9 +46,9 @@ def test_connection(self):
         result = False
         try:
             result = self.read_gen_data()
-        except:
-            logger.exception("Unexpected exception encountered")
-            pass
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
 
         return result
 
@@ -54,11 +56,11 @@ 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_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
 
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
         return True
 
     def refresh_data(self):
@@ -203,7 +205,7 @@ def read_serial_data_renogy(self, command):
             return False
 
         start, flag, length = unpack_from("BBB", data)
-        checksum = unpack_from(">H", data, length + 3)
+        # checksum = unpack_from(">H", data, length + 3)
 
         if flag == 3:
             return data[3 : length + 3]
diff --git a/etc/dbus-serialbattery/sinowealth.py b/etc/dbus-serialbattery/sinowealth.py
index f5021ff5..60b41895 100755
--- a/etc/dbus-serialbattery/sinowealth.py
+++ b/etc/dbus-serialbattery/sinowealth.py
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
-from battery import Protection, Battery, Cell
-from utils import *
-from struct import *
+from battery import Battery, Cell
+from utils import kelvin_to_celsius, read_serial_data, logger
+import utils
+from struct import unpack_from
 
 
 class Sinowealth(Battery):
@@ -33,25 +34,30 @@ def __init__(self, port, baud, address):
     LENGTH_POS = 0
 
     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
         try:
             result = self.read_status_data()
             result = result and self.read_remaining_capacity()
             result = result and self.read_pack_config_data()
-        except:
-            pass
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
+
         return result
 
     def get_settings(self):
         # hardcoded parameters, to be requested from the BMS in the future
-        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
 
         if self.cell_count is None:
             self.read_pack_config_data()
 
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        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)
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index 04ac14ea..d90bbfbb 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -36,7 +36,7 @@ def _get_list_from_config(
 
 # Constants - Need to dynamically get them in future
 DRIVER_VERSION = "1.0"
-DRIVER_SUBVERSION = ".0-jkbms_ble (20230426)"
+DRIVER_SUBVERSION = ".0-jkbms_ble (20230427)"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 

From 8d3f25e5cacf340385191f59d23fb740c2160f8f Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 17:33:09 +0200
Subject: [PATCH 10/17] fixed black lint errors

---
 etc/dbus-serialbattery/hlpdatabms4s.py        | 23 ++++++++++---------
 .../hlpdatabms4s_miniterm.py                  | 13 +++++------
 2 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/etc/dbus-serialbattery/hlpdatabms4s.py b/etc/dbus-serialbattery/hlpdatabms4s.py
index 63995357..6fcbc6b0 100644
--- a/etc/dbus-serialbattery/hlpdatabms4s.py
+++ b/etc/dbus-serialbattery/hlpdatabms4s.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
-from battery import Protection, Battery, Cell
-from utils import *
-from struct import *
+from battery import Battery, Cell
+from utils import logger
+import utils
 import serial
 from time import sleep
 
@@ -20,9 +20,10 @@ def test_connection(self):
         result = False
         try:
             result = self.read_test_data()
-        except Exception as e:
-            # logger.error(e, exc_info=True)
-            pass
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
+
         return result
 
     def get_settings(self):
@@ -33,7 +34,7 @@ def get_settings(self):
         try:
             result = self.read_settings_data()
         except Exception as e:
-            # logger.error(e, exc_info=True)
+            logger.error(e, exc_info=True)
             pass
         return result
 
@@ -45,7 +46,7 @@ def refresh_data(self):
         try:
             result = self.read_status_data()
         except Exception as e:
-            # logger.error(e, exc_info=True)
+            logger.error(e, exc_info=True)
             pass
         return result
 
@@ -71,8 +72,8 @@ def read_test_data(self):
         if ix > 0:
             self.hardware_version = s1[ix : len(s1) - 1]
             self.version = self.hardware_version
-            self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-            self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+            self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+            self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
             self.poll_interval = 10000
             self.control_discharge_current = 1000
             self.control_charge_current = 1000
@@ -201,7 +202,7 @@ def read_serial_data2(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)
-            if not ret is False:
+            if False is not ret:
                 return ret
         return False
 
diff --git a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py
index 741ac75a..22df77fd 100644
--- a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py
+++ b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py
@@ -19,7 +19,6 @@
 import serial
 from serial.tools.list_ports import comports
 from serial.tools import hexlify_codec
-from time import sleep
 
 # pylint: disable=wrong-import-order,wrong-import-position
 
@@ -549,10 +548,10 @@ def reader(self):
             # read all that is there or wait for one byte
             try:
                 data2 = self.serial.read(self.serial.in_waiting or 1)
-            except:
+            except Exception:
                 data2 = b""
             if data2:
-                if self.remove_no_write == True:
+                if self.remove_no_write is True:
                     self.remove_no_write = False
                     self.no_write = False
                     data = data2
@@ -567,7 +566,7 @@ def reader(self):
                             text = transformation.rx(text)
                         if b"m1\n" in data:
                             self.no_write = True
-                        if self.no_write == False:
+                        if self.no_write is False:
                             self.console.write(text)
                     data = b""
 
@@ -606,7 +605,7 @@ def writer(self):
                         for transformation in self.tx_transformations:
                             echo_text = transformation.echo(echo_text)
                         self.console.write(echo_text)
-        except:
+        except Exception:
             self.alive = False
             raise
 
@@ -1163,12 +1162,12 @@ def main(
             )
         )
         sys.stderr.write(
-            "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format(
+            "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format(  # noqa: F522
                 p=miniterm.serial
             )
         )
         sys.stderr.write(
-            "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format(
+            "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format(  # noqa: F522
                 p=miniterm.serial
             )
         )

From a4a3efbb19ea9984ce9870c146c843e8fe775a33 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 17:33:30 +0200
Subject: [PATCH 11/17] change flake8 settings

---
 .flake8      | 10 +++++-----
 CHANGELOG.md |  2 ++
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/.flake8 b/.flake8
index 5e3bb3fe..fcc7f0bf 100644
--- a/.flake8
+++ b/.flake8
@@ -2,17 +2,17 @@
 max-line-length = 120
 exclude =
           #./etc/dbus-serialbattery/ant.py,
-          #./etc/dbus-serialbattery/battery_template.py,
+          ./etc/dbus-serialbattery/battery_template.py,
           #./etc/dbus-serialbattery/daly.py,
           #./etc/dbus-serialbattery/dbus-serialbattery.py,
-          #./etc/dbus-serialbattery/dbushelper.py,
+          ./etc/dbus-serialbattery/dbushelper.py,
           #./etc/dbus-serialbattery/ecs.py,
           #./etc/dbus-serialbattery/lifepower.py,
           #./etc/dbus-serialbattery/lltjbd.py,
-          #./etc/dbus-serialbattery/minimalmodbus.py,
-          #./etc/dbus-serialbattery/mnb.py,
+          ./etc/dbus-serialbattery/minimalmodbus.py,
+          ./etc/dbus-serialbattery/mnb.py,
           #./etc/dbus-serialbattery/renogy.py,
-          #./etc/dbus-serialbattery/revov.py,
+          ./etc/dbus-serialbattery/revov.py,
           #./etc/dbus-serialbattery/sinowealth.py,
           ./etc/dbus-serialbattery/test_max17853.py,
           ./etc/dbus-serialbattery/util_max17853.py,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12d2ee32..9aa162c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@
 * Added: Show charge/discharge limitation reason
 * Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected
 * Added: Show TimeToGo in GUI only, if enabled
+* Added: Support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 by @peterohman
+* Added: Support for Seplos BMS https://github.com/Louisvdw/dbus-serialbattery/pull/530 by @wollew
 * Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable)
 * Changed: `reinstalllocal.sh` to recreate `/data/conf/serial-starter.d` if deleted by `disabledriver.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository
 * Changed: Added QML to `restoregui.sh`

From 4a26d0d5f4ce9f60058848eef0969cff67d6612f Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 17:39:58 +0200
Subject: [PATCH 12/17] remove wildcard import and fix black lint errors

---
 etc/dbus-serialbattery/lltjbd.py |  6 +++++-
 etc/dbus-serialbattery/seplos.py | 12 +++++++-----
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py
index 29ac7068..c285d44a 100644
--- a/etc/dbus-serialbattery/lltjbd.py
+++ b/etc/dbus-serialbattery/lltjbd.py
@@ -98,7 +98,11 @@ def to_protection_bits(self, byte_data):
 
         # Software implementations for low soc
         self.protection.soc_low = (
-            2 if self.soc < utils.SOC_LOW_ALARM else 1 if self.soc < utils.SOC_LOW_WARNING else 0
+            2
+            if self.soc < utils.SOC_LOW_ALARM
+            else 1
+            if self.soc < utils.SOC_LOW_WARNING
+            else 0
         )
 
         # extra protection flags for LltJbd
diff --git a/etc/dbus-serialbattery/seplos.py b/etc/dbus-serialbattery/seplos.py
index a5319dcf..1adf12bf 100644
--- a/etc/dbus-serialbattery/seplos.py
+++ b/etc/dbus-serialbattery/seplos.py
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
 from battery import Protection, Battery, Cell
-from utils import *
+from utils import logger
+import utils
+import serial
 
 
 def int_from_hex_ascii(to_decode, signed=False):
@@ -97,10 +99,10 @@ def get_settings(self):
 
         # Uncomment if BMS does not supply capacity
         # self.capacity = BATTERY_CAPACITY
-        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
         # init the cell array
         for _ in range(self.cell_count):

From 564d4edc151500bdf00dac54f76ffd9822b51893 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 17:47:43 +0200
Subject: [PATCH 13/17] removed wildcard import

---
 etc/dbus-serialbattery/ecs.py | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/etc/dbus-serialbattery/ecs.py b/etc/dbus-serialbattery/ecs.py
index d8bc7295..1777e6f0 100644
--- a/etc/dbus-serialbattery/ecs.py
+++ b/etc/dbus-serialbattery/ecs.py
@@ -1,8 +1,8 @@
 # -*- 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 utils import logger
+import utils
 import minimalmodbus
 
 
@@ -33,7 +33,7 @@ def test_connection(self):
 
         # Trying to find Green Meter ID
         try:
-            mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS)
+            mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS)
             mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN
             tmpId = mbdev.read_register(0, 0)
             if tmpId in range(self.GREENMETER_ID_500A, self.GREENMETER_ID_125A + 1):
@@ -52,7 +52,7 @@ def test_connection(self):
 
     def find_LiPro_cells(self):
         # test for LiPro cell devices
-        for cell_address in range(LIPRO_START_ADDRESS, LIPRO_END_ADDRESS + 1):
+        for cell_address in range(utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1):
             try:
                 mbdev = minimalmodbus.Instrument(self.port, cell_address)
                 mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN
@@ -74,11 +74,11 @@ def get_settings(self):
         # Return True if success, False for failure
 
         # Uncomment if BMS does not supply capacity
-        self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT
-        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
-        self.cell_count = LIPRO_CELL_COUNT
-        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
-        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
+        self.cell_count = utils.LIPRO_CELL_COUNT
+        self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
         self.temp_sensors = 2
 
         return self.read_status_data()
@@ -94,7 +94,7 @@ def refresh_data(self):
 
     def read_status_data(self):
         try:
-            mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS)
+            mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS)
             mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN
 
             self.max_battery_discharge_current = abs(
@@ -120,7 +120,7 @@ def read_status_data(self):
 
     def read_soc_data(self):
         try:
-            mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS)
+            mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS)
             mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN
 
             self.voltage = (

From 41cdadb837e83fc5b336f748f6cfd3fb03a7b813 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 17:53:00 +0200
Subject: [PATCH 14/17] fixed black lint check

---
 etc/dbus-serialbattery/ecs.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/etc/dbus-serialbattery/ecs.py b/etc/dbus-serialbattery/ecs.py
index 1777e6f0..41065b7e 100644
--- a/etc/dbus-serialbattery/ecs.py
+++ b/etc/dbus-serialbattery/ecs.py
@@ -52,7 +52,9 @@ def test_connection(self):
 
     def find_LiPro_cells(self):
         # test for LiPro cell devices
-        for cell_address in range(utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1):
+        for cell_address in range(
+            utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1
+        ):
             try:
                 mbdev = minimalmodbus.Instrument(self.port, cell_address)
                 mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN

From 440b113100f6b4ae051103f63a26ee5a53ec28e6 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 18:23:26 +0200
Subject: [PATCH 15/17] config changes

---
 .flake8      |   7 ----
 .gitignore   |   5 ++-
 CHANGELOG.md | 104 ++++++++++++++++++++++++++-------------------------
 3 files changed, 57 insertions(+), 59 deletions(-)

diff --git a/.flake8 b/.flake8
index fcc7f0bf..85d1bd06 100644
--- a/.flake8
+++ b/.flake8
@@ -1,19 +1,12 @@
 [flake8]
 max-line-length = 120
 exclude =
-          #./etc/dbus-serialbattery/ant.py,
           ./etc/dbus-serialbattery/battery_template.py,
-          #./etc/dbus-serialbattery/daly.py,
           #./etc/dbus-serialbattery/dbus-serialbattery.py,
           ./etc/dbus-serialbattery/dbushelper.py,
-          #./etc/dbus-serialbattery/ecs.py,
-          #./etc/dbus-serialbattery/lifepower.py,
-          #./etc/dbus-serialbattery/lltjbd.py,
           ./etc/dbus-serialbattery/minimalmodbus.py,
           ./etc/dbus-serialbattery/mnb.py,
-          #./etc/dbus-serialbattery/renogy.py,
           ./etc/dbus-serialbattery/revov.py,
-          #./etc/dbus-serialbattery/sinowealth.py,
           ./etc/dbus-serialbattery/test_max17853.py,
           ./etc/dbus-serialbattery/util_max17853.py,
           ./velib_python
diff --git a/.gitignore b/.gitignore
index 287a9c8b..59bf8fe4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -151,6 +151,9 @@ cython_debug/
 #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
 #.idea/
 
+# VS Code
+.vscode/
+
 # Custom for this repo
 venus-data.tar.gz
 BMS-trials
@@ -161,4 +164,4 @@ BMS-trials
 etc/dbus-serialbattery/config.ini
 
 # Local Clone of velib_python
-velib_python
\ No newline at end of file
+velib_python
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9aa162c6..ba0d4aa5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,58 +2,60 @@
 
 ## v1.0.0-jkbms_ble
 
-* Added: Balancing status for JKBMS
-* Added: Balancing switch status for JKBMS
-* Added: Balancing switch status to the GUI -> SerialBattery -> IO
-* Added: Charge Mode display
-* Added: Choose how battery temperature is assembled (mean temp 1 & 2, only temp 1 or only temp 2)
-* Added: Create empty `config.ini` for easier user usage
-* Added: Cronjob to restart Bluetooth service every 12 hours
-* Added: Driver uninstall script
-* Added: Fix for Venus OS >= v3.00~14 showing unused items https://github.com/Louisvdw/dbus-serialbattery/issues/469
-* Added: HighInternalTemperature alarm (MOSFET) for JKBMS
-* Added: Install needed components automatically after a Venus OS upgrade
-* Added: JKBMS - MOS temperature https://github.com/Louisvdw/dbus-serialbattery/pull/440
-* Added: JKBMS BLE - Balancing switch status
-* Added: JKBMS BLE - Capacity
-* Added: JKBMS BLE - Cell imbalance alert
-* Added: JKBMS BLE - Charging switch status
-* Added: JKBMS BLE - Discharging switch status
-* Added: JKBMS BLE - MOS temperature
-* Added: JKBMS BLE - Show if balancing is active and which cells are balancing
-* Added: Post install notes
-* Added: Script to install directly from repository
-* Added: Show charge mode (absorption, bulk, ...) in Parameters page
-* Added: Show charge/discharge limitation reason
-* Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected
-* Added: Show TimeToGo in GUI only, if enabled
+* Added: Balancing status for JKBMS by @mr-manuel
+* Added: Balancing switch status for JKBMS by @mr-manuel
+* Added: Balancing switch status to the GUI -> SerialBattery -> IO by @mr-manuel
+* Added: Charge Mode display by @mr-manuel
+* Added: Choose how battery temperature is assembled (mean temp 1 & 2, only temp 1 or only temp 2) by @mr-manuel
+* Added: Config file by @ppuetsch
+* Added: Create empty `config.ini` for easier user usage by @mr-manuel
+* Added: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel
+* Added: Driver uninstall script by @mr-manuel
+* Added: Fix for Venus OS >= v3.00~14 showing unused items https://github.com/Louisvdw/dbus-serialbattery/issues/469 by @mr-manuel
+* Added: HighInternalTemperature alarm (MOSFET) for JKBMS by @mr-manuel
+* Added: Install needed components automatically after a Venus OS upgrade by @mr-manuel
+* Added: JKBMS - MOS temperature https://github.com/Louisvdw/dbus-serialbattery/pull/440 by @mr-manuel
+* Added: JKBMS BLE - Balancing switch status by @mr-manuel
+* Added: JKBMS BLE - Capacity by @mr-manuel
+* Added: JKBMS BLE - Cell imbalance alert by @mr-manuel
+* Added: JKBMS BLE - Charging switch status by @mr-manuel
+* Added: JKBMS BLE - Discharging switch status by @mr-manuel
+* Added: JKBMS BLE - MOS temperature by @mr-manuel
+* Added: JKBMS BLE - Show if balancing is active and which cells are balancing by @mr-manuel
+* Added: Post install notes by @mr-manuel
+* Added: Script to install directly from repository by @mr-manuel
+* Added: Show charge mode (absorption, bulk, ...) in Parameters page by @mr-manuel
+* Added: Show charge/discharge limitation reason by @mr-manuel
+* Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected by @mr-manuel
+* Added: Show TimeToGo in GUI only, if enabled by @mr-manuel
 * Added: Support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 by @peterohman
 * Added: Support for Seplos BMS https://github.com/Louisvdw/dbus-serialbattery/pull/530 by @wollew
-* Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable)
-* Changed: `reinstalllocal.sh` to recreate `/data/conf/serial-starter.d` if deleted by `disabledriver.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository
-* Changed: Added QML to `restoregui.sh`
-* Changed: Bash output
-* Changed: Default config file
-  * Added missing descriptions to make it much clearer to understand
-  * Changed name from `default_config.ini` to `config.default.ini` https://github.com/Louisvdw/dbus-serialbattery/pull/412#issuecomment-1434287942
-  * Changed TimeToSoc default value `TIME_TO_SOC_VALUE_TYPE` from `Both seconds and time string "<seconds> [<days>d <hours>h <minutes>m <seconds>s]"` to `1 Seconds`
-  * Changed TimeToSoc description
-  * Changed value positions, added groups and much clearer descriptions
-* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239
-* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311
-* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351
+* Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable) by @mr-manuel
+* Changed: `reinstalllocal.sh` to recreate `/data/conf/serial-starter.d` if deleted by `disabledriver.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository by @mr-manuel
+* Changed: Added QML to `restoregui.sh` by @mr-manuel
+* Changed: Bash output by @mr-manuel
+* Changed: Default config file by @mr-manuel
+  * Added missing descriptions to make it much clearer to understand by @mr-manuel
+  * Changed name from `default_config.ini` to `config.default.ini` https://github.com/Louisvdw/dbus-serialbattery/pull/412#issuecomment-1434287942 by @mr-manuel
+  * Changed TimeToSoc default value `TIME_TO_SOC_VALUE_TYPE` from `Both seconds and time string "<seconds> [<days>d <hours>h <minutes>m <seconds>s]"` to `1 Seconds` by @mr-manuel
+  * Changed TimeToSoc description by @mr-manuel
+  * Changed value positions, added groups and much clearer descriptions by @mr-manuel
+* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239 by @mr-manuel
+* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311 by @mr-manuel
+* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351 by @mr-manuel
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/397 by @transistorgit
-* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421
-* Changed: Fixed black lint errors
-* Changed: Fixed cell balancing background for cells 17-24
-* Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018
+* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421 by @mr-manuel
+* Changed: Fixed black lint errors by @mr-manuel
+* Changed: Fixed cell balancing background for cells 17-24 by @mr-manuel
+* Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018 by @mr-manuel
 * Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 by @aaronreek
-* Changed: Logging to get relevant data
-* Changed: Moved ble part to `installble.sh`
-* Changed: Optimized installation scripts
-* Changed: Serial-Starter file is now created from `reinstalllocal.sh`. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/520
-* Changed: Separate Time-To-Go and Time-To-SoC activation
-* Changed: Temperature alarm changed in order to not trigger all in the same condition for JKBMS
-* Changed: Time-To-Soc repetition from cycles to seconds. Minimum value is every 5 seconds. This prevents CPU overload and ensures system stability. Renamed `TIME_TO_SOC_LOOP_CYCLES` to `TIME_TO_SOC_RECALCULATE_EVERY`
-* Changed: Time-To-Soc string from `days, HR:MN:SC` to `<days>d <hours>h <minutes>m <seconds>s` (same as Time-To-Go)
-* Changed: Uninstall also installed Bluetooth modules on uninstall.
+* Changed: Logging to get relevant data by @mr-manuel
+* Changed: Moved ble part to `installble.sh` by @mr-manuel
+* Changed: Optimized installation scripts by @mr-manuel
+* Changed: Removed wildcard imports from several BMS drivers and fixed black lint errors by @mr-manuel
+* Changed: Serial-Starter file is now created from `reinstalllocal.sh`. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/520 by @mr-manuel
+* Changed: Separate Time-To-Go and Time-To-SoC activation by @mr-manuel
+* Changed: Temperature alarm changed in order to not trigger all in the same condition for JKBMS by @mr-manuel
+* Changed: Time-To-Soc repetition from cycles to seconds. Minimum value is every 5 seconds. This prevents CPU overload and ensures system stability. Renamed `TIME_TO_SOC_LOOP_CYCLES` to `TIME_TO_SOC_RECALCULATE_EVERY` by @mr-manuel
+* Changed: Time-To-Soc string from `days, HR:MN:SC` to `<days>d <hours>h <minutes>m <seconds>s` (same as Time-To-Go) by @mr-manuel
+* Changed: Uninstall also installed Bluetooth modules on uninstall. by @mr-manuel

From bc91973ca9d4fb5af8ea446b36cb2f29dee397cd Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 18:53:07 +0200
Subject: [PATCH 16/17] remove old log message in handle_changed_setting()

---
 etc/dbus-serialbattery/dbushelper.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py
index 36b3c819..0a629778 100644
--- a/etc/dbus-serialbattery/dbushelper.py
+++ b/etc/dbus-serialbattery/dbushelper.py
@@ -94,10 +94,6 @@ def handle_changed_setting(self, setting, oldvalue, newvalue):
             self.battery.role, self.instance = self.get_role_instance()
             logger.info("Changed DeviceInstance = %d", self.instance)
             return
-        logger.info(
-            "Changed DeviceInstance = %d", float(self.settings["CellVoltageMin"])
-        )
-        # self._dbusservice['/History/ChargeCycles']
 
     def setup_vedbus(self):
         # Set up dbus service and device instance

From 83668b62b558af7b052d93d570588e42f531d8c4 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 27 Apr 2023 19:09:18 +0200
Subject: [PATCH 17/17] simplified condition for Time-To-Go/Soc

---
 etc/dbus-serialbattery/dbushelper.py | 14 +++-----------
 etc/dbus-serialbattery/mnb.py        |  2 +-
 2 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py
index 0a629778..246a1aad 100644
--- a/etc/dbus-serialbattery/dbushelper.py
+++ b/etc/dbus-serialbattery/dbushelper.py
@@ -498,22 +498,14 @@ def publish_dbus(self):
             except:
                 pass
 
-        # Update TimeToSoC
+        # Update TimeToGo and/or TimeToSoC
         try:
             if (
                 self.battery.capacity is not None
                 and (TIME_TO_GO_ENABLE or len(TIME_TO_SOC_POINTS) > 0)
                 and (
-                    (
-                        # update only once in same second
-                        int(time()) != self.battery.time_to_soc_update
-                        and
-                        # update only every x seconds
-                        int(time()) % TIME_TO_SOC_RECALCULATE_EVERY == 0
-                    )
-                    or
-                    # update on first run
-                    self.battery.time_to_soc_update == 0
+                    int(time()) - self.battery.time_to_soc_update
+                    >= TIME_TO_SOC_RECALCULATE_EVERY
                 )
             ):
                 self.battery.time_to_soc_update = int(time())
diff --git a/etc/dbus-serialbattery/mnb.py b/etc/dbus-serialbattery/mnb.py
index 8e91bd5b..ce627a39 100644
--- a/etc/dbus-serialbattery/mnb.py
+++ b/etc/dbus-serialbattery/mnb.py
@@ -3,7 +3,7 @@
 from struct import *
 
 # from test_max17853 import *#{these two lines are mutually}
-from util_max17853 import *  # {exclusive. use test for testing}
+# from util_max17853 import *  # {exclusive. use test for testing}
 
 
 class MNBProtection(Protection):