From 2f6ea71ef606c9aabdc253abb8ef16df3dd92f74 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 18 May 2023 23:55:08 +0200
Subject: [PATCH 01/21] Changes 2023.05.18 (#649)

* Added: Show specific TimeToSoC points in GUI
Only if 0%, 10%, 20%, 80%, 90% and/or 100% are selected

* Added: Show specific TimeToSoC points in GUI
Only if 0%, 10%, 20%, 80%, 90% and/or 100% are selected

* fix black lint error

* fix black lint error

* Improved JBD BMS soc calculation
https://github.com/Louisvdw/dbus-serialbattery/pull/439

* Fix for #397
https://github.com/Louisvdw/dbus-serialbattery/pull/484

* small fixes

* sort bms imports

* Add support for HLPdata BMS4S
https://github.com/Louisvdw/dbus-serialbattery/pull/505

* Add support for Seplos BMS
https://github.com/Louisvdw/dbus-serialbattery/pull/530

* change flake8 settings

* fix black lint errors

* removed wildcard imports

* fixed black lint errors

* change flake8 settings

* remove wildcard import and fix black lint errors

* removed wildcard import

* fixed black lint check

* removed wildcard import, fixed black lint errors

* config changes

* removed wildcard import, fixed black lint errors

* remove old log message in handle_changed_setting()

* remove old log message in handle_changed_setting()

* simplified condition for Time-To-Go/Soc

* simplified condition for Time-To-Go/Soc

* fix renogy import

* fix renogy import

* added BMS info and cleanup
* MNB
* Revov
* Sinowealth

* added BMS info and cleanup
* MNB
* Revov
* Sinowealth

* moved BMS to subfolder

* moved BMS to subfolder

* moved BMS to subfolder

* corrected installble to run correct script

* Added self.unique_identifier to the battery class
Used to identify a BMS when multiple BMS are connected
planned for future use

* Added self.unique_identifier to the battery class
Used to identify a BMS when multiple BMS are connected
planned for future use

* changed ble service name
from `dbus-blebattery-$1` to `dbus-blebattery.$1` like the non ble service

* fix small errors

* read installed capacity at startup

* disable ANT BMS by default
https://github.com/Louisvdw/dbus-serialbattery/issues/479

* fix cell voltage header parser

* rework daly receive routine

* improve read cell voltages - only work on sufficient data, drop only the bad package on checksum error, not the complete transmission

* allow read_soc to also retry serial transmission

* add daly cell balance state info. cells are red only if unbalanced now

* bump version

* typo

* moved read_serialport_data() to daly.py

* revert read_serialport_data() to the state before my changes

* fix connection log startup message.
now voltage/current/soc are displayed correctly

* black reformatting

* added linear voltage recalculation interval
In the config file can now be defined how often CVL, CCL and DCL is recalculated

* revert Daly adaption

* replaced penalty voltage calculation
with automatically calculated penalty voltages to simplify config
max voltage is kept until batteries are balanced

* flake config change

* flake config change

* added linear voltage recalculation interval
In the config file can now be defined how often CVL, CCL and DCL is recalculated

* replaced penalty voltage calculation
with automatically calculated penalty voltages to simplify config
max voltage is kept until batteries are balanced

* fix black lint errors

* updated changelog

* disabled ANT BMS by default
https://github.com/Louisvdw/dbus-serialbattery/issues/479

* updated config.default.ini

* fix typo

* update nightly install script

* Removed line

* fixed error in HLPdataBMS4S

* fixed wrong variable assignment
`str` instead of `int`

* fixed wrong variable assignment
`str` instead of `int`

* updated battery template

* updated battery template

* Fix for #450
https://github.com/Louisvdw/dbus-serialbattery/issues/450

* Read charge/discharge limit JKBMS
https://github.com/Louisvdw/dbus-serialbattery/issues/4

* updated battery template

* Progress with config limits reason

* updated CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT default value

* added SoC round for LLT/JBD

* fixed log typo

* updated nightly script

* Fix for #450
https://github.com/Louisvdw/dbus-serialbattery/issues/450

* Read charge/discharge limit JKBMS
https://github.com/Louisvdw/dbus-serialbattery/issues/4

* reworked installation procedure
Bluetooth BMS is now also fetched from config.ini

* updated release workflow

* updated readme

* Merge branch 'master' into jkbms_ble

* deploy to github pages only
on changes in master or docusaurus branch

* cleanup

* Merge branch 'master' of into jkbms_ble

* GitHub pages config change

* GitHub pages config change

* cleanup

* Renamed scripts for better reading #532

* update docusaurus dependencies

* Renamed scripts for better reading #532

* update docusaurus dependencies

* change sh with bash

* limitation reason cleanup

* limitation reason cleanup

* changed default config settings
FLOAT_CELL_VOLTAGE from 3.350V to 3.375V
LINEAR_LIMITATION_ENABLE from False to True

* changed default config settings
FLOAT_CELL_VOLTAGE from 3.350V to 3.375V
LINEAR_LIMITATION_ENABLE from False to True

* removed testing line

* Cleanup duplicated files

Files were moved and not deleted

* Cleanup

* MOSFET temperature was displayed twice after merge

* small typo fixes

* updated changelog

* updated changelog

* Small fixes

* fix disconnection behaviour: on disconnect, show '---' after 10s and 'not connected' after 60s

* fix flake errors

* small fix

* fix disconnection behaviour & small fixes
* on disconnect, show '---' after 10s and 'not connected' after 60s by @transistorgit
* small fixes in shell script
* added restart driver script

* fixed file permission

* Added: apply max voltage if CVCM_ENABLE is False
before float voltage was applied

* fixed type error

* Added: BMS disconnect behaviour
* Choose to block charge/discharge on disconnect
* Trigger Venus OS alarm

* Changed: Remove wildcard import from dbushelper.py

* small fixes

* Added: apply max voltage if CVCM_ENABLE is False
before float voltage was applied

* Added: BMS disconnect behaviour
* Choose to block charge/discharge on disconnect
* Trigger Venus OS alarm

* Changed: Remove wildcard import from dbushelper.py

* flake8 changes

* copied lltjbd_ble from idstein:jdb_ble

* Added and adapted LltJbd_Ble
ATTENTION: Currently it's untested

* small changes

* read production date and append to hardware version

* Set SOC nightly. Button is working, next is send command to bms

* Added: Show additional data in device page
* show self.unique_identifier as serial number
* show self.production as device name

* Added: JKBMS unique identifier & fixed data length

* Added: JKBMS BLE unique identifier

* Added: Jkbms_Ble connection_name()

* Added: Daly unique identifier

* Added: JKBMS BLE serial number, user defined field

* Added: Show additional data in device page
* show self.unique_identifier as serial number
* show self.production as device name

* Added: JKBMS unique identifier & fixed data length

* move config.ini before update

* read production date by @tranistorgit
this adds the battery production date

* read out daly battery code and use as unique id

* moved production date and added custom field

* clean battery code
strip whitespace and replace one or multiple spaces with one underline
if no battery code generate unique field

* Daly read_capacity change
Read capacity from config file, if no value provided by BMS

* Daly try to fix no reply

* Improvements by @transistorgit

* changed value

* set SOC (and date time) on button press.

* fix battery code parser

* format fix

* format fix

* fix extra long serial timeouts
by calculating max time instead of loop counts

* Changed: Merged all install files into one

* updated install docs for nightly build

* Small fixes

* changed config backup

* updated config file

* updated changelog

* debug daly

* changed release workflow

* changed release workflow

* changed release workflow

* changed release workflow

* Updated from master

* fix blank screen, debug daly

* make Reset SoC a spin box

* fix possible read_capacity problem

* Daly read_balance_state() add missing return

* Daly advanced troubleshooting

* Changed: Improved Daily stability by a lot

* fixes for disable and uninstall
service was not removed and if removed, it was recreated by the serial starter

* optimized USB install method

* updated changelog

* added missing qml to restore-gui.sh

* optimized USB install method

* Daly improvements
* Set SoC on button press by @transistorgit
* Improved driver stability by @transistorgit & @mr-manuel

* moved production date and added custom field

* changed jkbms_ble to dev

* changed order

* final daly 'broken packages handling'

* Last changes for daly read problem
by @transistorgit

* change version in utils based on GitHub tag

* test automatic release version change

* Added: Configure voltage drop

* test automatic release version change

* fix Daly alarms

* fixes small errors in bash files

* fix missing driver name in restart-driver.sh

* linear mode, allow max voltage on soc thesshold

* Daly added one retry if failed

* fixed LLT/JBD cell balancing display

* rename tar after USB install
fixes https://github.com/Louisvdw/dbus-serialbattery/issues/638

* add force buttons

* force buttons working

* fixed removing entries

* implement force charge/discharge

* little bit cleaner soc preset

* use existing serial read/write function

* add stop balancong switch

* use existing serial read/write function

* Changed: Get bg colors from MbStyle for dark mode

* prevent short circuit evaluation

* fix merge errors

* added changelog info

* Fix #648

---------

Co-authored-by: Bernd Stahlbock <stahlbock@gmail.com>
---
 CHANGELOG.md                                  |   8 +-
 etc/dbus-serialbattery/battery.py             |  27 ++-
 etc/dbus-serialbattery/bms/daly.py            | 175 ++++++++++++------
 etc/dbus-serialbattery/bms/lifepower.py       |   2 +-
 etc/dbus-serialbattery/bms/lltjbd.py          |  38 ++++
 etc/dbus-serialbattery/config.default.ini     |  26 ++-
 etc/dbus-serialbattery/dbus-serialbattery.py  |   4 +-
 etc/dbus-serialbattery/dbushelper.py          |  22 ++-
 etc/dbus-serialbattery/disable.sh             |  24 ++-
 etc/dbus-serialbattery/install.sh             |   4 +-
 .../qml/PageBatteryCellVoltages.qml           |  50 ++---
 etc/dbus-serialbattery/qml/PageLynxIonIo.qml  |  18 ++
 etc/dbus-serialbattery/reinstall-local.sh     |   5 +-
 etc/dbus-serialbattery/restart-driver.sh      |   2 +-
 etc/dbus-serialbattery/uninstall.sh           |  52 ++----
 etc/dbus-serialbattery/utils.py               |  60 ++++--
 rc/post-hook.sh                               |   7 +
 17 files changed, 362 insertions(+), 162 deletions(-)
 mode change 100755 => 100644 etc/dbus-serialbattery/install.sh

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30822f96..ad28b18d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,8 +20,10 @@
 * Added: Daly BMS - Read production date and build unique identifier by @transistorgit
 * Added: Daly BMS - Set SoC by @transistorgit
 * Added: Daly BMS - Show "battery code" field that can be set in the Daly app by @transistorgit
+* Added: Daly BMS - Discharge / Charge Mosfet switching over remote console/GUI https://github.com/Louisvdw/dbus-serialbattery/issues/26 by @transistorgit
 * Added: Device name field (found in the GUI -> SerialBattery -> Device), that show a custom string that can be set in some BMS, if available by @mr-manuel
 * Added: Driver uninstall script by @mr-manuel
+* Added: Rename TAR file after USB/SD card install to not overwrite the data on every reboot https://github.com/Louisvdw/dbus-serialbattery/issues/638 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: Improved maintainability (flake8, black lint), introduced code checks and automate release build https://github.com/Louisvdw/dbus-serialbattery/pull/386 by @ppuetsch
@@ -38,6 +40,7 @@
 * Added: JKBMS BLE - Show serial number and "User Private Data" field that can be set in the JKBMS App to identify the BMS in a multi battery environment by @mr-manuel
 * Added: JKBMS BLE driver by @baranator
 * Added: Possibility to add `config.ini` to the root of a USB flash drive on install via the USB method by @mr-manuel
+* Added: Possibility to configure a `VOLTAGE_DROP` voltage, if you are using a SmartShunt as battery monitor as there is a little voltage difference https://github.com/Louisvdw/dbus-serialbattery/discussions/632 by @mr-manuel
 * Added: Post install notes by @mr-manuel
 * Added: Read charge/discharge limits from JKBMS by @mr-manuel
 * Added: Recalculation interval in linear mode for CVL, CCL and DCL by @mr-manuel
@@ -56,6 +59,7 @@
 * Changed: `reinstall-local.sh` to recreate `/data/conf/serial-starter.d`, if deleted by `disable.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository by @mr-manuel
 * Changed: Added QML to `restore-gui.sh` by @mr-manuel
 * Changed: Bash output by @mr-manuel
+* Changed: Daly BMS - Fixed BMS alerts by @mr-manuel
 * Changed: Daly BMS - Improved driver stability by @transistorgit & @mr-manuel
 * Changed: Default config file by @ppuetsch
   * Added missing descriptions to make it much clearer to understand by @mr-manuel
@@ -73,8 +77,10 @@
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/397 by @transistorgit
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421 by @mr-manuel
 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/450 by @mr-manuel
+* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/648 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 cell balancing display for JBD/LLT BMS https://github.com/Louisvdw/dbus-serialbattery/issues/359 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 install workflow via USB flash drive by @mr-manuel
 * Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 by @aaronreek
@@ -83,7 +89,7 @@
 * Changed: Moved Bluetooth part to `reinstall-local.sh` by @mr-manuel
 * Changed: Moved BMS scripts to subfolder by @mr-manuel
 * Changed: Removed all wildcard imports and fixed black lint errors by @mr-manuel
-* Changed: Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced by @mr-manuel
+* Changed: CVL calculation improvement. Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced or SoC is below threshold by @mr-manuel
 * Changed: Renamed scripts for better reading #532 by @mr-manuel
 * Changed: Reworked and optimized installation scripts by @mr-manuel
 * Changed: Separate Time-To-Go and Time-To-SoC activation by @mr-manuel
diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index 108cf211..767be09d 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -207,18 +207,21 @@ def manage_charge_voltage_linear(self) -> None:
                 voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage()
 
                 if self.max_voltage_start_time is None:
+                    # start timer, if max voltage is reached and cells are balanced
                     if (
-                        utils.MAX_CELL_VOLTAGE * self.cell_count <= voltageSum
+                        (utils.MAX_CELL_VOLTAGE * self.cell_count) - utils.VOLTAGE_DROP
+                        <= voltageSum
                         and voltageDiff
                         <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL
                         and self.allow_max_voltage
                     ):
                         self.max_voltage_start_time = time()
+
+                    # allow max voltage again, if cells are unbalanced or SoC threshold is reached
                     elif (
-                        # utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
-                        voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
-                        and not self.allow_max_voltage
-                    ):
+                        utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
+                        or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
+                    ) and not self.allow_max_voltage:
                         self.allow_max_voltage = True
                 else:
                     tDiff = time() - self.max_voltage_start_time
@@ -309,9 +312,8 @@ def manage_charge_voltage_step(self) -> None:
                 if self.max_voltage_start_time is None:
                     # check if max voltage is reached and start timer to keep max voltage
                     if (
-                        utils.MAX_CELL_VOLTAGE * self.cell_count <= voltageSum
-                        and self.allow_max_voltage
-                    ):
+                        utils.MAX_CELL_VOLTAGE * self.cell_count
+                    ) - utils.VOLTAGE_DROP <= voltageSum and self.allow_max_voltage:
                         # example 2
                         self.max_voltage_start_time = time()
 
@@ -928,3 +930,12 @@ def log_settings(self) -> None:
     def reset_soc_callback(self, path, value):
         # callback for handling reset soc request
         return
+
+    def force_charging_off_callback(self, path, value):
+        return
+
+    def force_discharging_off_callback(self, path, value):
+        return
+
+    def turn_balancing_off_callback(self, path, value):
+        return
diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py
index b358a886..54226dac 100644
--- a/etc/dbus-serialbattery/bms/daly.py
+++ b/etc/dbus-serialbattery/bms/daly.py
@@ -21,9 +21,11 @@ def __init__(self, port, baud, address):
         self.poll_interval = 1000
         self.type = self.BATTERYTYPE
         self.has_settings = 1
-        self.reset_soc = 0
+        self.reset_soc = 100
         self.soc_to_set = None
         self.runtime = 0  # TROUBLESHOOTING for no reply errors
+        self.trigger_force_disable_discharge = None
+        self.trigger_force_disable_charge = None
 
     # 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"
@@ -40,6 +42,8 @@ def __init__(self, port, baud, address):
     command_temp = b"\x96"
     command_cell_balance = b"\x97"  # no reply
     command_alarm = b"\x98"  # no reply
+    command_disable_discharge_mos = b"\xD9"
+    command_disable_charge_mos = b"\xDA"
 
     BATTERYTYPE = "Daly"
     LENGTH_CHECK = 1
@@ -57,9 +61,6 @@ def test_connection(self):
                 result = self.read_status_data(ser)
                 self.read_soc_data(ser)
                 self.read_battery_code(ser)
-                self.reset_soc = (
-                    self.soc
-                )  # set to meaningful value as preset for the GUI
 
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
@@ -93,7 +94,7 @@ def refresh_data(self):
                         + "s"
                     )
 
-                result = result and self.read_fed_data(ser)
+                result = self.read_fed_data(ser) and result
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
                         "  |- refresh_data: read_fed_data - result: "
@@ -103,7 +104,7 @@ def refresh_data(self):
                         + "s"
                     )
 
-                result = result and self.read_cell_voltage_range_data(ser)
+                result = self.read_cell_voltage_range_data(ser) and result
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
                         "  |- refresh_data: read_cell_voltage_range_data - result: "
@@ -123,7 +124,7 @@ def refresh_data(self):
                         + "s"
                     )
 
-                result = result and self.read_alarm_data(ser)
+                result = self.read_alarm_data(ser) and result
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
                         "  |- refresh_data: read_alarm_data - result: "
@@ -133,7 +134,7 @@ def refresh_data(self):
                         + "s"
                     )
 
-                result = result and self.read_temperature_range_data(ser)
+                result = self.read_temperature_range_data(ser) and result
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
                         "  |- refresh_data: read_temperature_range_data - result: "
@@ -143,26 +144,28 @@ def refresh_data(self):
                         + "s"
                     )
 
-                result = result and self.read_cells_volts(ser)
+                result = self.read_balance_state(ser) and result
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
-                        "  |- refresh_data: read_cells_volts - result: "
+                        "  |- refresh_data: read_balance_state - result: "
                         + str(result)
                         + " - runtime: "
                         + str(self.runtime)
                         + "s"
                     )
 
-                result = result and self.read_balance_state(ser)
+                result = self.read_cells_volts(ser) and result
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
-                        "  |- refresh_data: read_balance_state - result: "
+                        "  |- refresh_data: read_cells_volts - result: "
                         + str(result)
                         + " - runtime: "
                         + str(self.runtime)
                         + "s"
                     )
 
+                self.write_charge_discharge_mos(ser)
+
         except OSError:
             logger.warning("Couldn't open serial port")
 
@@ -245,57 +248,57 @@ def read_alarm_data(self, ser):
 
         if al_volt & 48:
             # High voltage levels - Alarm
-            self.voltage_high = 2
+            self.protection.voltage_high = 2
         elif al_volt & 15:
             # High voltage Warning levels - Pre-alarm
-            self.voltage_high = 1
+            self.protection.voltage_high = 1
         else:
-            self.voltage_high = 0
+            self.protection.voltage_high = 0
 
         if al_volt & 128:
             # Low voltage level - Alarm
-            self.voltage_low = 2
+            self.protection.voltage_low = 2
         elif al_volt & 64:
             # Low voltage Warning level - Pre-alarm
-            self.voltage_low = 1
+            self.protection.voltage_low = 1
         else:
-            self.voltage_low = 0
+            self.protection.voltage_low = 0
 
         if al_temp & 2:
             # High charge temp - Alarm
-            self.temp_high_charge = 2
+            self.protection.temp_high_charge = 2
         elif al_temp & 1:
             # High charge temp - Pre-alarm
-            self.temp_high_charge = 1
+            self.protection.temp_high_charge = 1
         else:
-            self.temp_high_charge = 0
+            self.protection.temp_high_charge = 0
 
         if al_temp & 8:
             # Low charge temp - Alarm
-            self.temp_low_charge = 2
+            self.protection.temp_low_charge = 2
         elif al_temp & 4:
             # Low charge temp - Pre-alarm
-            self.temp_low_charge = 1
+            self.protection.temp_low_charge = 1
         else:
-            self.temp_low_charge = 0
+            self.protection.temp_low_charge = 0
 
         if al_temp & 32:
             # High discharge temp - Alarm
-            self.temp_high_discharge = 2
+            self.protection.temp_high_discharge = 2
         elif al_temp & 16:
             # High discharge temp - Pre-alarm
-            self.temp_high_discharge = 1
+            self.protection.temp_high_discharge = 1
         else:
-            self.temp_high_discharge = 0
+            self.protection.temp_high_discharge = 0
 
         if al_temp & 128:
             # Low discharge temp - Alarm
-            self.temp_low_discharge = 2
+            self.protection.temp_low_discharge = 2
         elif al_temp & 64:
             # Low discharge temp - Pre-alarm
-            self.temp_low_discharge = 1
+            self.protection.temp_low_discharge = 1
         else:
-            self.temp_low_discharge = 0
+            self.protection.temp_low_discharge = 0
 
         # if al_crnt_soc & 2:
         #    # High charge current - Alarm
@@ -317,21 +320,21 @@ def read_alarm_data(self, ser):
 
         if al_crnt_soc & 2 or al_crnt_soc & 8:
             # High charge/discharge current - Alarm
-            self.current_over = 2
+            self.protection.current_over = 2
         elif al_crnt_soc & 1 or al_crnt_soc & 4:
             # High charge/discharge current - Pre-alarm
-            self.current_over = 1
+            self.protection.current_over = 1
         else:
-            self.current_over = 0
+            self.protection.current_over = 0
 
         if al_crnt_soc & 128:
             # Low SoC - Alarm
-            self.soc_low = 2
+            self.protection.soc_low = 2
         elif al_crnt_soc & 64:
             # Low SoC Warning level - Pre-alarm
-            self.soc_low = 1
+            self.protection.soc_low = 1
         else:
-            self.soc_low = 0
+            self.protection.soc_low = 0
 
         return True
 
@@ -558,8 +561,16 @@ def read_serial_data_daly(self, ser, command):
             ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK
         )
         if data is False:
-            logger.info("No reply to cmd " + bytes(command).hex())
-            return False
+            # sleep 100 ms and retry.
+            sleep(0.100)
+            data = self.read_serialport_data(
+                ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK
+            )
+            if data is False:
+                logger.info("No reply to cmd " + bytes(command).hex())
+                return False
+            else:
+                logger.info("       |- Error cleared, received data after one retry.")
 
         if len(data) <= 12:
             logger.debug("Too short reply to cmd " + bytes(command).hex())
@@ -595,7 +606,7 @@ def read_serial_data_daly(self, ser, command):
             )
             return False
 
-    # Read data from previously openned serial port
+    # Read data from previously opened serial port
     def read_serialport_data(
         self,
         ser,
@@ -610,6 +621,7 @@ def read_serialport_data(
             # if you see a lot of errors, try to increase in steps of 0.005
             sleep(0.020)
 
+            time_run = 0
             time_start = time()
             ser.flushOutput()
             ser.flushInput()
@@ -715,21 +727,78 @@ def write_soc_and_datetime(self, ser):
         logger.info(f"write soc {self.soc_to_set}%")
         self.soc_to_set = None  # Reset value, so we will set it only once
 
-        time_start = time()
-        ser.flushOutput()
-        ser.flushInput()
-        ser.write(cmd)
-
-        toread = ser.inWaiting()
-        while toread < 13:
-            sleep(0.005)
-            toread = ser.inWaiting()
-            time_run = time() - time_start
-            if time_run > 0.500:
-                logger.warning("write soc: no reply, probably failed")
-                return False
+        reply = self.read_serialport_data(ser, cmd, self.LENGTH_POS, self.LENGTH_CHECK)
 
-        reply = ser.read(toread)
         if reply[4] != 1:
             logger.error("write soc failed")
         return True
+
+    def force_charging_off_callback(self, path, value):
+        if value is None:
+            return False
+
+        if value == 0:
+            self.trigger_force_disable_charge = False
+            return True
+
+        if value == 1:
+            self.trigger_force_disable_charge = True
+            return True
+
+        return False
+
+    def force_discharging_off_callback(self, path, value):
+        if value is None:
+            return False
+
+        if value == 0:
+            self.trigger_force_disable_discharge = False
+            return True
+
+        if value == 1:
+            self.trigger_force_disable_discharge = True
+            return True
+
+        return False
+
+    def write_charge_discharge_mos(self, ser):
+        if (
+            self.trigger_force_disable_charge is None
+            and self.trigger_force_disable_discharge is None
+        ):
+            return False
+
+        cmd = bytearray(self.command_base)
+
+        if self.trigger_force_disable_charge is not None:
+            cmd[2] = self.command_disable_charge_mos[0]
+            cmd[4] = 0 if self.trigger_force_disable_charge else 1
+            cmd[12] = sum(cmd[:12]) & 0xFF
+            logger.info(
+                f"write force disable charging: {'true' if self.trigger_force_disable_charge else 'false'}"
+            )
+            self.trigger_force_disable_charge = None
+
+            reply = self.read_serialport_data(
+                ser, cmd, self.LENGTH_POS, self.LENGTH_CHECK
+            )
+            if reply is False or reply[4] != cmd[4]:
+                logger.error("write force disable charge/discharge failed")
+                return False
+
+        if self.trigger_force_disable_discharge is not None:
+            cmd[2] = self.command_disable_discharge_mos[0]
+            cmd[4] = 0 if self.trigger_force_disable_discharge else 1
+            cmd[12] = sum(cmd[:12]) & 0xFF
+            logger.info(
+                f"write force disable discharging: {'true' if self.trigger_force_disable_discharge else 'false'}"
+            )
+            self.trigger_force_disable_discharge = None
+
+            reply = self.read_serialport_data(
+                ser, cmd, self.LENGTH_POS, self.LENGTH_CHECK
+            )
+            if reply is False or reply[4] != cmd[4]:
+                logger.error("write force disable charge/discharge failed")
+                return False
+        return True
diff --git a/etc/dbus-serialbattery/bms/lifepower.py b/etc/dbus-serialbattery/bms/lifepower.py
index 84c93a37..5780d58c 100644
--- a/etc/dbus-serialbattery/bms/lifepower.py
+++ b/etc/dbus-serialbattery/bms/lifepower.py
@@ -38,7 +38,7 @@ 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 = utils.MAX_BATTERY_CURRENT
+        self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_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:
diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py
index a74697cc..c1bf2ae3 100644
--- a/etc/dbus-serialbattery/bms/lltjbd.py
+++ b/etc/dbus-serialbattery/bms/lltjbd.py
@@ -116,6 +116,43 @@ def to_protection_bits(self, byte_data):
         self.protection.set_short = is_bit_set(tmp[2])
 
     def to_cell_bits(self, byte_data, byte_data_high):
+        # init the cell array once
+        if len(self.cells) == 0:
+            for _ in range(self.cell_count):
+                print("#" + str(_))
+                self.cells.append(Cell(False))
+
+        # get up to the first 16 cells
+        tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), utils.zero_char)
+        # 4 cells
+        # tmp = 0101
+        # 16 cells
+        # tmp = 0101010101010101
+
+        tmp_reversed = list(reversed(tmp))
+        # print(tmp_reversed) --> ['1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0']
+        # [cell1, cell2, cell3, ...]
+
+        if self.cell_count > 16:
+            tmp2 = bin(byte_data_high)[2:].rjust(self.cell_count - 16, utils.zero_char)
+            # tmp = 1100110011001100
+            tmp_reversed = tmp_reversed + list(reversed(tmp2))
+            # print(tmp_reversed) --> [
+            # '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0',
+            # '0', '0', '1', '1', '0', '0', '1', '1', '0', '0', '1', '1', '0', '0', '1', '1'
+            # ]
+            # [
+            # cell1, cell2, ..., cell16,
+            # cell17, cell18, ..., cell32
+            # ]
+
+        for c in range(self.cell_count):
+            if is_bit_set(tmp_reversed[c]):
+                self.cells[c].balance = True
+            else:
+                self.cells[c].balance = False
+
+        """
         # clear the list
         for c in self.cells:
             self.cells.remove(c)
@@ -128,6 +165,7 @@ def to_cell_bits(self, byte_data, byte_data_high):
             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, utils.zero_char)
diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini
index 8018e3bd..4b11e9b5 100644
--- a/etc/dbus-serialbattery/config.default.ini
+++ b/etc/dbus-serialbattery/config.default.ini
@@ -50,15 +50,18 @@ LINEAR_RECALCULATION_ON_PERC_CHANGE = 5
 ;                    SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT it switches back to max voltage.
 ;         Linear mode: After max voltage is reachend and cell voltage difference is smaller or equal to
 ;                      CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL it switches to float voltage after 300 (fixed)
-;                      additional seconds. After cell voltage difference is greater or equal to
-;                      CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT it switches back to max voltage.
+;                      additional seconds.
+;                      After cell voltage difference is greater or equal to CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
+;                      OR
+;                      SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT
+;                      it switches back to max voltage.
 ; Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to
 ;          float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is
 ;          once below 90%
 ;          OR
 ;          The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float
 ;          voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of
-;          55.2V again if max cell difference is above 0.050V
+;          55.2V again if max cell difference is above 0.080V or SoC below 90%.
 ; Charge voltage control management enable (True/False).
 CVCM_ENABLE = True
 
@@ -218,7 +221,7 @@ SOC_LOW_WARNING = 20
 SOC_LOW_ALARM   = 10
 
 ; -- Daly settings
-; Battery capacity (amps) if the BMS does not support reading it
+; Battery capacity (amps), if the BMS does not support reading it
 BATTERY_CAPACITY = 50
 ; Invert Battery Current. Default non-inverted. Set to -1 to invert
 INVERT_CURRENT_MEASUREMENT = 1
@@ -228,3 +231,18 @@ GREENMETER_ADDRESS  = 1
 LIPRO_START_ADDRESS = 2
 LIPRO_END_ADDRESS   = 4
 LIPRO_CELL_COUNT = 15
+
+
+; --------- Battery monitor specific settings ---------
+; If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported
+; from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage,
+; since max voltage is never reached.
+; Example:
+;     cell count: 16
+;     MAX_CELL_VOLTAGE = 3.45
+;     max voltage calculated = 16 * 3.45 = 55.20
+;     CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS
+;     now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures
+;     55.05 V the max voltage is never reached for the driver and max voltage is kept forever.
+;     Set VOLTAGE_DROP to 0.15
+VOLTAGE_DROP = 0.00
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index 68265a07..b50c938c 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -97,9 +97,7 @@ def get_port() -> str:
             logger.info("No Port needed")
             return "/dev/tty/USB9"
 
-    logger.info(
-        "dbus-serialbattery v" + str(utils.DRIVER_VERSION) + utils.DRIVER_SUBVERSION
-    )
+    logger.info("dbus-serialbattery v" + str(utils.DRIVER_VERSION))
 
     port = get_port()
     battery = None
diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py
index 555cb1ba..ccf7181e 100644
--- a/etc/dbus-serialbattery/dbushelper.py
+++ b/etc/dbus-serialbattery/dbushelper.py
@@ -121,9 +121,7 @@ def setup_vedbus(self):
         self._dbusservice.add_path(
             "/ProductName", "SerialBattery(" + self.battery.type + ")"
         )
-        self._dbusservice.add_path(
-            "/FirmwareVersion", str(utils.DRIVER_VERSION) + utils.DRIVER_SUBVERSION
-        )
+        self._dbusservice.add_path("/FirmwareVersion", str(utils.DRIVER_VERSION))
         self._dbusservice.add_path("/HardwareVersion", self.battery.hardware_version)
         self._dbusservice.add_path("/Connected", 1)
         self._dbusservice.add_path(
@@ -253,6 +251,24 @@ def setup_vedbus(self):
         self._dbusservice.add_path("/Io/AllowToCharge", 0, writeable=True)
         self._dbusservice.add_path("/Io/AllowToDischarge", 0, writeable=True)
         self._dbusservice.add_path("/Io/AllowToBalance", 0, writeable=True)
+        self._dbusservice.add_path(
+            "/Io/ForceChargingOff",
+            0,
+            writeable=True,
+            onchangecallback=self.battery.force_charging_off_callback,
+        )
+        self._dbusservice.add_path(
+            "/Io/ForceDischargingOff",
+            0,
+            writeable=True,
+            onchangecallback=self.battery.force_discharging_off_callback,
+        )
+        self._dbusservice.add_path(
+            "/Io/TurnBalancingOff",
+            0,
+            writeable=True,
+            onchangecallback=self.battery.turn_balancing_off_callback,
+        )
         # self._dbusservice.add_path('/SystemSwitch', 1, writeable=True)
 
         # Create the alarms
diff --git a/etc/dbus-serialbattery/disable.sh b/etc/dbus-serialbattery/disable.sh
index 34304cd5..f1902881 100755
--- a/etc/dbus-serialbattery/disable.sh
+++ b/etc/dbus-serialbattery/disable.sh
@@ -4,16 +4,27 @@
 #set -x
 
 # handle read only mounts
-sh /opt/victronenergy/swupdate-scripts/remount-rw.sh
+bash /opt/victronenergy/swupdate-scripts/remount-rw.sh
 
-# remove files, don't use variables here, since on an error the whole /opt/victronenergy gets deleted
+# remove driver from serial starter
 rm -f /data/conf/serial-starter.d/dbus-serialbattery.conf
+# kill serial starter, to reload changes
+pkill -f "/opt/victronenergy/serial-starter/serial-starter.sh"
+
+# remove services
 rm -rf /service/dbus-serialbattery.*
 rm -rf /service/dbus-blebattery.*
 
+# kill driver, if running
+pkill -f "python .*/dbus-serialbattery.py"
+pkill -f "blebattery"
+
 # remove install script from rc.local
 sed -i "/bash \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local
 
+# remove cronjob
+sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root
+
 
 ### needed for upgrading from older versions | start ###
 # remove old drivers before changing from dbus-blebattery-$1 to dbus-blebattery.$1
@@ -25,10 +36,5 @@ sed -i "/sh \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.loca
 sed -i "/sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local
 ### needed for upgrading from older versions | end ###
 
-
-# kill serial starter, to reload changes
-pkill -f "/opt/victronenergy/serial-starter/serial-starter.sh"
-
-# kill driver, if running
-pkill -f "serialbattery"
-pkill -f "blebattery"
+echo "The dbus-serialbattery driver was disabled".
+echo
diff --git a/etc/dbus-serialbattery/install.sh b/etc/dbus-serialbattery/install.sh
old mode 100755
new mode 100644
index 17a12ed3..c54c92c6
--- a/etc/dbus-serialbattery/install.sh
+++ b/etc/dbus-serialbattery/install.sh
@@ -49,8 +49,8 @@ fi
 ## specific version
 if [ "$version" = "specific version" ]; then
     # read the url
-    read -p "Enter the url of the \"venus-data.tar.gz\" you want to install: " tar_url
-    wget -O /tmp/venus-data.tar.gz $tar_url
+    read -r -p "Enter the url of the \"venus-data.tar.gz\" you want to install: " tar_url
+    wget -O /tmp/venus-data.tar.gz "$tar_url"
     if [ $? -ne 0 ]; then
         echo "Error during downloading the TAR file. Please check, if the URL is correct."
         exit
diff --git a/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml b/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml
index 112564df..ea723a89 100644
--- a/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml
+++ b/etc/dbus-serialbattery/qml/PageBatteryCellVoltages.qml
@@ -4,6 +4,8 @@ import com.victron.velib 1.0
 MbPage {
     id: root
     property string bindPrefix
+	property MbStyle style: MbStyle{}
+
     property VBusItem _b1: VBusItem { bind: service.path("/Balances/Cell1") }
     property VBusItem _b2: VBusItem { bind: service.path("/Balances/Cell2") }
     property VBusItem _b3: VBusItem { bind: service.path("/Balances/Cell3") }
@@ -52,30 +54,30 @@ MbPage {
     property VBusItem volt22: VBusItem { bind: service.path("/Voltages/Cell22") }
     property VBusItem volt23: VBusItem { bind: service.path("/Voltages/Cell23") }
     property VBusItem volt24: VBusItem { bind: service.path("/Voltages/Cell24") }
-    property string c1: _b1.valid && _b1.text == "1" ? "#ff0000" : "#ddd"
-    property string c2: _b2.valid && _b2.text == "1" ? "#ff0000" : "#ddd"
-    property string c3: _b3.valid && _b3.text == "1" ? "#ff0000" : "#ddd"
-    property string c4: _b4.valid && _b4.text == "1" ? "#ff0000" : "#ddd"
-    property string c5: _b5.valid && _b5.text == "1" ? "#ff0000" : "#ddd"
-    property string c6: _b6.valid && _b6.text == "1" ? "#ff0000" : "#ddd"
-    property string c7: _b7.valid && _b7.text == "1" ? "#ff0000" : "#ddd"
-    property string c8: _b8.valid && _b8.text == "1" ? "#ff0000" : "#ddd"
-    property string c9: _b9.valid && _b9.text == "1" ? "#ff0000" : "#ddd"
-    property string c10: _b10.valid && _b10.text == "1" ? "#ff0000" : "#ddd"
-    property string c11: _b11.valid && _b11.text == "1" ? "#ff0000" : "#ddd"
-    property string c12: _b12.valid && _b12.text == "1" ? "#ff0000" : "#ddd"
-    property string c13: _b13.valid && _b13.text == "1" ? "#ff0000" : "#ddd"
-    property string c14: _b14.valid && _b14.text == "1" ? "#ff0000" : "#ddd"
-    property string c15: _b15.valid && _b15.text == "1" ? "#ff0000" : "#ddd"
-    property string c16: _b16.valid && _b16.text == "1" ? "#ff0000" : "#ddd"
-    property string c17: _b17.valid && _b17.text == "1" ? "#ff0000" : "#ddd"
-    property string c18: _b18.valid && _b18.text == "1" ? "#ff0000" : "#ddd"
-    property string c19: _b19.valid && _b19.text == "1" ? "#ff0000" : "#ddd"
-    property string c20: _b20.valid && _b20.text == "1" ? "#ff0000" : "#ddd"
-    property string c21: _b21.valid && _b21.text == "1" ? "#ff0000" : "#ddd"
-    property string c22: _b22.valid && _b22.text == "1" ? "#ff0000" : "#ddd"
-    property string c23: _b23.valid && _b23.text == "1" ? "#ff0000" : "#ddd"
-    property string c24: _b24.valid && _b24.text == "1" ? "#ff0000" : "#ddd"
+    property string c1: _b1.valid && _b1.text == "1" ? "#ff0000" : style.borderColor
+    property string c2: _b2.valid && _b2.text == "1" ? "#ff0000" : style.borderColor
+    property string c3: _b3.valid && _b3.text == "1" ? "#ff0000" : style.borderColor
+    property string c4: _b4.valid && _b4.text == "1" ? "#ff0000" : style.borderColor
+    property string c5: _b5.valid && _b5.text == "1" ? "#ff0000" : style.borderColor
+    property string c6: _b6.valid && _b6.text == "1" ? "#ff0000" : style.borderColor
+    property string c7: _b7.valid && _b7.text == "1" ? "#ff0000" : style.borderColor
+    property string c8: _b8.valid && _b8.text == "1" ? "#ff0000" : style.borderColor
+    property string c9: _b9.valid && _b9.text == "1" ? "#ff0000" : style.borderColor
+    property string c10: _b10.valid && _b10.text == "1" ? "#ff0000" : style.borderColor
+    property string c11: _b11.valid && _b11.text == "1" ? "#ff0000" : style.borderColor
+    property string c12: _b12.valid && _b12.text == "1" ? "#ff0000" : style.borderColor
+    property string c13: _b13.valid && _b13.text == "1" ? "#ff0000" : style.borderColor
+    property string c14: _b14.valid && _b14.text == "1" ? "#ff0000" : style.borderColor
+    property string c15: _b15.valid && _b15.text == "1" ? "#ff0000" : style.borderColor
+    property string c16: _b16.valid && _b16.text == "1" ? "#ff0000" : style.borderColor
+    property string c17: _b17.valid && _b17.text == "1" ? "#ff0000" : style.borderColor
+    property string c18: _b18.valid && _b18.text == "1" ? "#ff0000" : style.borderColor
+    property string c19: _b19.valid && _b19.text == "1" ? "#ff0000" : style.borderColor
+    property string c20: _b20.valid && _b20.text == "1" ? "#ff0000" : style.borderColor
+    property string c21: _b21.valid && _b21.text == "1" ? "#ff0000" : style.borderColor
+    property string c22: _b22.valid && _b22.text == "1" ? "#ff0000" : style.borderColor
+    property string c23: _b23.valid && _b23.text == "1" ? "#ff0000" : style.borderColor
+    property string c24: _b24.valid && _b24.text == "1" ? "#ff0000" : style.borderColor
     title: service.description + " | Cell Voltages"
 
     model: VisibleItemModel {
diff --git a/etc/dbus-serialbattery/qml/PageLynxIonIo.qml b/etc/dbus-serialbattery/qml/PageLynxIonIo.qml
index e6ad7106..21bfedf4 100644
--- a/etc/dbus-serialbattery/qml/PageLynxIonIo.qml
+++ b/etc/dbus-serialbattery/qml/PageLynxIonIo.qml
@@ -49,6 +49,24 @@ MbPage {
 			]
 		}
 
+		MbSwitch {
+			name: qsTr("Force charging off")
+			bind: Utils.path(bindPrefix, "/Io/ForceChargingOff")
+			show: item.valid
+		}
+
+		MbSwitch {
+			name: qsTr("Force discharging off")
+			bind: Utils.path(bindPrefix, "/Io/ForceDischargingOff")
+			show: item.valid
+		}
+
+		MbSwitch {
+			name: qsTr("Turn balancing off")
+			bind: Utils.path(bindPrefix, "/Io/TurnBalancingOff")
+			show: item.valid
+		}
+
 		MbItemOptions {
 			description: qsTr("External relay")
 			bind: Utils.path(bindPrefix, "/Io/ExternalRelay")
diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh
index f822e026..b082c620 100755
--- a/etc/dbus-serialbattery/reinstall-local.sh
+++ b/etc/dbus-serialbattery/reinstall-local.sh
@@ -141,9 +141,10 @@ fi
 # remove old drivers before changing from dbus-blebattery-$1 to dbus-blebattery.$1
 rm -rf /service/dbus-blebattery-*
 # remove old install script from rc.local
-sed -i "/sh \/data\/etc\/$DRIVERNAME\/reinstalllocal.sh/d" /data/rc.local
+sed -i "/^sh \/data\/etc\/dbus-serialbattery\/reinstalllocal.sh/d" /data/rc.local
+sed -i "/^sh \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local
 # remove old entry from rc.local
-sed -i "/sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local
+sed -i "/^sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local
 ### needed for upgrading from older versions | end ###
 
 
diff --git a/etc/dbus-serialbattery/restart-driver.sh b/etc/dbus-serialbattery/restart-driver.sh
index 853a8b97..88a15800 100755
--- a/etc/dbus-serialbattery/restart-driver.sh
+++ b/etc/dbus-serialbattery/restart-driver.sh
@@ -10,7 +10,7 @@ cp -f /data/etc/dbus-serialbattery/config.ini /opt/victronenergy/dbus-serialbatt
 # svc -d -u /service/dbus-serialbattery
 
 # kill driver, if running. It gets restarted by the service daemon
-pkill -f "python .*/$DRIVERNAME.py"
+pkill -f "python .*/dbus-serialbattery.py"
 
 
 # get BMS list from config file
diff --git a/etc/dbus-serialbattery/uninstall.sh b/etc/dbus-serialbattery/uninstall.sh
index d11df02f..94100a9d 100755
--- a/etc/dbus-serialbattery/uninstall.sh
+++ b/etc/dbus-serialbattery/uninstall.sh
@@ -3,41 +3,15 @@
 # remove comment for easier troubleshooting
 #set -x
 
-# handle read only mounts
-bash /opt/victronenergy/swupdate-scripts/remount-rw.sh
+# disable driver
+bash /data/etc/dbus-serialbattery/disable.sh
 
-# remove files, don't use variables here, since on an error the whole /opt/victronenergy gets deleted
-rm -f /data/conf/serial-starter.d/dbus-serialbattery.conf
+
+# remove files in Victron directory. Don't use variables here,
+# since on an error the whole /opt/victronenergy gets deleted
 rm -rf /opt/victronenergy/service/dbus-serialbattery
 rm -rf /opt/victronenergy/service-templates/dbus-serialbattery
 rm -rf /opt/victronenergy/dbus-serialbattery
-rm -rf /service/dbus-serialbattery.*
-rm -rf /service/dbus-blebattery.*
-
-# remove install-script from rc.local
-sed -i "/bash \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local
-
-# remove cronjob
-sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root
-
-
-### needed for upgrading from older versions | start ###
-# remove old drivers before changing from dbus-blebattery-$1 to dbus-blebattery.$1
-rm -rf /service/dbus-blebattery-*
-# remove old install script from rc.local
-sed -i "/sh \/data\/etc\/dbus-serialbattery\/reinstalllocal.sh/d" /data/rc.local
-sed -i "/sh \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local
-# remove old entry from rc.local
-sed -i "/sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local
-### needed for upgrading from older versions | end ###
-
-
-# kill serial starter, to reload changes
-pkill -f "/opt/victronenergy/serial-starter/serial-starter.sh"
-
-# kill driver, if running
-pkill -f "serialbattery"
-pkill -f "blebattery"
 
 
 # restore GUI changes
@@ -45,10 +19,10 @@ pkill -f "blebattery"
 
 
 # uninstall modules
-read -r -p "Do you also want to uninstall bleak, python3-pip and python3-modules? If you don't know select y. [Y/n] " response
+read -r -p "Do you want to uninstall bleak, python3-pip and python3-modules? If you don't know just press enter. [y/N] " response
 echo
 response=${response,,} # tolower
-if [[ $response =~ ^(y| ) ]] || [[ -z $response ]]; then
+if [[ $response =~ ^(y) ]]; then
     echo "Uninstalling modules..."
     pip3 uninstall bleak
     opkg remove python3-pip python3-modules
@@ -57,5 +31,15 @@ if [[ $response =~ ^(y| ) ]] || [[ -z $response ]]; then
 fi
 
 
-echo "The driver was uninstalled. To delete also the install files run \"rm -rf /data/etc/dbus-serialbattery\" now."
+read -r -p "Do you want to delete the install and configuration files in \"/data/etc/dbus-serialbattery\"? If you don't know just press enter. [y/N] " response
+echo
+response=${response,,} # tolower
+if [[ $response =~ ^(y) ]]; then
+    rm -rf /data/etc/dbus-serialbattery
+    echo "The folder \"/data/etc/dbus-serialbattery\" was removed."
+    echo
+fi
+
+
+echo "The dbus-serialbattery driver was uninstalled. Please reboot."
 echo
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index 74aa7d22..e36782da 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -35,8 +35,7 @@ def _get_list_from_config(
 # if not specified: baud = 9600
 
 # Constants - Need to dynamically get them in future
-DRIVER_VERSION = "1.0"
-DRIVER_SUBVERSION = ".0 (20230508)"
+DRIVER_VERSION = "1.0.20230518dev"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 
@@ -64,9 +63,10 @@ def _get_list_from_config(
 
 # --------- Charge mode ---------
 # Choose the mode for voltage / current limitations (True / False)
-# False is a step mode. This is the default with limitations on hard boundary steps
-# True is a linear mode. For CCL and DCL the values between the steps are calculated for smoother values (by WaldemarFech)
-#                        For CVL max battery voltage is calculated dynamically in order that the max cell voltage is not exceeded
+# False is a step mode: This is the default with limitations on hard boundary steps
+# True is a linear mode:
+#     For CCL and DCL the values between the steps are calculated for smoother values (by WaldemarFech)
+#     For CVL max battery voltage is calculated dynamically in order that the max cell voltage is not exceeded
 LINEAR_LIMITATION_ENABLE = "True" == config["DEFAULT"]["LINEAR_LIMITATION_ENABLE"]
 
 # Specify in seconds how often the penalty should be recalculated
@@ -79,17 +79,26 @@ def _get_list_from_config(
 
 
 # --------- Charge Voltage limitation (affecting CVL) ---------
-# Description: Limit max charging voltage (MAX_CELL_VOLTAGE * cell count), switch from max voltage to float voltage (FLOAT_CELL_VOLTAGE * cell count) and back
-#     Step mode: After max voltage is reached for MAX_VOLTAGE_TIME_SEC it switches to float voltage. After SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT it
-#                switches back to max voltage.
-#     Linear mode: After max voltage is reachend and cell voltage difference is smaller or equal to CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL it switches to
-#                  float voltage after 300 (fixed) additional seconds. After cell voltage difference is greater or equal to CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
-#                  it switches back to max voltage.
-# Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to float voltage of 53.6V to don't stress the batteries.
-#          Allow max voltage of 55.2V again, if SoC is once below 90%
+# Description: Limit max charging voltage (MAX_CELL_VOLTAGE * cell count), switch from max voltage to float
+#              voltage (FLOAT_CELL_VOLTAGE * cell count) and back
+#     False: Max charging voltage is always kept
+#     True: Max charging voltage is reduced based on charge mode
+#         Step mode: After max voltage is reached for MAX_VOLTAGE_TIME_SEC it switches to float voltage. After
+#                    SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT it switches back to max voltage.
+#         Linear mode: After max voltage is reachend and cell voltage difference is smaller or equal to
+#                      CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL it switches to float voltage after 300 (fixed)
+#                      additional seconds.
+#                      After cell voltage difference is greater or equal to CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
+#                      OR
+#                      SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT
+#                      it switches back to max voltage.
+# Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to
+#          float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is
+#          once below 90%
 #          OR
-#          The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float voltage of 53.6V after 300 additional seconds
-#          to don't stress the batteries. Allow max voltage of 55.2V again if max cell difference is above 0.050V
+#          The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float
+#          voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of
+#          55.2V again if max cell difference is above 0.080V or SoC below 90%.
 # Charge voltage control management enable (True/False).
 CVCM_ENABLE = "True" == config["DEFAULT"]["CVCM_ENABLE"]
 
@@ -99,14 +108,16 @@ def _get_list_from_config(
     config["DEFAULT"]["CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL"]
 )
 # Specify cell voltage diff where CVL limit is reset to max voltage, if value get above
+# the cells are considered as imbalanced, if the cell diff exceeds 5% of the nominal cell voltage
+# e.g. 3.2 V * 5 / 100 = 0.160 V
 CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = float(
     config["DEFAULT"]["CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT"]
 )
 
 # -- CVL Reset based on SoC option
-# Reset max voltage after
+# Specify how long the max voltage should be kept, if reached then switch to float voltage
 MAX_VOLTAGE_TIME_SEC = float(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"])
-# Specify SoC where CVL limit is reset to max voltage
+# Specify SoC where CVL limit is reset to max voltage, if value gets below
 SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = float(
     config["DEFAULT"]["SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT"]
 )
@@ -305,6 +316,21 @@ def _get_list_from_config(
 LIPRO_CELL_COUNT = int(config["DEFAULT"]["LIPRO_CELL_COUNT"])
 
 
+# --------- Battery monitor specific settings ---------
+# If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported
+# from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage,
+# since max voltage is never reached.
+# Example:
+#     cell count: 16
+#     MAX_CELL_VOLTAGE = 3.45
+#     max voltage calculated = 16 * 3.45 = 55.20
+#     CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS
+#     now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures
+#     55.05 V the max voltage is never reached for the driver and max voltage is kept forever.
+#     Set VOLTAGE_DROP to 0.15
+VOLTAGE_DROP = float(config["DEFAULT"]["VOLTAGE_DROP"])
+
+
 # --------- Functions ---------
 def constrain(val, min_val, max_val):
     if min_val > max_val:
diff --git a/rc/post-hook.sh b/rc/post-hook.sh
index 37cb7b2b..b4997054 100644
--- a/rc/post-hook.sh
+++ b/rc/post-hook.sh
@@ -23,3 +23,10 @@ fi
 
 # run reinstall local
 bash /data/etc/dbus-serialbattery/reinstall-local.sh
+
+# rename the venus-data.tar.gz else the data is overwritten, if the USB is not removed
+for dir in /media/*; do
+    if [ -f "/media/$dir/venus-data.tar.gz" ]; then
+        mv "/media/$dir/venus-data.tar.gz" "/media/$dir/venus-data_installed.tar.gz"
+    fi
+done

From 3c21953202e2aa91e645c9b1b15d7a8944be1117 Mon Sep 17 00:00:00 2001
From: Oleg Gurevich <oleg@gurevich.de>
Date: Sat, 20 May 2023 20:23:51 +0200
Subject: [PATCH 02/21] Limit control voltage to max cell voltage

---
 etc/dbus-serialbattery/battery.py | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index 767be09d..2b8c8d9d 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -241,15 +241,19 @@ def manage_charge_voltage_linear(self) -> None:
                 ):
                     self.linear_cvl_last_set = int(time())
 
-                    # Keep penalty above min battery voltage
+                    # Keep penalty above min battery voltage and below max battery voltage
                     self.control_voltage = round(
-                        max(
-                            voltageSum - penaltySum,
-                            utils.MIN_CELL_VOLTAGE * self.cell_count,
+                        min(
+                            max(
+                                voltageSum - penaltySum,
+                                utils.MIN_CELL_VOLTAGE * self.cell_count,
+                            ),
+                            utils.MAX_CELL_VOLTAGE * self.cell_count
                         ),
                         3,
                     )
 
+
                 self.charge_mode = (
                     "Bulk dynamic"
                     # + " (vS: "

From 3811281ef98a379d16b20eee93e609c7088522b8 Mon Sep 17 00:00:00 2001
From: Bernd <6627385+transistorgit@users.noreply.github.com>
Date: Sun, 21 May 2023 21:23:56 +0200
Subject: [PATCH 03/21] Rework serial parser (#12)

much cleaner code, as it is optimised for the daly protocol
---
 etc/dbus-serialbattery/bms/daly.py           | 418 +++++++------------
 etc/dbus-serialbattery/qml/PageLynxIonIo.qml |   1 +
 2 files changed, 161 insertions(+), 258 deletions(-)

diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py
index 54226dac..8555367b 100644
--- a/etc/dbus-serialbattery/bms/daly.py
+++ b/etc/dbus-serialbattery/bms/daly.py
@@ -21,7 +21,7 @@ def __init__(self, port, baud, address):
         self.poll_interval = 1000
         self.type = self.BATTERYTYPE
         self.has_settings = 1
-        self.reset_soc = 100
+        self.reset_soc = 0
         self.soc_to_set = None
         self.runtime = 0  # TROUBLESHOOTING for no reply errors
         self.trigger_force_disable_discharge = None
@@ -85,12 +85,13 @@ def refresh_data(self):
         try:
             with open_serial_port(self.port, self.baud_rate) as ser:
                 result = self.read_soc_data(ser)
+                self.reset_soc = self.soc if self.soc else 0
                 if self.runtime > 0.200:  # TROUBLESHOOTING for no reply errors
                     logger.info(
                         "  |- refresh_data: read_soc_data - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -100,7 +101,7 @@ def refresh_data(self):
                         "  |- refresh_data: read_fed_data - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -110,7 +111,7 @@ def refresh_data(self):
                         "  |- refresh_data: read_cell_voltage_range_data - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -120,7 +121,7 @@ def refresh_data(self):
                         "  |- refresh_data: write_soc_and_datetime - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -130,7 +131,7 @@ def refresh_data(self):
                         "  |- refresh_data: read_alarm_data - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -140,7 +141,7 @@ def refresh_data(self):
                         "  |- refresh_data: read_temperature_range_data - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -150,7 +151,7 @@ def refresh_data(self):
                         "  |- refresh_data: read_balance_state - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -160,7 +161,7 @@ def refresh_data(self):
                         "  |- refresh_data: read_cells_volts - result: "
                         + str(result)
                         + " - runtime: "
-                        + str(self.runtime)
+                        + str(f"{self.runtime:.1f}")
                         + "s"
                     )
 
@@ -174,7 +175,7 @@ def refresh_data(self):
         return result
 
     def read_status_data(self, ser):
-        status_data = self.read_serial_data_daly(ser, self.command_status)
+        status_data = self.request_data(ser, self.command_status)
         # check if connection success
         if status_data is False:
             logger.warning("No data received in read_status_data()")
@@ -208,7 +209,7 @@ def read_soc_data(self, ser):
         triesValid = 2
         while triesValid > 0:
             triesValid -= 1
-            soc_data = self.read_serial_data_daly(ser, self.command_soc)
+            soc_data = self.request_data(ser, self.command_soc)
             # check if connection success
             if soc_data is False:
                 continue
@@ -229,7 +230,7 @@ def read_soc_data(self, ser):
         return False
 
     def read_alarm_data(self, ser):
-        alarm_data = self.read_serial_data_daly(ser, self.command_alarm)
+        alarm_data = self.request_data(ser, self.command_alarm)
         # check if connection success
         if alarm_data is False:
             logger.warning("No data received in read_alarm_data()")
@@ -339,76 +340,59 @@ def read_alarm_data(self, ser):
         return True
 
     def read_cells_volts(self, ser):
-        if self.cell_count is not None:
-            buffer = bytearray(self.command_base)
-            buffer[1] = self.command_address[0]  # Always serial 40 or 80
-            buffer[2] = self.command_cell_volts[0]
-            buffer[12] = sum(buffer[:12]) & 0xFF
-
-            # logger.info(f"{bytes(buffer).hex()}")
-
-            if (int(self.cell_count) % 3) == 0:
-                maxFrame = int(self.cell_count / 3)
-            else:
-                maxFrame = int(self.cell_count / 3) + 1
-            lenFixed = (
-                maxFrame * 13
-            )  # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + chksum
-
-            cells_volts_data = self.read_serialport_data(
-                ser, buffer, self.LENGTH_POS, 0, lenFixed
-            )
-            if cells_volts_data is False:
-                logger.warning("No data received in read_cells_volts()")
-                return False
+        if self.cell_count is None:
+            return True
+
+        # calculate how many sentences we will receive
+        # in each sentence, the bms will send 3 cell voltages
+        # so for a 4s, we will receive 2 sentences
+        if (int(self.cell_count) % 3) == 0:
+            sentences_expected = int(self.cell_count / 3)
+        else:
+            sentences_expected = int(self.cell_count / 3) + 1
+
+        cells_volts_data = self.request_data(
+            ser, self.command_cell_volts, sentences_to_receive=sentences_expected
+        )
+
+        if cells_volts_data is False:
+            logger.debug(
+                "No or invalid data has been received in read_cells_volts()"
+            )  # just debug level, as there are DALY BMS that send broken packages occasionally
+            return False
+
+        frameCell = [0, 0, 0]
+        lowMin = utils.MIN_CELL_VOLTAGE / 2
+        frame = 0
+
+        if len(self.cells) != self.cell_count:
+            # init the numbers of cells
+            self.cells = []
+            for idx in range(self.cell_count):
+                self.cells.append(Cell(True))
 
-            frameCell = [0, 0, 0]
-            lowMin = utils.MIN_CELL_VOLTAGE / 2
-            frame = 0
-            bufIdx = 0
-
-            if len(self.cells) != self.cell_count:
-                # init the numbers of cells
-                self.cells = []
-                for idx in range(self.cell_count):
-                    self.cells.append(Cell(True))
-
-            # logger.warning("data " + bytes(cells_volts_data).hex())
-
-            while bufIdx <= len(cells_volts_data) - (
-                4 + 8 + 1
-            ):  # we at least need 13 bytes to extract the identifiers + 8 bytes payload + checksum
-                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:
-                        logger.warning("bad cell voltages checksum")
-                    else:
-                        for idx in range(3):
-                            cellnum = (
-                                (frame - 1) * 3
-                            ) + idx  # daly is 1 based, driver 0 based
-                            if cellnum >= self.cell_count:
-                                break
-                            cellVoltage = frameCell[idx] / 1000
-                            self.cells[cellnum].voltage = (
-                                None if cellVoltage < lowMin else cellVoltage
-                            )
-                    bufIdx += 13  # BBBBBhhhBB -> 13 byte
-                else:
-                    bufIdx += 1  # step through buffer to find valid start
-                    logger.warning("bad cell voltages header")
+        # logger.warning("data " + bytes(cells_volts_data).hex())
+
+        # from each of the received sentences, read up to 3 voltages
+        for i in range(sentences_expected):
+            (
+                frame,
+                frameCell[0],
+                frameCell[1],
+                frameCell[2],
+            ) = unpack_from(">Bhhh", cells_volts_data, 8 * i)
+            for idx in range(3):
+                cellnum = ((frame - 1) * 3) + idx  # daly is 1 based, driver 0 based
+                if cellnum >= self.cell_count:
+                    break  # ignore possible unused bytes of last sentence
+                cellVoltage = frameCell[idx] / 1000
+                self.cells[cellnum].voltage = (
+                    None if cellVoltage < lowMin else cellVoltage
+                )
         return True
 
     def read_cell_voltage_range_data(self, ser):
-        minmax_data = self.read_serial_data_daly(ser, self.command_minmax_cell_volts)
+        minmax_data = self.request_data(ser, self.command_minmax_cell_volts)
         # check if connection success
         if minmax_data is False:
             logger.warning("No data received in read_cell_voltage_range_data()")
@@ -429,7 +413,7 @@ def read_cell_voltage_range_data(self, ser):
         return True
 
     def read_balance_state(self, ser):
-        balance_data = self.read_serial_data_daly(ser, self.command_cell_balance)
+        balance_data = self.request_data(ser, self.command_cell_balance)
         # check if connection success
         if balance_data is False:
             logger.debug("No data received in read_balance_state()")
@@ -445,7 +429,7 @@ def read_balance_state(self, ser):
         return True
 
     def read_temperature_range_data(self, ser):
-        minmax_data = self.read_serial_data_daly(ser, self.command_minmax_temp)
+        minmax_data = self.request_data(ser, self.command_minmax_temp)
         # check if connection success
         if minmax_data is False:
             logger.debug("No data received in read_temperature_range_data()")
@@ -457,7 +441,7 @@ def read_temperature_range_data(self, ser):
         return True
 
     def read_fed_data(self, ser):
-        fed_data = self.read_serial_data_daly(ser, self.command_fet)
+        fed_data = self.request_data(ser, self.command_fet)
         # check if connection success
         if fed_data is False:
             logger.debug("No data received in read_fed_data()")
@@ -475,7 +459,7 @@ def read_fed_data(self, ser):
 
     # new
     def read_capacity(self, ser):
-        capa_data = self.read_serial_data_daly(ser, self.command_rated_params)
+        capa_data = self.request_data(ser, self.command_rated_params)
         # check if connection success
         if capa_data is False:
             logger.warning("No data received in read_capacity()")
@@ -490,7 +474,7 @@ def read_capacity(self, ser):
 
     # new
     def read_production_date(self, ser):
-        production = self.read_serial_data_daly(ser, self.command_batt_details)
+        production = self.request_data(ser, self.command_batt_details)
         # check if connection success
         if production is False:
             logger.warning("No data received in read_production_date()")
@@ -502,39 +486,19 @@ def read_production_date(self, ser):
 
     # new
     def read_battery_code(self, ser):
-        lenFixed = (
-            5 * 13
-        )  # batt code field is 35 bytes and we transfer 7 bytes in each telegram
-        data = self.read_serialport_data(
-            ser,
-            self.generate_command(self.command_batt_code),
-            self.LENGTH_POS,
-            0,
-            lenFixed,
-        )
+        data = self.request_data(ser, self.command_batt_code, sentences_to_receive=5)
 
         if data is False:
             logger.warning("No data received in read_battery_code()")
             return False
 
-        bufIdx = 0
         battery_code = ""
         # logger.warning("data " + bytes(cells_volts_data).hex())
-        while (
-            bufIdx <= len(data) - 13
-        ):  # we at least need 13 bytes to extract the identifiers + 8 bytes payload + checksum
-            b1, b2, b3, b4 = unpack_from(">BBBB", data, bufIdx)
-            if b1 == 0xA5 and b2 == 0x01 and b3 == 0x57 and b4 == 0x08:
-                _, part, chk = unpack_from(">B7sB", data, bufIdx + 4)
-                if sum(data[bufIdx : bufIdx + 12]) & 0xFF != chk:
-                    logger.warning(
-                        "bad battery code checksum"
-                    )  # use string anyhow, just warn
-                battery_code += part.decode("utf-8")
-                bufIdx += 13  # BBBBB7sB -> 13 byte
-            else:
-                bufIdx += 1  # step through buffer to find valid start
-                logger.warning("bad battery code header")
+        for i in range(5):
+            nr, part = unpack_from(">B7s", data, i * 8)
+            if nr != i + 1:
+                logger.warning("bad battery code index")  # use string anyhow, just warn
+            battery_code += part.decode("utf-8")
 
         if battery_code != "":
             self.custom_field = sub(
@@ -549,145 +513,6 @@ def read_battery_code(self, ser):
             )
         return True
 
-    def generate_command(self, command):
-        buffer = bytearray(self.command_base)
-        buffer[1] = self.command_address[0]  # Always serial 40 or 80
-        buffer[2] = command[0]
-        buffer[12] = sum(buffer[:12]) & 0xFF  # checksum calc
-        return buffer
-
-    def read_serial_data_daly(self, ser, command):
-        data = self.read_serialport_data(
-            ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK
-        )
-        if data is False:
-            # sleep 100 ms and retry.
-            sleep(0.100)
-            data = self.read_serialport_data(
-                ser, self.generate_command(command), self.LENGTH_POS, self.LENGTH_CHECK
-            )
-            if data is False:
-                logger.info("No reply to cmd " + bytes(command).hex())
-                return False
-            else:
-                logger.info("       |- Error cleared, received data after one retry.")
-
-        if len(data) <= 12:
-            logger.debug("Too short reply to cmd " + bytes(command).hex())
-            return False
-
-        # search sentence start
-        try:
-            idx = data.index(0xA5)
-        except ValueError:
-            logger.debug(
-                "No Sentence Start found for reply to cmd " + bytes(command).hex()
-            )
-            return False
-
-        if len(data[idx:]) <= 12:
-            logger.debug("Too short reply to cmd " + bytes(command).hex())
-            return False
-
-        if data[12 + idx] != sum(data[idx : 12 + idx]) & 0xFF:
-            logger.debug("Bad checksum in reply to cmd " + bytes(command).hex())
-            return False
-
-        _, _, _, length = unpack_from(">BBBB", data, idx)
-
-        if length == 8:
-            return data[4 + idx : length + 4 + idx]
-        else:
-            logger.debug(
-                ">>> ERROR: Incorrect Reply to CMD "
-                + bytes(command).hex()
-                + ": 0x"
-                + bytes(data).hex()
-            )
-            return False
-
-    # Read data from previously opened serial port
-    def read_serialport_data(
-        self,
-        ser,
-        command,
-        length_pos,
-        length_check,
-        length_fixed=None,
-        length_size=None,
-    ):
-        try:
-            # wait shortly, else the Daly is not ready and throws a lot of no reply errors
-            # if you see a lot of errors, try to increase in steps of 0.005
-            sleep(0.020)
-
-            time_run = 0
-            time_start = time()
-            ser.flushOutput()
-            ser.flushInput()
-            ser.write(command)
-
-            length_byte_size = 1
-            if length_size is not None:
-                if length_size.upper() == "H":
-                    length_byte_size = 2
-                elif length_size.upper() == "I" or length_size.upper() == "L":
-                    length_byte_size = 4
-
-            toread = ser.inWaiting()
-
-            while toread < (length_pos + length_byte_size):
-                sleep(0.005)
-                toread = ser.inWaiting()
-                time_run = time() - time_start
-                if time_run > 0.500:
-                    self.runtime = time_run
-                    logger.error(">>> ERROR: No reply - returning")
-                    return False
-
-            # logger.info('serial data toread ' + str(toread))
-            res = ser.read(toread)
-            if length_fixed is not None:
-                length = length_fixed
-            else:
-                if len(res) < (length_pos + length_byte_size):
-                    logger.error(
-                        ">>> ERROR: No reply - returning [len:" + str(len(res)) + "]"
-                    )
-                    return False
-                length_size = length_size if length_size is not None else "B"
-                length = unpack_from(">" + length_size, res, length_pos)[0]
-
-            data = bytearray(res)
-
-            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)
-                sleep(0.005)
-                time_run = time() - time_start
-                if time_run > 0.500:
-                    self.runtime = time_run
-                    logger.error(
-                        ">>> ERROR: No reply - returning [len:"
-                        + str(len(data))
-                        + "/"
-                        + str(length + length_check)
-                        + "]"
-                    )
-                    return False
-
-            self.runtime = time_run
-            return data
-
-        except Exception as e:
-            logger.error(e)
-            return False
-
     def reset_soc_callback(self, path, value):
         if value is None:
             return False
@@ -727,9 +552,13 @@ def write_soc_and_datetime(self, ser):
         logger.info(f"write soc {self.soc_to_set}%")
         self.soc_to_set = None  # Reset value, so we will set it only once
 
-        reply = self.read_serialport_data(ser, cmd, self.LENGTH_POS, self.LENGTH_CHECK)
+        time_start = time()
+        ser.flushOutput()
+        ser.flushInput()
+        ser.write(cmd)
 
-        if reply[4] != 1:
+        reply = self.read_sentence(ser, self.command_set_soc)
+        if reply[0] != 1:
             logger.error("write soc failed")
         return True
 
@@ -778,11 +607,12 @@ def write_charge_discharge_mos(self, ser):
                 f"write force disable charging: {'true' if self.trigger_force_disable_charge else 'false'}"
             )
             self.trigger_force_disable_charge = None
+            ser.flushOutput()
+            ser.flushInput()
+            ser.write(cmd)
 
-            reply = self.read_serialport_data(
-                ser, cmd, self.LENGTH_POS, self.LENGTH_CHECK
-            )
-            if reply is False or reply[4] != cmd[4]:
+            reply = self.read_sentence(ser, self.command_disable_charge_mos)
+            if reply is False or reply[0] != cmd[4]:
                 logger.error("write force disable charge/discharge failed")
                 return False
 
@@ -794,11 +624,83 @@ def write_charge_discharge_mos(self, ser):
                 f"write force disable discharging: {'true' if self.trigger_force_disable_discharge else 'false'}"
             )
             self.trigger_force_disable_discharge = None
+            ser.flushOutput()
+            ser.flushInput()
+            ser.write(cmd)
 
-            reply = self.read_serialport_data(
-                ser, cmd, self.LENGTH_POS, self.LENGTH_CHECK
-            )
-            if reply is False or reply[4] != cmd[4]:
+            reply = self.read_sentence(ser, self.command_disable_discharge_mos)
+            if reply is False or reply[0] != cmd[4]:
                 logger.error("write force disable charge/discharge failed")
                 return False
         return True
+
+    def generate_command(self, command):
+        buffer = bytearray(self.command_base)
+        buffer[1] = self.command_address[0]  # Always serial 40 or 80
+        buffer[2] = command[0]
+        buffer[12] = sum(buffer[:12]) & 0xFF  # checksum calc
+        return buffer
+
+    def request_data(self, ser, command, sentences_to_receive=1):
+        # wait shortly, else the Daly is not ready and throws a lot of no reply errors
+        # if you see a lot of errors, try to increase in steps of 0.005
+        sleep(0.020)
+
+        self.runtime = 0
+        time_start = time()
+        ser.flushOutput()
+        ser.flushInput()
+        ser.write(self.generate_command(command))
+
+        reply = bytearray()
+        for i in range(sentences_to_receive):
+            next = self.read_sentence(ser, command)
+            if not next:
+                logger.info(f"request_data: bad reply no. {i}")
+                return False
+            reply += next
+        self.runtime = time() - time_start
+        return reply
+
+    def read_sentence(self, ser, expected_reply, timeout=0.5):
+        """read one 13 byte sentence from daly smart bms.
+        return false if less than 13 bytes received in timeout secs, or frame errors occured
+        return received datasection as bytearray else
+        """
+        time_start = time()
+
+        reply = ser.read_until(b"\xA5")
+        if not reply or b"\xA5" not in reply:
+            logger.error(
+                f"read_sentence {bytes(expected_reply).hex()}: no sentence start received"
+            )
+            return False
+
+        idx = reply.index(b"\xA5")
+        reply = reply[idx:]
+        toread = ser.inWaiting()
+        while toread < 12:
+            sleep((12 - toread) * 0.001)
+            toread = ser.inWaiting()
+            time_run = time() - time_start
+            if time_run > timeout:
+                logger.warning(f"read_sentence {bytes(expected_reply).hex()}: timeout")
+                return False
+
+        reply += ser.read(12)
+        _, id, cmd, length = unpack_from(">BBBB", reply)
+
+        # logger.info(f"reply: {bytes(reply).hex()}")  # debug
+
+        if id != 1 or length != 8 or cmd != expected_reply[0]:
+            logger.error(f"read_sentence {bytes(expected_reply).hex()}: wrong header")
+            return False
+
+        chk = unpack_from(">B", reply, 12)[0]
+        if sum(reply[:12]) & 0xFF != chk:
+            logger.warning(
+                f"read_sentence {bytes(expected_reply).hex()}: wrong checksum"
+            )
+            return False
+
+        return reply[4:12]
diff --git a/etc/dbus-serialbattery/qml/PageLynxIonIo.qml b/etc/dbus-serialbattery/qml/PageLynxIonIo.qml
index 21bfedf4..9d0ff4c4 100644
--- a/etc/dbus-serialbattery/qml/PageLynxIonIo.qml
+++ b/etc/dbus-serialbattery/qml/PageLynxIonIo.qml
@@ -88,5 +88,6 @@ MbPage {
 				MbOption{description: qsTr("Active"); value: 1}
 			]
 		}
+
 	}
 }

From a69118e8df8166f4633466768be10ed2c9d0941a Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Sun, 21 May 2023 21:26:17 +0200
Subject: [PATCH 04/21] updated changelog

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

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad28b18d..68bf2917 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -61,6 +61,7 @@
 * Changed: Bash output by @mr-manuel
 * Changed: Daly BMS - Fixed BMS alerts by @mr-manuel
 * Changed: Daly BMS - Improved driver stability by @transistorgit & @mr-manuel
+* Changed: Daly BMS - Reworked serial parser by @transistorgit
 * Changed: Default config file by @ppuetsch
   * 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
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index e36782da..35ffa370 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -35,7 +35,7 @@ def _get_list_from_config(
 # if not specified: baud = 9600
 
 # Constants - Need to dynamically get them in future
-DRIVER_VERSION = "1.0.20230518dev"
+DRIVER_VERSION = "1.0.20230521dev"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 

From 96d7c3861cd2d025e35187370571d568d7b66ea9 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Sun, 21 May 2023 21:32:04 +0200
Subject: [PATCH 05/21] fix black lint error

---
 etc/dbus-serialbattery/battery.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index 2b8c8d9d..04f0258d 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -248,12 +248,11 @@ def manage_charge_voltage_linear(self) -> None:
                                 voltageSum - penaltySum,
                                 utils.MIN_CELL_VOLTAGE * self.cell_count,
                             ),
-                            utils.MAX_CELL_VOLTAGE * self.cell_count
+                            utils.MAX_CELL_VOLTAGE * self.cell_count,
                         ),
                         3,
                     )
 
-
                 self.charge_mode = (
                     "Bulk dynamic"
                     # + " (vS: "

From f7d76212ce7ae9f0df7964ee71f289c9c179b75a Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Sun, 21 May 2023 21:47:05 +0200
Subject: [PATCH 06/21] fixed black lint error

---
 etc/dbus-serialbattery/bms/daly.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py
index 8555367b..b304b283 100644
--- a/etc/dbus-serialbattery/bms/daly.py
+++ b/etc/dbus-serialbattery/bms/daly.py
@@ -552,7 +552,6 @@ def write_soc_and_datetime(self, ser):
         logger.info(f"write soc {self.soc_to_set}%")
         self.soc_to_set = None  # Reset value, so we will set it only once
 
-        time_start = time()
         ser.flushOutput()
         ser.flushInput()
         ser.write(cmd)

From e17ebab33ca849808cdd70538bdea12f61febcfd Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Mon, 22 May 2023 10:19:25 +0200
Subject: [PATCH 07/21] added infos to battery template

---
 etc/dbus-serialbattery/bms/battery_template.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/etc/dbus-serialbattery/bms/battery_template.py b/etc/dbus-serialbattery/bms/battery_template.py
index e6148e65..e6cbd6a6 100644
--- a/etc/dbus-serialbattery/bms/battery_template.py
+++ b/etc/dbus-serialbattery/bms/battery_template.py
@@ -1,9 +1,10 @@
 # -*- coding: utf-8 -*-
 
 # NOTES
-# Please also update the feature comparison table, if you are adding a new BMS
-# https://louisvdw.github.io/dbus-serialbattery/general/features/#bms-feature-comparison
+# Please see "Add/Request a new BMS" https://louisvdw.github.io/dbus-serialbattery/general/supported-bms#add-by-opening-a-pull-request
+# in the documentation for a checklist what you have to do, when adding a new BMS
 
+# avoid importing wildcards
 from battery import Protection, Battery, Cell
 from utils import is_bit_set, read_serial_data, logger
 import utils

From bb93ee3269c2bb8e49a5d97f0cf67ec7b52dd482 Mon Sep 17 00:00:00 2001
From: Paul Strawder <paulidstein@gmail.com>
Date: Tue, 23 May 2023 16:40:10 +0200
Subject: [PATCH 08/21] JDB BLE support (#499)

* Implementing JBD BLE support. It is built upon Bleak
* Additionally, it provides some handling of for up to 4 temperature probes labels T1 (NTC2), T2 (NTC3), T3 (NTC4) and T4 (NTC5). NTC1 is the BMS module temperature itself
* The device page has been extend to provide more details about the actual used hardware (= product name), firmware version and BLE address
---
 etc/dbus-serialbattery/battery.py         | 104 ++++++--
 etc/dbus-serialbattery/bms/lltjbd.py      | 294 ++++++++++++++++++++--
 etc/dbus-serialbattery/bms/lltjbd_ble.py  |  44 ++--
 etc/dbus-serialbattery/bms/mnb.py         |   4 +-
 etc/dbus-serialbattery/config.default.ini |  12 +-
 etc/dbus-serialbattery/dbushelper.py      |  16 +-
 etc/dbus-serialbattery/utils.py           |   6 +
 requirements.txt                          |   1 +
 8 files changed, 408 insertions(+), 73 deletions(-)

diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index 2b8c8d9d..fba9cd35 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -96,6 +96,8 @@ def init_values(self):
         self.temp_sensors = None
         self.temp1 = None
         self.temp2 = None
+        self.temp3 = None
+        self.temp4 = None
         self.temp_mos = None
         self.cells: List[Cell] = []
         self.control_charging = None
@@ -127,6 +129,15 @@ def test_connection(self) -> bool:
         # return false when failed, true if successful
         return False
 
+    def connection_name(self) -> str:
+        return "Serial " + self.port
+
+    def custom_name(self) -> str:
+        return "SerialBattery(" + self.type + ")"
+
+    def product_name(self) -> str:
+        return "SerialBattery(" + self.type + ")"
+
     @abstractmethod
     def get_settings(self) -> bool:
         """
@@ -164,6 +175,10 @@ def to_temp(self, sensor: int, value: float) -> None:
             self.temp1 = min(max(value, -20), 100)
         if sensor == 2:
             self.temp2 = min(max(value, -20), 100)
+        if sensor == 3:
+            self.temp3 = min(max(value, -20), 100)
+        if sensor == 4:
+            self.temp4 = min(max(value, -20), 100)
 
     def manage_charge_voltage(self) -> None:
         """
@@ -808,14 +823,10 @@ def get_balancing(self) -> int:
                 return 1
         return 0
 
-    def extract_from_temp_values(self, extractor) -> Union[float, None]:
-        if self.temp1 is not None and self.temp2 is not None:
-            return extractor(self.temp1, self.temp2)
-        if self.temp1 is not None and self.temp2 is None:
-            return self.temp1
-        if self.temp1 is None and self.temp2 is not None:
-            return self.temp2
-        else:
+    def get_temperatures(self) -> Union[List[float], None]:
+        temperatures = [self.temp1, self.temp2, self.temp3, self.temp4]
+        result = [(t, i) for (t, i) in enumerate(temperatures) if t is not None]
+        if not result:
             return None
 
     def get_temp(self) -> Union[float, None]:
@@ -824,46 +835,93 @@ def get_temp(self) -> Union[float, None]:
                 return self.temp1
             elif utils.TEMP_BATTERY == 2:
                 return self.temp2
+            elif utils.TEMP_BATTERY == 3:
+                return self.temp3
+            elif utils.TEMP_BATTERY == 4:
+                return self.temp4
             else:
-                return self.extract_from_temp_values(
-                    extractor=lambda temp1, temp2: round(
-                        (float(temp1) + float(temp2)) / 2, 2
-                    )
-                )
+                temps = [
+                    t
+                    for t in [self.temp1, self.temp2, self.temp3, self.temp4]
+                    if t is not None
+                ]
+                n = len(temps)
+                if not temps or n == 0:
+                    return None
+                data = sorted(temps)
+                if n % 2 == 1:
+                    return data[n // 2]
+                else:
+                    i = n // 2
+                    return (data[i - 1] + data[i]) / 2
         except TypeError:
             return None
 
     def get_min_temp(self) -> Union[float, None]:
         try:
-            return self.extract_from_temp_values(
-                extractor=lambda temp1, temp2: min(temp1, temp2)
-            )
+            temps = [
+                t
+                for t in [self.temp1, self.temp2, self.temp3, self.temp4]
+                if t is not None
+            ]
+            if not temps:
+                return None
+            return min(temps)
         except TypeError:
             return None
 
     def get_min_temp_id(self) -> Union[str, None]:
         try:
-            if self.temp1 < self.temp2:
+            temps = [
+                (t, i)
+                for i, t in enumerate([self.temp1, self.temp2, self.temp3, self.temp4])
+                if t is not None
+            ]
+            if not temps:
+                return None
+            index = min(temps)[1]
+            if index == 0:
                 return utils.TEMP_1_NAME
-            else:
+            if index == 1:
                 return utils.TEMP_2_NAME
+            if index == 2:
+                return utils.TEMP_3_NAME
+            if index == 3:
+                return utils.TEMP_4_NAME
         except TypeError:
             return None
 
     def get_max_temp(self) -> Union[float, None]:
         try:
-            return self.extract_from_temp_values(
-                extractor=lambda temp1, temp2: max(temp1, temp2)
-            )
+            temps = [
+                t
+                for t in [self.temp1, self.temp2, self.temp3, self.temp4]
+                if t is not None
+            ]
+            if not temps:
+                return None
+            return max(temps)
         except TypeError:
             return None
 
     def get_max_temp_id(self) -> Union[str, None]:
         try:
-            if self.temp1 > self.temp2:
+            temps = [
+                (t, i)
+                for i, t in enumerate([self.temp1, self.temp2, self.temp3, self.temp4])
+                if t is not None
+            ]
+            if not temps:
+                return None
+            index = max(temps)[1]
+            if index == 0:
                 return utils.TEMP_1_NAME
-            else:
+            if index == 1:
                 return utils.TEMP_2_NAME
+            if index == 2:
+                return utils.TEMP_3_NAME
+            if index == 3:
+                return utils.TEMP_4_NAME
         except TypeError:
             return None
 
diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py
index c1bf2ae3..4c091aff 100644
--- a/etc/dbus-serialbattery/bms/lltjbd.py
+++ b/etc/dbus-serialbattery/bms/lltjbd.py
@@ -5,6 +5,166 @@
 from struct import unpack_from
 import struct
 
+# Protocol registers
+REG_ENTER_FACTORY = 0x00
+REG_EXIT_FACTORY = 0x01
+# REG_UNKNOWN = 0x02
+REG_GENERAL = 0x03
+REG_CELL = 0x04
+REG_HARDWARE = 0x05
+# Firmware 0x16+
+REG_USE_PASSWORD = 0x06
+REG_SET_PASSWORD = 0x07
+# REG_UNKNOWN2 = 0x08 - Maybe define master password?
+REG_CLEAR_PASSWORD = 0x09
+
+REG_FRESET = 0x0A
+
+REG_DESIGN_CAP = 0x10
+REG_CYCLE_CAP = 0x11
+REG_CAP_100 = 0x12
+REG_CAP_0 = 0x13
+REG_SELF_DSG_RATE = 0x14
+REG_MFG_DATE = 0x15
+REG_SERIAL_NUM = 0x16
+REG_CYCLE_CNT = 0x17
+REG_CHGOT = 0x18
+REG_CHGOT_REL = 0x19
+REG_CHGUT = 0x1A
+REG_CHGUT_REL = 0x1B
+REG_DSGOT = 0x1C
+REG_DSGOT_REL = 0x1D
+REG_DSGUT = 0x1E
+REG_DSGUT_REL = 0x1F
+REG_POVP = 0x20
+REG_POVP_REL = 0x21
+REG_PUVP = 0x22
+REG_PUVP_REL = 0x23
+REG_COVP = 0x24
+REG_COVP_REL = 0x25
+REG_CUVP = 0x26
+REG_CUVP_REL = 0x27
+REG_CHGOC = 0x28
+REG_DSGOC = 0x29
+REG_BAL_START = 0x2A
+REG_BAL_WINDOW = 0x2B
+REG_SHUNT_RES = 0x2C
+REG_FUNC_CONFIG = 0x2D
+REG_NTC_CONFIG = 0x2E
+REG_CELL_CNT = 0x2F
+REG_FET_TIME = 0x30
+REG_LED_TIME = 0x31
+REG_CAP_80 = 0x32
+REG_CAP_60 = 0x33
+REG_CAP_40 = 0x34
+REG_CAP_20 = 0x35
+REG_COVP_HIGH = 0x36
+REG_CUVP_HIGH = 0x37
+REG_SC_DSGOC2 = 0x38
+REG_CXVP_HIGH_DELAY_SC_REL = 0x39
+REG_CHG_T_DELAYS = 0x3A
+REG_DSG_T_DELAYS = 0x3B
+REG_PACK_V_DELAYS = 0x3C
+REG_CELL_V_DELAYS = 0x3D
+REG_CHGOC_DELAYS = 0x3E
+REG_DSGOC_DELAYS = 0x3F
+REG_GPSOFF = 0x40
+REG_GPSOFF_TIME = 0x41
+REG_CAP_90 = 0x42
+REG_CAP_70 = 0x43
+REG_CAP_50 = 0x44
+REG_CAP_30 = 0x45
+REG_CAP_10 = 0x46
+# REG_CAP2_100 = 0x47
+
+# [0x48, 0x9F] - 87 registers
+
+REG_MFGNAME = 0xA0
+REG_MODEL = 0xA1
+REG_BARCODE = 0xA2
+REG_ERROR = 0xAA
+# 0xAB
+# 0xAC
+REG_CAL_CUR_IDLE = 0xAD
+REG_CAL_CUR_CHG = 0xAE
+REG_CAL_CUR_DSG = 0xAF
+
+REG_CAL_V_CELL_01 = 0xB0
+REG_CAL_V_CELL_02 = 0xB1
+REG_CAL_V_CELL_03 = 0xB2
+REG_CAL_V_CELL_04 = 0xB3
+REG_CAL_V_CELL_05 = 0xB4
+REG_CAL_V_CELL_06 = 0xB5
+REG_CAL_V_CELL_07 = 0xB6
+REG_CAL_V_CELL_08 = 0xB7
+REG_CAL_V_CELL_09 = 0xB8
+REG_CAL_V_CELL_10 = 0xB9
+REG_CAL_V_CELL_11 = 0xBA
+REG_CAL_V_CELL_12 = 0xBB
+REG_CAL_V_CELL_13 = 0xBC
+REG_CAL_V_CELL_14 = 0xBD
+REG_CAL_V_CELL_15 = 0xBE
+REG_CAL_V_CELL_16 = 0xBF
+REG_CAL_V_CELL_17 = 0xC0
+REG_CAL_V_CELL_18 = 0xC1
+REG_CAL_V_CELL_19 = 0xC2
+REG_CAL_V_CELL_20 = 0xC3
+REG_CAL_V_CELL_21 = 0xC4
+REG_CAL_V_CELL_22 = 0xC5
+REG_CAL_V_CELL_23 = 0xC6
+REG_CAL_V_CELL_24 = 0xC7
+REG_CAL_V_CELL_25 = 0xC8
+REG_CAL_V_CELL_26 = 0xC9
+REG_CAL_V_CELL_27 = 0xCA
+REG_CAL_V_CELL_28 = 0xCB
+REG_CAL_V_CELL_29 = 0xCC
+REG_CAL_V_CELL_30 = 0xCD
+REG_CAL_V_CELL_31 = 0xCE
+REG_CAL_V_CELL_32 = 0xCF
+
+REG_CAL_T_NTC_0 = 0xD0
+REG_CAL_T_NTC_1 = 0xD1
+REG_CAL_T_NTC_2 = 0xD2
+REG_CAL_T_NTC_3 = 0xD3
+REG_CAL_T_NTC_4 = 0xD4
+REG_CAL_T_NTC_5 = 0xD5
+REG_CAL_T_NTC_6 = 0xD6
+REG_CAL_T_NTC_7 = 0xD7
+
+REG_CAP_REMAINING = 0xE0
+REG_CTRL_MOSFET = 0xE1
+REG_CTRL_BALANCE = 0xE2
+REG_RESET = 0xE3
+
+# Protocol commands
+CMD_ENTER_FACTORY_MODE = b"\x56\x78"
+CMD_EXIT_FACTORY_MODE = b"\x00\x00"
+CMD_EXIT_AND_SAVE_FACTORY_MODE = b"\x28\x28"
+
+
+def checksum(payload):
+    return (0x10000 - sum(payload)) % 0x10000
+
+
+def cmd(op, reg, data):
+    payload = [reg, len(data)] + list(data)
+    chksum = checksum(payload)
+    data = [0xDD, op] + payload + [chksum, 0x77]
+    format = f">BB{len(payload)}BHB"
+    return struct.pack(format, *data)
+
+
+def readCmd(reg, data=None):
+    if data is None:
+        data = []
+    return cmd(0xA5, reg, data)
+
+
+def writeCmd(reg, data=None):
+    if data is None:
+        data = []
+    return cmd(0x5A, reg, data)
+
 
 class LltJbdProtection(Protection):
     def __init__(self):
@@ -51,37 +211,84 @@ def __init__(self, port, baud, address):
         super(LltJbd, self).__init__(port, baud, address)
         self.protection = LltJbdProtection()
         self.type = self.BATTERYTYPE
+        self._product_name: str = ""
+        self.has_settings = 0
+        self.reset_soc = 100
+        self.soc_to_set = None
+        self.factory_mode = False
+        self.writable = False
 
     # degree_sign = u'\N{DEGREE SIGN}'
-    command_general = b"\xDD\xA5\x03\x00\xFF\xFD\x77"
-    command_cell = b"\xDD\xA5\x04\x00\xFF\xFC\x77"
-    command_hardware = b"\xDD\xA5\x05\x00\xFF\xFB\x77"
     BATTERYTYPE = "LLT/JBD"
     LENGTH_CHECK = 6
     LENGTH_POS = 3
 
+    command_general = readCmd(REG_GENERAL)  # b"\xDD\xA5\x03\x00\xFF\xFD\x77"
+    command_cell = readCmd(REG_CELL)  # b"\xDD\xA5\x04\x00\xFF\xFC\x77"
+    command_hardware = readCmd(REG_HARDWARE)  # b"\xDD\xA5\x05\x00\xFF\xFB\x77"
+
     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()
+            result = self.get_settings()
+            if result:
+                result = result and self.read_hardware_data()
             # get first data to show in startup log
             if result:
-                self.refresh_data()
+                result = result and self.refresh_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
 
         return result
 
+    def product_name(self) -> str:
+        return self._product_name
+
     def get_settings(self):
-        self.read_gen_data()
+        if not self.read_gen_data():
+            return False
         self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
         self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
+        with self.eeprom(writable=False):
+            charge_over_current = self.read_serial_data_llt(readCmd(REG_CHGOC))
+            if charge_over_current:
+                self.max_battery_charge_current = float(
+                    unpack_from(">h", charge_over_current)[0] / 100.0
+                )
+            discharge_over_current = self.read_serial_data_llt(readCmd(REG_DSGOC))
+            if discharge_over_current:
+                self.max_battery_discharge_current = float(
+                    unpack_from(">h", discharge_over_current)[0] / -100.0
+                )
+
         return True
 
+    def reset_soc_callback(self, path, value):
+        if value is None:
+            return False
+
+        if value < 0 or value > 100:
+            return False
+
+        self.reset_soc = value
+        self.soc_to_set = value
+        return True
+
+    def write_soc(self):
+        if self.soc_to_set is None or self.soc_to_set != 100 or not self.voltage:
+            return False
+        logger.info(f"write soc {self.soc_to_set}%")
+        self.soc_to_set = None  # Reset value, so we will set it only once
+        # TODO implement logic to map current pack readings into
+        # REG_CAP_100, REG_CAP_90, REG_CAP_80, REG_CAP_70, REG_CAP_60, ...
+        with self.eeprom(writable=True):
+            pack_voltage = struct.pack(">H", int(self.voltage * 10))
+            self.read_serial_data_llt(writeCmd(REG_CAP_100, pack_voltage))
+
     def refresh_data(self):
         result = self.read_gen_data()
         result = result and self.read_cell_data()
@@ -200,7 +407,9 @@ def read_gen_data(self):
         self.capacity_remain = capacity_remain / 100
         self.capacity = capacity / 100
         self.to_cell_bits(balance, balance2)
-        self.version = float(str(version >> 4 & 0x0F) + "." + str(version & 0x0F))
+        self.hardware_version = float(
+            str(version >> 4 & 0x0F) + "." + str(version & 0x0F)
+        )
         self.to_fet_bits(fet)
         self.to_protection_bits(protection)
         self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
@@ -234,24 +443,73 @@ def read_hardware_data(self):
         if hardware_data is False:
             return False
 
-        self.hardware_version = unpack_from(
+        self._product_name = unpack_from(
             ">" + str(len(hardware_data)) + "s", hardware_data
         )[0].decode()
-        logger.debug(self.hardware_version)
+        logger.debug(self._product_name)
         return True
 
+    @staticmethod
+    def validate_packet(data):
+        if not data:
+            return False
+
+        if data is False:
+            return False
+
+        start, op, status, payload_length = unpack_from("BBBB", data)
+        if start != 0xDD:
+            logger.error(
+                ">>> ERROR: Invalid response packet. Expected begin packet character 0xDD"
+            )
+        if status != 0x0:
+            logger.warn(">>> WARN: BMS rejected request. Status " + status)
+            return False
+        if len(data) != payload_length + 7:
+            logger.error(
+                ">>> ERROR: BMS send insufficient data. Received "
+                + str(len(data))
+                + " expected "
+                + str(payload_length + 7)
+            )
+            return False
+        chk_sum, end = unpack_from(">HB", data, payload_length + 4)
+        if end != 0x77:
+            logger.error(
+                ">>> ERROR: Incorrect Reply. Expected end packet character 0x77"
+            )
+            return False
+        if chk_sum != checksum(data[2:-3]):
+            logger.error(">>> ERROR: Invalid checksum.")
+            return False
+
+        payload = data[4 : payload_length + 4]
+
+        return payload
+
     def read_serial_data_llt(self, command):
         data = read_serial_data(
             command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK
         )
-        if data is False:
-            return False
+        return self.validate_packet(data)
 
-        start, flag, command_ret, length = unpack_from("BBBB", data)
-        checksum, end = unpack_from("HB", data, length + 4)
+    def __enter__(self):
+        if self.read_serial_data_llt(
+            writeCmd(REG_ENTER_FACTORY, CMD_ENTER_FACTORY_MODE)
+        ):
+            self.factory_mode = True
 
-        if end == 119:
-            return data[4 : length + 4]
-        else:
-            logger.error(">>> ERROR: Incorrect Reply")
-            return False
+    def __exit__(self, type, value, traceback):
+        cmd_value = (
+            CMD_EXIT_AND_SAVE_FACTORY_MODE if self.writable else CMD_EXIT_FACTORY_MODE
+        )
+        if self.factory_mode:
+            if not self.read_serial_data_llt(writeCmd(REG_EXIT_FACTORY, cmd_value)):
+                logger.error(">>> ERROR: Unable to exit factory mode.")
+            else:
+                self.factory_mode = False
+                self.writable = False
+
+    def eeprom(self, writable=False):
+        self.writable = writable
+        return self
diff --git a/etc/dbus-serialbattery/bms/lltjbd_ble.py b/etc/dbus-serialbattery/bms/lltjbd_ble.py
index fa4b38da..de995492 100644
--- a/etc/dbus-serialbattery/bms/lltjbd_ble.py
+++ b/etc/dbus-serialbattery/bms/lltjbd_ble.py
@@ -3,13 +3,12 @@
 import atexit
 import functools
 import threading
+from asyncio import CancelledError
 from typing import Union, Optional
 from utils import logger
-from struct import unpack_from
 from bleak import BleakClient, BleakScanner, BLEDevice
 from bms.lltjbd import LltJbdProtection, LltJbd
 
-
 BLE_SERVICE_UUID = "0000ff00-0000-1000-8000-00805f9b34fb"
 BLE_CHARACTERISTICS_TX_UUID = "0000ff02-0000-1000-8000-00805f9b34fb"
 BLE_CHARACTERISTICS_RX_UUID = "0000ff01-0000-1000-8000-00805f9b34fb"
@@ -21,7 +20,9 @@ class LltJbd_Ble(LltJbd):
     BATTERYTYPE = "LltJbd_Ble"
 
     def __init__(self, port: Optional[str], baud: Optional[int], address: str):
-        super(LltJbd_Ble, self).__init__(address.replace(":", "").lower(), -1, address)
+        super(LltJbd_Ble, self).__init__(
+            "ble" + address.replace(":", "").lower(), -1, address
+        )
 
         self.address = address
         self.protection = LltJbdProtection()
@@ -50,9 +51,14 @@ def on_disconnect(self, client):
         logger.info("BLE client disconnected")
 
     async def bt_main_loop(self):
-        self.device = await BleakScanner.find_device_by_address(
-            self.address, cb=dict(use_bdaddr=True)
-        )
+        try:
+            self.device = await BleakScanner.find_device_by_address(
+                self.address, cb=dict(use_bdaddr=True)
+            )
+        except Exception as e:
+            logger.error(">>> ERROR: Bluetooth stack failed.", e)
+            self.device = None
+            await asyncio.sleep(0.5)
 
         if not self.device:
             self.run = False
@@ -155,22 +161,18 @@ async def async_read_serial_data_llt(self, command):
     def read_serial_data_llt(self, command):
         if not self.bt_loop:
             return False
-        data = asyncio.run(self.async_read_serial_data_llt(command))
-        if not data:
+        try:
+            data = asyncio.run(self.async_read_serial_data_llt(command))
+            return self.validate_packet(data)
+        except CancelledError as e:
+            logger.error(">>> ERROR: No reply - canceled - returning", e)
             return False
-
-        start, flag, command_ret, length = unpack_from("BBBB", data)
-        checksum, end = unpack_from("HB", data, length + 4)
-
-        if end == 119:
-            return data[4 : length + 4]
-        else:
-            logger.error(">>> ERROR: Incorrect Reply")
+        except Exception as e:
+            logger.error(">>> ERROR: No reply - returning", e)
             return False
 
 
-"""
-async def test_LltJbd_Ble():
+if __name__ == "__main__":
     import sys
 
     bat = LltJbd_Ble("Foo", -1, sys.argv[1])
@@ -178,8 +180,4 @@ async def test_LltJbd_Ble():
         logger.error(">>> ERROR: Unable to connect")
     else:
         bat.refresh_data()
-
-
-if __name__ == "__main__":
-    test_LltJbd_Ble()
-"""
+        bat.get_settings()
diff --git a/etc/dbus-serialbattery/bms/mnb.py b/etc/dbus-serialbattery/bms/mnb.py
index 1ad1126e..84365866 100644
--- a/etc/dbus-serialbattery/bms/mnb.py
+++ b/etc/dbus-serialbattery/bms/mnb.py
@@ -169,9 +169,9 @@ def manage_charge_current(self):
 
         # Change depending on the cell_min_voltage values
         if self.cell_min_voltage < self.V_C_min + 0.05:
-            self.control_allow_dicharge = False
+            self.control_allow_discharge = False
         else:
-            self.control_allow_dicharge = True
+            self.control_allow_discharge = True
 
         if self.cell_min_voltage < self.V_C_min + 0.15:
             b = 10 * (self.cell_min_voltage - self.V_C_min - 0.05)
diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini
index 4b11e9b5..e9b4f403 100644
--- a/etc/dbus-serialbattery/config.default.ini
+++ b/etc/dbus-serialbattery/config.default.ini
@@ -199,10 +199,12 @@ MIDPOINT_ENABLE = False
 
 
 ; Battery temperature
-; Specifiy how the battery temperature is assembled
-; 0 Get mean of temperature sensor 1 and temperature sensor 2
+; Specify how the battery temperature is assembled
+; 0 Get mean of temperature sensor 1 to sensor 4
 ; 1 Get only temperature from temperature sensor 1
 ; 2 Get only temperature from temperature sensor 2
+; 3 Get only temperature from temperature sensor 3
+; 4 Get only temperature from temperature sensor 4
 TEMP_BATTERY = 0
 
 ; Temperature sensor 1 name
@@ -211,6 +213,12 @@ TEMP_1_NAME = Temp 1
 ; Temperature sensor 2 name
 TEMP_2_NAME = Temp 2
 
+; Temperature sensor 2 name
+TEMP_3_NAME = Temp 3
+
+; Temperature sensor 2 name
+TEMP_4_NAME = Temp 4
+
 
 ; --------- BMS specific settings ---------
 
diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py
index ccf7181e..0cca3187 100644
--- a/etc/dbus-serialbattery/dbushelper.py
+++ b/etc/dbus-serialbattery/dbushelper.py
@@ -113,19 +113,17 @@ def setup_vedbus(self):
         self._dbusservice.add_path(
             "/Mgmt/ProcessVersion", "Python " + platform.python_version()
         )
-        self._dbusservice.add_path("/Mgmt/Connection", "Serial " + self.battery.port)
+        self._dbusservice.add_path("/Mgmt/Connection", self.battery.connection_name())
 
         # Create the mandatory objects
         self._dbusservice.add_path("/DeviceInstance", self.instance)
         self._dbusservice.add_path("/ProductId", 0x0)
-        self._dbusservice.add_path(
-            "/ProductName", "SerialBattery(" + self.battery.type + ")"
-        )
+        self._dbusservice.add_path("/ProductName", self.battery.product_name())
         self._dbusservice.add_path("/FirmwareVersion", str(utils.DRIVER_VERSION))
         self._dbusservice.add_path("/HardwareVersion", self.battery.hardware_version)
         self._dbusservice.add_path("/Connected", 1)
         self._dbusservice.add_path(
-            "/CustomName", "SerialBattery(" + self.battery.type + ")", writeable=True
+            "/CustomName", self.battery.custom_name(), writeable=True
         )
         self._dbusservice.add_path(
             "/Serial", self.battery.unique_identifier, writeable=True
@@ -231,6 +229,10 @@ def setup_vedbus(self):
         self._dbusservice.add_path("/System/MaxCellTemperature", None, writeable=True)
         self._dbusservice.add_path("/System/MaxTemperatureCellId", None, writeable=True)
         self._dbusservice.add_path("/System/MOSTemperature", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature1", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature2", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature3", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature4", None, writeable=True)
         self._dbusservice.add_path(
             "/System/MaxCellVoltage",
             None,
@@ -470,6 +472,10 @@ def publish_dbus(self):
             "/System/MaxTemperatureCellId"
         ] = self.battery.get_max_temp_id()
         self._dbusservice["/System/MOSTemperature"] = self.battery.get_mos_temp()
+        self._dbusservice["/System/Temperature1"] = self.battery.temp1
+        self._dbusservice["/System/Temperature2"] = self.battery.temp2
+        self._dbusservice["/System/Temperature3"] = self.battery.temp3
+        self._dbusservice["/System/Temperature4"] = self.battery.temp4
 
         # Voltage control
         self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index e36782da..8b188446 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -294,6 +294,12 @@ def _get_list_from_config(
 # Temperature sensor 2 name
 TEMP_2_NAME = config["DEFAULT"]["TEMP_2_NAME"]
 
+# Temperature sensor 3 name
+TEMP_3_NAME = config["DEFAULT"]["TEMP_3_NAME"]
+
+# Temperature sensor 2 name
+TEMP_4_NAME = config["DEFAULT"]["TEMP_4_NAME"]
+
 
 # --------- BMS specific settings ---------
 
diff --git a/requirements.txt b/requirements.txt
index 8bec2184..6de24eea 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
 pyserial==3.5
 minimalmodbus==2.0.1
+bleak==0.20.0
\ No newline at end of file

From a0a5ebfb5907d7caf2a49d8c2be768e09a386f73 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Tue, 23 May 2023 17:02:58 +0200
Subject: [PATCH 09/21] updated changelog

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

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68bf2917..c4f88750 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@
 * Added: JKBMS BLE - Show if balancing is active and which cells are balancing by @mr-manuel
 * Added: JKBMS BLE - Show serial number and "User Private Data" field that can be set in the JKBMS App to identify the BMS in a multi battery environment by @mr-manuel
 * Added: JKBMS BLE driver by @baranator
+* Added: LLT/JBD BMS BLE driver by @idstein
 * Added: Possibility to add `config.ini` to the root of a USB flash drive on install via the USB method by @mr-manuel
 * Added: Possibility to configure a `VOLTAGE_DROP` voltage, if you are using a SmartShunt as battery monitor as there is a little voltage difference https://github.com/Louisvdw/dbus-serialbattery/discussions/632 by @mr-manuel
 * Added: Post install notes by @mr-manuel
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index b98197e0..c854c4f3 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -35,7 +35,7 @@ def _get_list_from_config(
 # if not specified: baud = 9600
 
 # Constants - Need to dynamically get them in future
-DRIVER_VERSION = "1.0.20230521dev"
+DRIVER_VERSION = "1.0.20230523dev"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 

From f19e004ed643180aa909dbf4a6fb2208bf6cfbcb Mon Sep 17 00:00:00 2001
From: Raphael Mack <ramack@raphael-mack.de>
Date: Wed, 24 May 2023 16:59:13 +0200
Subject: [PATCH 10/21] Add Supoprt for HeltecSmartBMS (YYBMS) using modbus via
 RS485 connection (#658)

---
 docs/docs/general/features.md                |  44 +-
 docs/docs/general/install.md                 |   2 +-
 docs/docs/general/supported-bms.md           |   3 +
 etc/dbus-serialbattery/bms/heltecmodbus.py   | 439 +++++++++++++++++++
 etc/dbus-serialbattery/config.default.ini    |   6 +
 etc/dbus-serialbattery/dbus-serialbattery.py |   2 +
 etc/dbus-serialbattery/utils.py              |   5 +
 7 files changed, 478 insertions(+), 23 deletions(-)
 create mode 100644 etc/dbus-serialbattery/bms/heltecmodbus.py

diff --git a/docs/docs/general/features.md b/docs/docs/general/features.md
index a7b2434c..2ed2e9b3 100644
--- a/docs/docs/general/features.md
+++ b/docs/docs/general/features.md
@@ -83,7 +83,7 @@ CCCM limits the charge/discharge current depending on the highest/lowest cell vo
 * between `2.8V - 2.9V` &rarr; `5A `discharge
 * below `<= 2.70V` &rarr; `0A` discharge
 
-### Temprature
+### Temperature
 
 * `CCCM_T_ENABLE = True/False`
 * `DCCM_T_ENABLE = True/False`
@@ -121,27 +121,27 @@ If the `MAX_CELL_VOLTAGE` \* `cell count` is reached for `MAX_VOLTAGE_TIME_SEC`
 
 ## BMS feature comparison
 
-| Feature                                  | Ant   | Daly  | ECS                | HLPdataBMS4S | JK BMS | Life/Tian Power | LLT/JBD | MNB <sup>(1)</sup> | Renogy | Seplos | Sinowealth <sup>(1)</sup> |
-| ---:                                     | :---: | :---: | :---:              | :---:        | :---:  | :---:           | :---:   | :---:              | :---:  | :---:  | :---:                     |
-| Voltage                                  | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Current                                  | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Power                                    | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| State Of Charge                          | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Battery temperature                      | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| MOSFET temperature                       | No    | No    | No                 | No           | Yes    | No              | Yes     | No                 | No     | No     | No                        |
-| Consumed Ah                              | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Time-to-go                               | Calc  | Calc  | Calc               | Calc         | Calc   | Calc            | Calc    | Calc               | Calc   | Calc   | Calc                      |
-| Min/max cell voltages                    | Yes   | Yes   | No                 | Yes          | Yes    | Yes             | Yes     | No                 | Yes    | Yes    | Yes                       |
-| Min/max temperature                      | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Installed capacity                       | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Available capacity                       | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Cell details                             | No    | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | No                 | Yes    | Yes    | ?                         |
-| Balancing status                         | Yes   | No    | Yes                | No           | Yes    | Yes             | No      | No                 | No     | No     | ?                         |
-| Raise alarms from the BMS                | Yes   | Yes   | Yes <sup>(2)</sup> | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | ?                         |
-| History of charge cycles                 | Yes   | Yes   | No                 | No           | Yes    | Yes             | Yes     | No                 | Yes    | Yes    | Yes                       |
-| Get CCL/DCL from the BMS                 | No    | No    | No                 | No           | Yes    | No              | No      | No                 | No     | No     | No                        |
-| Charge current control management (CCCM) | Yes   | Yes   | Yes                | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
-| Set battery parameters (DVCC)            | Calc  | Calc  | Yes                | Yes          | Calc   | Calc            | Calc    | Yes                | Calc   | Calc   | Calc                      |
+| Feature                                  | Ant   | Daly  | ECS                | Heltec | HLPdataBMS4S | JK BMS | Life/Tian Power | LLT/JBD | MNB <sup>(1)</sup> | Renogy | Seplos | Sinowealth <sup>(1)</sup> |
+| ---:                                     | :---: | :---: | :---:              | :---:  | :---:        | :---:  | :---:           | :---:   | :---:              | :---:  | :---:  | :---:                     |
+| Voltage                                  | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Current                                  | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Power                                    | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| State Of Charge                          | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Battery temperature                      | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| MOSFET temperature                       | No    | No    | No                 | Yes    | No           | Yes    | No              | Yes     | No                 | No     | No     | No                        |
+| Consumed Ah                              | Yes   | Yes   | Yes                | No     | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Time-to-go                               | Calc  | Calc  | Calc               | Calc   | Calc         | Calc   | Calc            | Calc    | Calc               | Calc   | Calc   | Calc                      |
+| Min/max cell voltages                    | Yes   | Yes   | No                 | Yes    | Yes          | Yes    | Yes             | Yes     | No                 | Yes    | Yes    | Yes                       |
+| Min/max temperature                      | Yes   | Yes   | Yes                | No     | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Installed capacity                       | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Available capacity                       | Yes   | Yes   | Yes                | No     | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Cell details                             | No    | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | No                 | Yes    | Yes    | ?                         |
+| Balancing status                         | Yes   | No    | Yes                | Yes    | No           | Yes    | Yes             | No      | No                 | No     | No     | ?                         |
+| Raise alarms from the BMS                | Yes   | Yes   | Yes <sup>(2)</sup> | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | ?                         |
+| History of charge cycles                 | Yes   | Yes   | No                 | No     | No           | Yes    | Yes             | Yes     | No                 | Yes    | Yes    | Yes                       |
+| Get CCL/DCL from the BMS                 | No    | No    | No                 | Yes    | No           | Yes    | No              | No      | No                 | No     | No     | No                        |
+| Charge current control management (CCCM) | Yes   | Yes   | Yes                | Yes    | Yes          | Yes    | Yes             | Yes     | Yes                | Yes    | Yes    | Yes                       |
+| Set battery parameters (DVCC)            | Calc  | Calc  | Yes                | Calc   | Yes          | Calc   | Calc            | Calc    | Yes                | Calc   | Calc   | Calc                      |
 
 
 `Calc` means that the value is calculated by the driver.
diff --git a/docs/docs/general/install.md b/docs/docs/general/install.md
index 56c891bc..4b30e030 100644
--- a/docs/docs/general/install.md
+++ b/docs/docs/general/install.md
@@ -122,7 +122,7 @@ Select `2` for `nightly build` and then select the branch you want to install fr
 ### BMS specific settings
 
 * ECS BMS &rarr; https://github.com/Louisvdw/dbus-serialbattery/issues/254#issuecomment-1275924313
-
+* HeltecModbus &rarr; in case the modbus slave address of the BMS was adjusted from the factory default, configure the slave addresses to query in config.ini:HELTEC_MODBUS_ADDR. As always the battery settings shall be configured in the BMS already via app or computer.
 
 ## How to change the default limits
 
diff --git a/docs/docs/general/supported-bms.md b/docs/docs/general/supported-bms.md
index 416e7362..ff247950 100644
--- a/docs/docs/general/supported-bms.md
+++ b/docs/docs/general/supported-bms.md
@@ -22,6 +22,9 @@ Disabled by default since driver version `v0.14.0` as it causes other issues. Se
 
 ### &bull; ECS GreenMeter with LiPro
 
+### &bull; HeltecModbus SmartBMS (YanYang BMS)
+Communication to the Heltec SmartBMS (which is a rebranded YYBMS) via Modbus/RS485.
+
 ### &bull; HLPdataBMS4S
 
 ### &bull; [JKBMS](https://www.jkbms.com/products/) / Heltec BMS
diff --git a/etc/dbus-serialbattery/bms/heltecmodbus.py b/etc/dbus-serialbattery/bms/heltecmodbus.py
new file mode 100644
index 00000000..9291fd26
--- /dev/null
+++ b/etc/dbus-serialbattery/bms/heltecmodbus.py
@@ -0,0 +1,439 @@
+# -*- coding: utf-8 -*-
+# known limitations:
+#   - only BMS variants with 2 cell temperature sensors supported
+#   - some "interesting" datapoints are not read (e. g. registers 52: switch type, 62: bootloader and firmware version)
+#   - SOC not yet resettable from Venus (similary to Daly for support of writing SOC), but modbus write to 120 should be
+#     fairly possible)
+
+
+from battery import Battery, Cell
+from utils import logger
+import utils
+import serial
+import time
+import minimalmodbus
+from typing import Dict
+import threading
+
+# the Heltec BMS is not always as responsive as it should, so let's try it up to (RETRYCNT - 1) times to talk to it
+RETRYCNT = 10
+
+# the wait time after a communication - normally this should be as defined by modbus RTU and handled in minimalmodbus,
+# but yeah, it seems we need it for the Heltec BMS
+SLPTIME = 0.03
+
+mbdevs: Dict[int, minimalmodbus.Instrument] = {}
+locks: Dict[int, any] = {}
+
+
+class HeltecModbus(Battery):
+    def __init__(self, port, baud, address):
+        super(HeltecModbus, self).__init__(port, baud, address)
+        self.type = "Heltec_Smart"
+
+    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
+        for self.address in utils.HELTEC_MODBUS_ADDR:
+            logger.info("Testing on slave address " + str(self.address))
+            found = False
+            if self.address not in locks:
+                locks[self.address] = threading.Lock()
+
+            # TODO: We need to lock not only based on the address, but based on the port as soon as multiple BMSs
+            # are supported on the same serial interface. Then locking on the port will be enough.
+
+            with locks[self.address]:
+                mbdev = minimalmodbus.Instrument(
+                    self.port,
+                    slaveaddress=self.address,
+                    mode="rtu",
+                    close_port_after_each_call=True,
+                    debug=False,
+                )
+                mbdev.serial.parity = minimalmodbus.serial.PARITY_NONE
+                mbdev.serial.stopbits = serial.STOPBITS_ONE
+                mbdev.serial.baudrate = 9600
+                # yes, 400ms is long but the BMS is sometimes really slow in responding, so this is a good compromise
+                mbdev.serial.timeout = 0.4
+                mbdevs[self.address] = mbdev
+
+                for n in range(1, RETRYCNT):
+                    try:
+                        string = mbdev.read_string(7, 13)
+                        time.sleep(SLPTIME)
+                        found = True
+                        logger.info(
+                            "found in try "
+                            + str(n)
+                            + "/"
+                            + str(RETRYCNT)
+                            + " for "
+                            + self.port
+                            + "("
+                            + str(self.address)
+                            + "): "
+                            + string
+                        )
+                    except Exception as e:
+                        logger.warn(
+                            "testing failed ("
+                            + str(e)
+                            + ") "
+                            + str(n)
+                            + "/"
+                            + str(RETRYCNT)
+                            + " for "
+                            + self.port
+                            + "("
+                            + str(self.address)
+                            + ")"
+                        )
+                        continue
+                    break
+                if found:
+                    self.type = "#" + str(self.address) + "_Heltec_Smart"
+                    break
+
+        return (
+            found
+            and self.read_status_data()
+            and self.get_settings()
+            and self.refresh_data()
+        )
+
+    def get_settings(self):
+        self.max_battery_voltage = self.max_cell_voltage * self.cell_count
+        self.min_battery_voltage = self.min_cell_voltage * self.cell_count
+
+        return True
+
+    def refresh_data(self):
+        # call all functions that will refresh the battery data.
+        # This will be called for every iteration (1 second)
+        # Return True if success, False for failure
+        return self.read_soc_data() and self.read_cell_data()
+
+    def read_status_data(self):
+        mbdev = mbdevs[self.address]
+
+        with locks[self.address]:
+            for n in range(1, RETRYCNT):
+                try:
+                    ccur = mbdev.read_register(191, 0, 3, False)
+                    self.max_battery_charge_current = (
+                        (int)(((ccur & 0xFF) << 8) | ((ccur >> 8) & 0xFF))
+                    ) / 100
+                    time.sleep(SLPTIME)
+
+                    dc = mbdev.read_register(194, 0, 3, False)
+                    self.max_battery_discharge_current = (
+                        ((dc & 0xFF) << 8) | ((dc >> 8) & 0xFF)
+                    ) / 100
+                    time.sleep(SLPTIME)
+
+                    cap = mbdev.read_register(118, 0, 3, False)
+                    self.capacity = (((cap & 0xFF) << 8) | ((cap >> 8) & 0xFF)) / 10
+                    time.sleep(SLPTIME)
+
+                    cap = mbdev.read_register(119, 0, 3, False)
+                    self.actual_capacity = (
+                        ((cap & 0xFF) << 8) | ((cap >> 8) & 0xFF)
+                    ) / 10
+                    time.sleep(SLPTIME)
+
+                    cap = mbdev.read_register(126, 0, 3, False)
+                    self.learned_capacity = (
+                        ((cap & 0xFF) << 8) | ((cap >> 8) & 0xFF)
+                    ) / 10
+                    time.sleep(SLPTIME)
+
+                    volt = mbdev.read_register(169, 0, 3, False)
+                    self.max_cell_voltage = (
+                        ((volt & 0xFF) << 8) | ((volt >> 8) & 0xFF)
+                    ) / 1000
+                    time.sleep(SLPTIME)
+
+                    volt = mbdev.read_register(172, 0, 3, False)
+                    self.min_cell_voltage = (
+                        ((volt & 0xFF) << 8) | ((volt >> 8) & 0xFF)
+                    ) / 1000
+                    time.sleep(SLPTIME)
+
+                    string = mbdev.read_string(7, 13)
+                    self.hwTypeName = string
+                    time.sleep(SLPTIME)
+
+                    string = mbdev.read_string(41, 6)
+                    self.devName = string
+                    time.sleep(SLPTIME)
+
+                    serial1 = mbdev.read_registers(2, number_of_registers=4)
+                    self.unique_identifier = "-".join(
+                        "{:04x}".format(x) for x in serial1
+                    )
+                    time.sleep(SLPTIME)
+
+                    self.pw = mbdev.read_string(47, 2)
+                    time.sleep(SLPTIME)
+
+                    tmp = mbdev.read_register(75)
+                    # h: batterytype: 0: Ternery Lithium, 1: Iron Lithium, 2: Lithium Titanat
+                    # l: #of cells
+
+                    self.cell_count = (tmp >> 8) & 0xFF
+                    tmp = tmp & 0xFF
+                    if tmp == 0:
+                        self.cellType = "Ternary Lithium"
+                    elif tmp == 1:
+                        self.cellType = "Iron Lithium"
+                    elif tmp == 2:
+                        self.cellType = "Lithium Titatnate"
+                    else:
+                        self.cellType = "unknown"
+                    time.sleep(SLPTIME)
+
+                    self.hardware_version = (
+                        self.devName
+                        + "("
+                        + str((mbdev.read_register(38) >> 8) & 0xFF)
+                        + ")"
+                    )
+                    time.sleep(SLPTIME)
+
+                    date = mbdev.read_long(39, 3, True, minimalmodbus.BYTEORDER_LITTLE)
+                    self.production_date = (
+                        str(date & 0xFFFF)
+                        + "-"
+                        + str((date >> 24) & 0xFF)
+                        + "-"
+                        + str((date >> 16) & 0xFF)
+                    )
+                    time.sleep(SLPTIME)
+
+                    # we finished all readings without trouble, so let's break from the retry loop
+                    break
+                except Exception as e:
+                    logger.warn(
+                        "Error reading settings from BMS, retry ("
+                        + str(n)
+                        + "/"
+                        + str(RETRYCNT)
+                        + "): "
+                        + str(e)
+                    )
+                    continue
+
+            logger.info(self.hardware_version)
+            logger.info("Heltec-" + self.hwTypeName)
+            logger.info("  Dev name: " + self.devName)
+            logger.info("  Serial: " + self.unique_identifier)
+            logger.info("  Made on: " + self.production_date)
+            logger.info("  Cell count: " + str(self.cell_count))
+            logger.info("  Cell type: " + self.cellType)
+            logger.info("  BT password: " + self.pw)
+            logger.info("  rated capacity: " + str(self.capacity))
+            logger.info("  actual capacity: " + str(self.actual_capacity))
+            logger.info("  learned capacity: " + str(self.learned_capacity))
+
+        return True
+
+    def read_soc_data(self):
+        mbdev = mbdevs[self.address]
+
+        with locks[self.address]:
+            for n in range(1, RETRYCNT):
+                try:
+                    self.voltage = (
+                        mbdev.read_long(76, 3, True, minimalmodbus.BYTEORDER_LITTLE)
+                        / 1000
+                    )
+                    time.sleep(SLPTIME)
+
+                    self.current = -(
+                        mbdev.read_long(78, 3, True, minimalmodbus.BYTEORDER_LITTLE)
+                        / 100
+                    )
+                    time.sleep(SLPTIME)
+
+                    runState1 = mbdev.read_long(
+                        152, 3, True, minimalmodbus.BYTEORDER_LITTLE
+                    )
+                    time.sleep(SLPTIME)
+
+                    # bit 29 is discharge protection
+                    if (runState1 & 0x20000000) == 0:
+                        self.discharge_fet = True
+                    else:
+                        self.discharge_fet = False
+
+                    # bit 28 is charge protection
+                    if (runState1 & 0x10000000) == 0:
+                        self.charge_fet = True
+                    else:
+                        self.charge_fet = False
+
+                    warnings = mbdev.read_long(
+                        156, 3, True, minimalmodbus.BYTEORDER_LITTLE
+                    )
+                    if (warnings & (1 << 3)) or (
+                        warnings & (1 << 15)
+                    ):  # 15 is full protection, 3 is total overvoltage
+                        self.voltage_high = 2
+                    else:
+                        self.voltage_high = 0
+
+                    if warnings & (1 << 0):
+                        self.protection.voltage_cell_high = 2
+                        # we handle a single cell OV as total OV, as long as cell_high is not explicitly handled
+                        self.protection.voltage_high = 1
+                    else:
+                        self.protection.voltage_cell_high = 0
+
+                    if warnings & (1 << 1):
+                        self.protection.voltage_cell_low = 2
+                    else:
+                        self.protection.voltage_cell_low = 0
+
+                    if warnings & (1 << 4):
+                        self.protection.voltage_low = 2
+                    else:
+                        self.protection.voltage_low = 0
+
+                    if warnings & (1 << 5):
+                        self.protection.current_over = 2
+                    else:
+                        self.protection.current_over = 0
+
+                    if warnings & (1 << 7):
+                        self.protection.current_under = 2
+                    elif warnings & (1 << 6):
+                        self.protection.current_under = 1
+                    else:
+                        self.protection.current_under = 0
+
+                    if warnings & (1 << 8):  # this is a short circuit
+                        self.protection.current_over = 2
+
+                    if warnings & (1 << 9):
+                        self.protection.temp_high_charge = 2
+                    else:
+                        self.protection.temp_high_charge = 0
+
+                    if warnings & (1 << 10):
+                        self.protection.temp_low_charge = 2
+                    else:
+                        self.protection.temp_low_charge = 0
+
+                    if warnings & (1 << 11):
+                        self.protection.temp_high_discharge = 2
+                    else:
+                        self.protection.temp_high_discharge = 0
+
+                    if warnings & (1 << 12):
+                        self.protection.temp_low_discharge = 2
+                    else:
+                        self.protection.temp_low_discharge = 0
+
+                    if warnings & (1 << 13):  # MOS overtemp
+                        self.protection.temp_high_internal = 2
+                    else:
+                        self.protection.temp_high_internal = 0
+
+                    if warnings & (1 << 14):  # SOC low
+                        self.protection.soc_low = 2
+                    else:
+                        self.protection.soc_low = 0
+
+                    if warnings & (0xFFFF0000):  # any other fault
+                        self.protection.internal_failure = 2
+                    else:
+                        self.protection.internal_failure = 0
+
+                    socsoh = mbdev.read_register(120, 0, 3, False)
+                    self.soh = socsoh & 0xFF
+                    self.soc = (socsoh >> 8) & 0xFF
+                    time.sleep(SLPTIME)
+
+                    # we could read min and max temperature, here, but I have a BMS with only 2 sensors,
+                    # so I couldn't test the logic and read therefore only the first two temperatures
+                    #   tminmax = mbdev.read_register(117, 0, 3, False)
+                    #   nmin = (tminmax & 0xFF)
+                    #   nmax = ((tminmax >> 8) & 0xFF)
+
+                    temps = mbdev.read_register(113, 0, 3, False)
+                    self.temp1 = (temps & 0xFF) - 40
+                    self.temp2 = ((temps >> 8) & 0xFF) - 40
+                    time.sleep(SLPTIME)
+
+                    temps = mbdev.read_register(112, 0, 3, False)
+                    most = (temps & 0xFF) - 40
+                    balt = ((temps >> 8) & 0xFF) - 40
+                    # balancer temperature is not handled separately in dbus-serialbattery,
+                    # so let's display the max of both temperatures inside the BMS as mos temperature
+                    self.temp_mos = max(most, balt)
+                    time.sleep(SLPTIME)
+
+                    return True
+
+                except Exception as e:
+                    logger.warn(
+                        "Error reading SOC, retry ("
+                        + str(n)
+                        + "/"
+                        + str(RETRYCNT)
+                        + ") "
+                        + str(e)
+                    )
+                    continue
+                break
+            logger.warn("Error reading SOC, failed")
+        return False
+
+    def read_cell_data(self):
+        result = False
+        mbdev = mbdevs[self.address]
+
+        with locks[self.address]:
+            for n in range(1, RETRYCNT):
+                try:
+                    cells = mbdev.read_registers(
+                        81, number_of_registers=self.cell_count
+                    )
+                    time.sleep(SLPTIME)
+
+                    balancing = mbdev.read_long(
+                        139, 3, signed=False, byteorder=minimalmodbus.BYTEORDER_LITTLE
+                    )
+                    time.sleep(SLPTIME)
+
+                    result = True
+                except Exception as e:
+                    logger.warn(
+                        "read_cell_data() failed ("
+                        + str(e)
+                        + ") "
+                        + str(n)
+                        + "/"
+                        + str(RETRYCNT)
+                    )
+                    continue
+                break
+            if result is False:
+                return False
+
+            if len(self.cells) != self.cell_count:
+                self.cells = []
+                for idx in range(self.cell_count):
+                    self.cells.append(Cell(False))
+
+            i = 0
+            for cell in cells:
+                cellV = ((cell & 0xFF) << 8) | ((cell >> 8) & 0xFF)
+                self.cells[i].voltage = cellV / 1000
+                self.cells[i].balance = balancing & (1 << i) != 0
+
+                i = i + 1
+
+        return True
diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini
index e9b4f403..25762314 100644
--- a/etc/dbus-serialbattery/config.default.ini
+++ b/etc/dbus-serialbattery/config.default.ini
@@ -240,6 +240,12 @@ LIPRO_START_ADDRESS = 2
 LIPRO_END_ADDRESS   = 4
 LIPRO_CELL_COUNT = 15
 
+; -- HeltecModbus (Heltec SmartBMS/YYBMS) settings
+; Set the Modbus addresses from the adapters
+; Separate each address to check by a comma like: 1, 2, 3, ...
+; factory default address will be 1
+HELTEC_MODBUS_ADDR = 1
+
 
 ; --------- Battery monitor specific settings ---------
 ; If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index b50c938c..6530ace1 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -24,6 +24,7 @@
 # import battery classes
 from bms.daly import Daly
 from bms.ecs import Ecs
+from bms.heltecmodbus import HeltecModbus
 from bms.hlpdatabms4s import HLPdataBMS4S
 from bms.jkbms import Jkbms
 from bms.lifepower import Lifepower
@@ -39,6 +40,7 @@
     {"bms": Daly, "baud": 9600, "address": b"\x40"},
     {"bms": Daly, "baud": 9600, "address": b"\x80"},
     {"bms": Ecs, "baud": 19200},
+    {"bms": HeltecModbus, "baud": 9600},
     {"bms": HLPdataBMS4S, "baud": 9600},
     {"bms": Jkbms, "baud": 115200},
     {"bms": Lifepower, "baud": 9600},
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index c854c4f3..8769d5a0 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -321,6 +321,11 @@ def _get_list_from_config(
 LIPRO_END_ADDRESS = int(config["DEFAULT"]["LIPRO_END_ADDRESS"])
 LIPRO_CELL_COUNT = int(config["DEFAULT"]["LIPRO_CELL_COUNT"])
 
+# -- HeltecModbus device settings
+HELTEC_MODBUS_ADDR = _get_list_from_config(
+    "DEFAULT", "HELTEC_MODBUS_ADDR", lambda v: int(v)
+)
+
 
 # --------- Battery monitor specific settings ---------
 # If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported

From fc95f6bdbd5439486300d443900ba9497bad91fe Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Wed, 24 May 2023 17:11:17 +0200
Subject: [PATCH 11/21] Changes 2023.05.24 (#667)

* Check Venus OS version before installing
---
 CHANGELOG.md                              |   8 +-
 etc/dbus-serialbattery/reinstall-local.sh | 132 +++++++++++++++++-----
 2 files changed, 107 insertions(+), 33 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4f88750..bf3497d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,20 +12,22 @@
 * Added: Balancing switch status to the GUI -> SerialBattery -> IO by @mr-manuel
 * Added: Block charge/discharge when BMS communication is lost. Can be enabled trough the config file by @mr-manuel
 * Added: Charge Mode display by @mr-manuel
+* Added: Check minimum required Venus OS version before installing 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: Daly BMS - Discharge / Charge Mosfet switching over remote console/GUI https://github.com/Louisvdw/dbus-serialbattery/issues/26 by @transistorgit
 * Added: Daly BMS - Read capacity https://github.com/Louisvdw/dbus-serialbattery/pull/594 by @transistorgit
 * Added: Daly BMS - Read production date and build unique identifier by @transistorgit
 * Added: Daly BMS - Set SoC by @transistorgit
 * Added: Daly BMS - Show "battery code" field that can be set in the Daly app by @transistorgit
-* Added: Daly BMS - Discharge / Charge Mosfet switching over remote console/GUI https://github.com/Louisvdw/dbus-serialbattery/issues/26 by @transistorgit
 * Added: Device name field (found in the GUI -> SerialBattery -> Device), that show a custom string that can be set in some BMS, if available by @mr-manuel
 * Added: Driver uninstall script by @mr-manuel
-* Added: Rename TAR file after USB/SD card install to not overwrite the data on every reboot https://github.com/Louisvdw/dbus-serialbattery/issues/638 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: HeltecSmartBMS driver by @ramack
 * Added: HighInternalTemperature alarm (MOSFET) for JKBMS by @mr-manuel
+* Added: HLPdata BMS driver by @ peterohman
 * Added: Improved maintainability (flake8, black lint), introduced code checks and automate release build https://github.com/Louisvdw/dbus-serialbattery/pull/386 by @ppuetsch
 * Added: Install needed Bluetooth components automatically after a Venus OS upgrade by @mr-manuel
 * Added: JKBMS - MOS temperature https://github.com/Louisvdw/dbus-serialbattery/pull/440 by @baphomett
@@ -45,8 +47,10 @@
 * Added: Post install notes by @mr-manuel
 * Added: Read charge/discharge limits from JKBMS by @mr-manuel
 * Added: Recalculation interval in linear mode for CVL, CCL and DCL by @mr-manuel
+* Added: Rename TAR file after USB/SD card install to not overwrite the data on every reboot https://github.com/Louisvdw/dbus-serialbattery/issues/638 by @mr-manuel
 * Added: Reset values to None, if battery goes offline (not reachable for 10s). Fixes https://github.com/Louisvdw/dbus-serialbattery/issues/193 https://github.com/Louisvdw/dbus-serialbattery/issues/64 by @transistorgit
 * Added: Script to install directly from repository by @mr-manuel
+* Added: Seplos BMS driver by @wollew
 * Added: Serial number field (found in the GUI -> SerialBattery -> Device), that show the serial number or a unique identifier for the BMS, if available by @mr-manuel
 * Added: Show charge mode (absorption, bulk, ...) in Parameters page by @mr-manuel
 * Added: Show charge/discharge limitation reason by @mr-manuel
diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh
index b082c620..bdc0030a 100755
--- a/etc/dbus-serialbattery/reinstall-local.sh
+++ b/etc/dbus-serialbattery/reinstall-local.sh
@@ -5,6 +5,63 @@
 
 DRIVERNAME=dbus-serialbattery
 
+
+# check if minimum required Venus OS is installed | start
+versionRequired="v2.90"
+
+# elaborate version string for better comparing
+# https://github.com/kwindrem/SetupHelper/blob/ebaa65fcf23e2bea6797f99c1c41174143c1153c/updateFileSets#L56-L81
+function versionStringToNumber ()
+{
+    local local p4="" ; local p5="" ; local p5=""
+    local major=""; local minor=""
+
+	# first character should be 'v' so first awk parameter will be empty and is not prited into the read command
+	#
+	# version number formats: v2.40, v2.40~6, v2.40-large-7, v2.40~6-large-7
+	# so we must adjust how we use paramters read from the version string
+	# and parsed by awk
+	# if no beta make sure release is greater than any beta (i.e., a beta portion of 999)
+
+    read major minor p4 p5 p6 <<< $(echo $1 | awk -v FS='[v.~-]' '{print $2, $3, $4, $5, $6}')
+	((versionNumber = major * 1000000000 + minor * 1000000))
+	if [ -z $p4 ] || [ $p4 = "large" ]; then
+        ((versionNumber += 999))
+	else
+		((versionNumber += p4))
+    fi
+	if [ ! -z $p4 ] && [ $p4 = "large" ]; then
+		((versionNumber += p5 * 1000))
+		large=$p5
+	elif [ ! -z $p6 ]; then
+		((versionNumber += p6 * 1000))
+	fi
+}
+
+# get current Venus OS version
+versionStringToNumber "$(head -n 1 /opt/victronenergy/version)"
+venusVersionNumber="$versionNumber"
+
+# minimum required version to install the driver
+versionStringToNumber "$versionRequired"
+
+if (( $venusVersionNumber < $versionNumber )); then
+    echo
+    echo
+    echo "Minimum required Venus OS version \"$versionRequired\" not met. Currently version \"$(head -n 1 /opt/victronenergy/version)\" is installed."
+    echo
+    echo "Please update via \"Remote Console/GUI -> Settings -> Firmware -> Online Update\""
+    echo "OR"
+    echo "by executing \"/opt/victronenergy/swupdate-scripts/check-updates.sh -update -force\""
+    echo
+    echo "Install the driver again after Venus OS was updated."
+    echo
+    echo
+    exit 1
+fi
+# check if minimum required Venus OS is installed | end
+
+
 # handle read only mounts
 bash /opt/victronenergy/swupdate-scripts/remount-rw.sh
 
@@ -24,38 +81,47 @@ serialstarter_path="/data/conf/serial-starter.d"
 serialstarter_file="$serialstarter_path/dbus-serialbattery.conf"
 
 # check if folder is a file (older versions of this driver < v1.0.0)
-if [ -f $serialstarter_path ]; then
-    rm -f $serialstarter_path
+if [ -f "$serialstarter_path" ]; then
+    rm -f "$serialstarter_path"
 fi
 
 # check if folder exists
-if [ ! -d $serialstarter_path ]; then
-    mkdir $serialstarter_path
+if [ ! -d "$serialstarter_path" ]; then
+    mkdir "$serialstarter_path"
 fi
 
 # check if file exists
-if [ ! -f $serialstarter_file ]; then
-    echo "service sbattery        dbus-serialbattery" >> $serialstarter_file
-    echo "alias default gps:vedirect:sbattery" >> $serialstarter_file
-    echo "alias rs485 cgwacs:fzsonick:imt:modbus:sbattery" >> $serialstarter_file
+if [ ! -f "$serialstarter_file" ]; then
+    {
+        echo "service sbattery        dbus-serialbattery"
+        echo "alias default gps:vedirect:sbattery"
+        echo "alias rs485 cgwacs:fzsonick:imt:modbus:sbattery"
+    } > "$serialstarter_file"
 fi
 
 # add install-script to rc.local to be ready for firmware update
 filename=/data/rc.local
-if [ ! -f $filename ]; then
-    echo "#!/bin/bash" >> $filename
-    chmod 755 $filename
+if [ ! -f "$filename" ]; then
+    echo "#!/bin/bash" > "$filename"
+    chmod 755 "$filename"
 fi
 grep -qxF "bash /data/etc/$DRIVERNAME/reinstall-local.sh" $filename || echo "bash /data/etc/$DRIVERNAME/reinstall-local.sh" >> $filename
 
 # add empty config.ini, if it does not exist to make it easier for users to add custom settings
-filename=/data/etc/$DRIVERNAME/config.ini
-if [ ! -f $filename ]; then
-    echo "[DEFAULT]" > $filename
-    echo "" >> $filename
-    echo "; If you want to add custom settings, then check the settings you want to change in \"config.default.ini\"" >> $filename
-    echo "; and add them below to persist future driver updates." >> $filename
-    echo "" >> $filename
+filename="/data/etc/$DRIVERNAME/config.ini"
+if [ ! -f "$filename" ]; then
+    {
+        echo "[DEFAULT]"
+        echo
+        echo "; If you want to add custom values/settings, then check the values/settings you want to change in \"config.default.ini\""
+        echo "; and insert them below to persist future driver updates."
+        echo
+        echo "; Example (remove the semicolon \";\" to uncomment and activate the value/setting):"
+        echo "; MAX_BATTERY_CURRENT = 50.0"
+        echo "; MAX_BATTERY_DISCHARGE_CURRENT = 60.0"
+        echo
+        echo
+    } > $filename
 fi
 
 
@@ -84,7 +150,7 @@ rm -rf /service/dbus-blebattery.*
 # kill all blebattery processes
 pkill -f "blebattery"
 
-if [ $length -gt 0 ]; then
+if [ "$length" -gt 0 ]; then
 
     echo "Found $length Bluetooth BMS in the config file!"
     echo ""
@@ -101,16 +167,20 @@ if [ $length -gt 0 ]; then
 
     # function to install ble battery
     install_blebattery_service() {
-        mkdir -p /service/dbus-blebattery.$1/log
-        echo "#!/bin/sh" > /service/dbus-blebattery.$1/log/run
-        echo "exec multilog t s25000 n4 /var/log/dbus-blebattery.$1" >> /service/dbus-blebattery.$1/log/run
-        chmod 755 /service/dbus-blebattery.$1/log/run
-
-        echo "#!/bin/sh" > /service/dbus-blebattery.$1/run
-        echo "exec 2>&1" >> /service/dbus-blebattery.$1/run
-        echo "bluetoothctl disconnect $3" >> /service/dbus-blebattery.$1/run
-        echo "python /opt/victronenergy/dbus-serialbattery/dbus-serialbattery.py $2 $3" >> /service/dbus-blebattery.$1/run
-        chmod 755 /service/dbus-blebattery.$1/run
+        mkdir -p "/service/dbus-blebattery.$1/log"
+        {
+            echo "#!/bin/sh"
+            echo "exec multilog t s25000 n4 /var/log/dbus-blebattery.$1"
+        } > "/service/dbus-blebattery.$1/log/run"
+        chmod 755 "/service/dbus-blebattery.$1/log/run"
+
+        {
+            echo "#!/bin/sh"
+            echo "exec 2>&1"
+            echo "bluetoothctl disconnect $3"
+            echo "python /opt/victronenergy/dbus-serialbattery/dbus-serialbattery.py $2 $3"
+        } > "/service/dbus-blebattery.$1/run"
+        chmod 755 "/service/dbus-blebattery.$1/run"
     }
 
     echo "Packages installed."
@@ -119,7 +189,7 @@ if [ $length -gt 0 ]; then
     # install_blebattery_service 0 Jkbms_Ble C8:47:8C:00:00:00
     # install_blebattery_service 1 Jkbms_Ble C8:47:8C:00:00:11
 
-    for (( i=0; i<${length}; i++ ));
+    for (( i=0; i<length; i++ ));
     do
         echo "Installing ${bms_array[$i]} as dbus-blebattery.$i"
         install_blebattery_service $i "${bms_array[$i]}"
@@ -152,7 +222,7 @@ sed -i "/^sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local
 pkill -f "python .*/$DRIVERNAME.py"
 
 # restart bluetooth service, if Bluetooth BMS configured
-if [ $length -gt 0 ]; then
+if [ "$length" -gt 0 ]; then
     /etc/init.d/bluetooth restart
 fi
 

From 886005daa63dd2996ea9d579abd1da4d6090a12d Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 25 May 2023 12:16:12 +0200
Subject: [PATCH 12/21] fix Jkbms_Ble error

---
 etc/dbus-serialbattery/bms/jkbms_ble.py | 1 +
 etc/dbus-serialbattery/utils.py         | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py
index 03a6e47e..276103c4 100644
--- a/etc/dbus-serialbattery/bms/jkbms_ble.py
+++ b/etc/dbus-serialbattery/bms/jkbms_ble.py
@@ -14,6 +14,7 @@ class Jkbms_Ble(Battery):
 
     def __init__(self, port, baud, address):
         super(Jkbms_Ble, self).__init__(address.replace(":", "").lower(), baud, address)
+        self.address = address
         self.type = self.BATTERYTYPE
         self.jk = Jkbms_Brn(address)
 
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index 8769d5a0..759834b4 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -35,7 +35,7 @@ def _get_list_from_config(
 # if not specified: baud = 9600
 
 # Constants - Need to dynamically get them in future
-DRIVER_VERSION = "1.0.20230523dev"
+DRIVER_VERSION = "1.0.20230525dev"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 

From 81ddf5736dd43433094799beffb2d80befb950d4 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Thu, 25 May 2023 16:04:43 +0200
Subject: [PATCH 13/21] updated changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf3497d8..910781ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -60,6 +60,7 @@
 * 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 1-4 are now also available on the dbus and MQTT by @idstein
 * 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: `reinstall-local.sh` to recreate `/data/conf/serial-starter.d`, if deleted by `disable.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository by @mr-manuel
 * Changed: Added QML to `restore-gui.sh` by @mr-manuel

From 94ece81c4a623de969a73d4ffd3a06162dafc0c6 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Fri, 26 May 2023 09:56:58 +0200
Subject: [PATCH 14/21] updated readme and added donation link

---
 README.md | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index e2989624..063eaaa3 100644
--- a/README.md
+++ b/README.md
@@ -3,23 +3,38 @@ This is a driver for Venus OS devices (any GX device sold by Victron or a Raspbe
 
 The driver will communicate with a Battery Management System (BMS) that support serial communication (RS232, RS485 or TTL UART) and publish this data to the Venus OS system. The main purpose is to act as a Battery Monitor in your GX and supply State Of Charge (SOC) and other values to the inverter.
 
- * [BMS Types supported](https://github.com/Louisvdw/dbus-serialbattery/wiki/BMS-types-supported)
- * [FAQ](https://github.com/Louisvdw/dbus-serialbattery/wiki/FAQ)
- * [Features](https://github.com/Louisvdw/dbus-serialbattery/wiki/Features)
- * [How to install](https://github.com/Louisvdw/dbus-serialbattery/wiki/How-to-install)
- * [Troubleshoot](https://github.com/Louisvdw/dbus-serialbattery/wiki/Troubleshoot)
+ * [BMS Types supported](https://louisvdw.github.io/dbus-serialbattery/general/supported-bms)
+ * [FAQ](https://louisvdw.github.io/dbus-serialbattery/faq/)
+ * [Features](https://louisvdw.github.io/dbus-serialbattery/general/features)
+ * [How to install](https://louisvdw.github.io/dbus-serialbattery/general/install)
+ * [How to troubleshoot](https://louisvdw.github.io/dbus-serialbattery/troubleshoot/)
 
-### Supporting this project:
+### Supporting this project
 If you find this driver helpful please considder supporting this project. You can buy me a Ko-Fi or get in contact if you would like to donate hardware.
 
+### Support [Louisvdw](https://github.com/Louisvdw)
+* Main developer
+* Added most of the BMS drivers
+
 [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Z8Z73LCW1) or using [Paypal.me](https://paypal.me/innernet)
 
+### Support [mr-manuel](https://github.com/mr-manuel)
+* Added a lot of features, optimizations and improvements with `v1.0.x`
+* Added a lot of documentation to the config file and notes that are displayed after installation for better understanding
+* Introduced the new documentation page of the driver and reworked a great part of it for easier understanding
+
+[<img src="https://github.md0.eu/uploads/donate-button.svg" height="38">](https://www.paypal.com/donate/?hosted_button_id=3NEVZBDM5KABW)
+
 ### Developer Remarks
 To develop this project, install the requirements. This project makes use of velib_python which is pre-installed on
 Venus-OS Devices under `/opt/victronenergy/dbus-systemcalc-py/ext/velib_python`. To use the python files locally,
 `git clone` the [velib_python](https://github.com/victronenergy/velib_python) project to velib_python and add
 velib_python to the `PYTHONPATH` environment variable.
 
+Make sure the GitHub Actions run fine in your repository. In order to make the GitHub Actions run please select in your repository settings under `Actions` -> `General` -> `Actions permissions` the option `Allow all actions and reusable workflows`. Check also in your repository settings under `Actions` -> `General` -> `Workflow permissions` if `Read and write permissions` are selected. This will check your code for Flake8 and Black Lint errors. [Here](https://py-vscode.readthedocs.io/en/latest/files/linting.html) is a short instruction on how to set up Flake8 and Black Lint checks in VS Code. This will save you a lot of time.
+
+See this checklist, if you want to [add a new BMS](https://louisvdw.github.io/dbus-serialbattery/general/supported-bms#add-by-opening-a-pull-request)
+
 #### How it works
 * Each supported BMS needs to implement the abstract base class `Battery` from `battery.py`.
 * `dbus-serialbattery.py` tries to figure out the correct connected BMS by looping through all known implementations of

From 02d4d9c16afa1d99c6f018a15210c96780728be8 Mon Sep 17 00:00:00 2001
From: "Strawder, Paul" <paul@coach-ai.com>
Date: Fri, 26 May 2023 13:55:58 +0200
Subject: [PATCH 15/21] bugfix: Heltec BMS test_connection breaks on other
 modbus compliant BMS systems

---
 etc/dbus-serialbattery/bms/heltecmodbus.py   |  4 ++-
 etc/dbus-serialbattery/config.default.ini    |  2 +-
 etc/dbus-serialbattery/dbus-serialbattery.py | 27 ++++++++++++--------
 3 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/etc/dbus-serialbattery/bms/heltecmodbus.py b/etc/dbus-serialbattery/bms/heltecmodbus.py
index 9291fd26..b6271bfb 100644
--- a/etc/dbus-serialbattery/bms/heltecmodbus.py
+++ b/etc/dbus-serialbattery/bms/heltecmodbus.py
@@ -119,7 +119,7 @@ def read_status_data(self):
         mbdev = mbdevs[self.address]
 
         with locks[self.address]:
-            for n in range(1, RETRYCNT):
+            for n in range(1, RETRYCNT + 1):
                 try:
                     ccur = mbdev.read_register(191, 0, 3, False)
                     self.max_battery_charge_current = (
@@ -223,6 +223,8 @@ def read_status_data(self):
                         + "): "
                         + str(e)
                     )
+                    if n == RETRYCNT:
+                        return False
                     continue
 
             logger.info(self.hardware_version)
diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini
index 25762314..7631fc6d 100644
--- a/etc/dbus-serialbattery/config.default.ini
+++ b/etc/dbus-serialbattery/config.default.ini
@@ -180,7 +180,7 @@ TIME_TO_SOC_INC_FROM = False
 
 
 ; --------- Additional settings ---------
-; Specify only one BMS type to load else leave empty to try to load all availabe
+; Specify only one BMS type to load else leave empty to try to load all available
 ; LltJbd, Ant, Daly, Daly, Jkbms, Lifepower, Renogy, Renogy, Ecs
 BMS_TYPE =
 
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index 6530ace1..c3358ae3 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -74,17 +74,24 @@ def get_battery(_port) -> Union[Battery, None]:
         while count > 0:
             # create a new battery object that can read the battery and run connection test
             for test in expected_bms_types:
-                logger.info("Testing " + test["bms"].__name__)
-                batteryClass = test["bms"]
-                baud = test["baud"]
-                battery: Battery = batteryClass(
-                    port=_port, baud=baud, address=test.get("address")
-                )
-                if battery.test_connection():
-                    logger.info(
-                        "Connection established to " + battery.__class__.__name__
+                # noinspection PyBroadException
+                try:
+                    logger.info("Testing " + test["bms"].__name__)
+                    batteryClass = test["bms"]
+                    baud = test["baud"]
+                    battery: Battery = batteryClass(
+                        port=_port, baud=baud, address=test.get("address")
                     )
-                    return battery
+                    if battery.test_connection():
+                        logger.info(
+                            "Connection established to " + battery.__class__.__name__
+                        )
+                        return battery
+                except KeyboardInterrupt:
+                    return None
+                except Exception:
+                    # Ignore any malfunction test_function()
+                    pass
             count -= 1
             sleep(0.5)
 

From d8995041c5f1d1b40892f0f746bb4a611049c4a7 Mon Sep 17 00:00:00 2001
From: "Strawder, Paul" <paul@coach-ai.com>
Date: Fri, 26 May 2023 14:29:12 +0200
Subject: [PATCH 16/21] bugfix: LLTJBD BMS ignore non ASCII letters for
 hardware version

---
 etc/dbus-serialbattery/bms/lltjbd.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py
index 4c091aff..2ff418c7 100644
--- a/etc/dbus-serialbattery/bms/lltjbd.py
+++ b/etc/dbus-serialbattery/bms/lltjbd.py
@@ -445,7 +445,7 @@ def read_hardware_data(self):
 
         self._product_name = unpack_from(
             ">" + str(len(hardware_data)) + "s", hardware_data
-        )[0].decode()
+        )[0].decode("ascii", errors="ignore")
         logger.debug(self._product_name)
         return True
 

From 97d2c708cb8cb2ae044303a05b07fffeac57862a Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Fri, 26 May 2023 15:03:08 +0200
Subject: [PATCH 17/21] updated readme

---
 README.md | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 063eaaa3..38f11e44 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,19 @@
 # dbus-serialbattery
 This is a driver for Venus OS devices (any GX device sold by Victron or a Raspberry Pi running the Venus OS image).
 
-The driver will communicate with a Battery Management System (BMS) that support serial communication (RS232, RS485 or TTL UART) and publish this data to the Venus OS system. The main purpose is to act as a Battery Monitor in your GX and supply State Of Charge (SOC) and other values to the inverter.
+The driver will communicate with a Battery Management System (BMS) that support serial (RS232, RS485 or TTL UART) and Bluetooth communication (see [BMS feature comparison](https://louisvdw.github.io/dbus-serialbattery/general/features#bms-feature-comparison) for details). The data is then published to the Venus OS system (dbus). The main purpose is to act as a Battery Monitor in your GX and supply State of Charge (SoC) and other values to the inverter/charger.
 
- * [BMS Types supported](https://louisvdw.github.io/dbus-serialbattery/general/supported-bms)
- * [FAQ](https://louisvdw.github.io/dbus-serialbattery/faq/)
- * [Features](https://louisvdw.github.io/dbus-serialbattery/general/features)
- * [How to install](https://louisvdw.github.io/dbus-serialbattery/general/install)
- * [How to troubleshoot](https://louisvdw.github.io/dbus-serialbattery/troubleshoot/)
+## Documentation
+
+* [Introduction](https://louisvdw.github.io/dbus-serialbattery/)
+* [Features](https://louisvdw.github.io/dbus-serialbattery/general/features)
+* [Supported BMS](https://louisvdw.github.io/dbus-serialbattery/general/supported-bms)
+* [How to install, update, disable, enable and uninstall](https://louisvdw.github.io/dbus-serialbattery/general/install)
+* [How to troubleshoot](https://louisvdw.github.io/dbus-serialbattery/troubleshoot/)
+* [FAQ](https://louisvdw.github.io/dbus-serialbattery/faq/)
 
 ### Supporting this project
-If you find this driver helpful please considder supporting this project. You can buy me a Ko-Fi or get in contact if you would like to donate hardware.
+If you find this driver helpful please consider supporting this project. You can buy me a Ko-Fi or get in contact, if you would like to donate hardware for development.
 
 ### Support [Louisvdw](https://github.com/Louisvdw)
 * Main developer
@@ -25,6 +28,7 @@ If you find this driver helpful please considder supporting this project. You ca
 
 [<img src="https://github.md0.eu/uploads/donate-button.svg" height="38">](https://www.paypal.com/donate/?hosted_button_id=3NEVZBDM5KABW)
 
+
 ### Developer Remarks
 To develop this project, install the requirements. This project makes use of velib_python which is pre-installed on
 Venus-OS Devices under `/opt/victronenergy/dbus-systemcalc-py/ext/velib_python`. To use the python files locally,

From cece2d100ef355184014663e453221f8e6164896 Mon Sep 17 00:00:00 2001
From: Bernd <6627385+transistorgit@users.noreply.github.com>
Date: Fri, 26 May 2023 15:35:29 +0200
Subject: [PATCH 18/21] Suppress daly read errors (#13)

---
 etc/dbus-serialbattery/bms/daly.py | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py
index b304b283..00873b8c 100644
--- a/etc/dbus-serialbattery/bms/daly.py
+++ b/etc/dbus-serialbattery/bms/daly.py
@@ -26,6 +26,7 @@ def __init__(self, port, baud, address):
         self.runtime = 0  # TROUBLESHOOTING for no reply errors
         self.trigger_force_disable_discharge = None
         self.trigger_force_disable_charge = None
+        self.cells_volts_data_lastreadbad = False
 
     # 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"
@@ -355,11 +356,20 @@ def read_cells_volts(self, ser):
             ser, self.command_cell_volts, sentences_to_receive=sentences_expected
         )
 
-        if cells_volts_data is False:
-            logger.debug(
-                "No or invalid data has been received in read_cells_volts()"
-            )  # just debug level, as there are DALY BMS that send broken packages occasionally
+        if cells_volts_data is False and self.cells_volts_data_lastreadbad is True:
+            # if this read out and the last one were bad, report error.
+            # (we don't report single errors, as current daly firmware sends corrupted cells volts data occassionally)
+            logger.warning(
+                "No or invalid data has been received repeatedly in read_cells_volts()"
+            )
             return False
+        elif cells_volts_data is False:
+            # memorize that this read was bad and bail out, ignoring it
+            self.cells_volts_data_lastreadbad = True
+            return True
+        else:
+            # this read was good, so reset error flag
+            self.cells_volts_data_lastreadbad = False
 
         frameCell = [0, 0, 0]
         lowMin = utils.MIN_CELL_VOLTAGE / 2
@@ -655,7 +665,7 @@ def request_data(self, ser, command, sentences_to_receive=1):
         for i in range(sentences_to_receive):
             next = self.read_sentence(ser, command)
             if not next:
-                logger.info(f"request_data: bad reply no. {i}")
+                logger.debug(f"request_data: bad reply no. {i}")
                 return False
             reply += next
         self.runtime = time() - time_start
@@ -697,9 +707,7 @@ def read_sentence(self, ser, expected_reply, timeout=0.5):
 
         chk = unpack_from(">B", reply, 12)[0]
         if sum(reply[:12]) & 0xFF != chk:
-            logger.warning(
-                f"read_sentence {bytes(expected_reply).hex()}: wrong checksum"
-            )
+            logger.debug(f"read_sentence {bytes(expected_reply).hex()}: wrong checksum")
             return False
 
         return reply[4:12]

From 4923329ca1bee68ead75c3cc1d63eff1095d01cc Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Fri, 26 May 2023 16:37:23 +0200
Subject: [PATCH 19/21] updated descriptions

---
 etc/dbus-serialbattery/bms/battery_template.py |  3 ++-
 etc/dbus-serialbattery/config.default.ini      | 13 ++++++++++---
 etc/dbus-serialbattery/dbus-serialbattery.py   |  2 +-
 etc/dbus-serialbattery/utils.py                | 10 +++++++---
 4 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/etc/dbus-serialbattery/bms/battery_template.py b/etc/dbus-serialbattery/bms/battery_template.py
index e6cbd6a6..e32e424b 100644
--- a/etc/dbus-serialbattery/bms/battery_template.py
+++ b/etc/dbus-serialbattery/bms/battery_template.py
@@ -27,7 +27,7 @@ def test_connection(self):
         result = False
         try:
             result = self.read_status_data()
-            # get first data to show in startup log
+            # get first data to show in startup log, only if result is true
             if result:
                 self.refresh_data()
         except Exception as err:
@@ -105,6 +105,7 @@ def read_serial_data_template(self, command):
             command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK
         )
         if data is False:
+            logger.error(">>> ERROR: No reply - returning")
             return False
 
         start, flag, command_ret, length = unpack_from("BBBB", data)
diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini
index 7631fc6d..e7c967ff 100644
--- a/etc/dbus-serialbattery/config.default.ini
+++ b/etc/dbus-serialbattery/config.default.ini
@@ -14,8 +14,11 @@ FLOAT_CELL_VOLTAGE = 3.375
 
 ; --------- Bluetooth BMS ---------
 ; Description: List the Bluetooth BMS here that you want to install
-; Example with 1 BMS: Jkbms_Ble C8:47:8C:00:00:00
-; Example with 3 BMS: Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22
+; -- Available Bluetooth BMS:
+; Jkbms_Ble, LltJbd_Ble
+; Example:
+;     1 BMS: Jkbms_Ble C8:47:8C:00:00:00
+;     3 BMS: Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22
 BLUETOOTH_BMS =
 
 ; --------- BMS disconnect behaviour ---------
@@ -181,7 +184,11 @@ TIME_TO_SOC_INC_FROM = False
 
 ; --------- Additional settings ---------
 ; Specify only one BMS type to load else leave empty to try to load all available
-; LltJbd, Ant, Daly, Daly, Jkbms, Lifepower, Renogy, Renogy, Ecs
+; -- Available BMS:
+; Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos
+; -- Available BMS, but disabled by default:
+; https://louisvdw.github.io/dbus-serialbattery/general/install#how-to-enable-a-disabled-bms
+; Ant, MNB, Sinowealth
 BMS_TYPE =
 
 ; Publish the config settings to the dbus path "/Info/Config/"
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index c3358ae3..4bca9b35 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -58,7 +58,7 @@
     if battery_type["bms"].__name__ == utils.BMS_TYPE or utils.BMS_TYPE == ""
 ]
 
-logger.info("")
+print("")
 logger.info("Starting dbus-serialbattery")
 
 
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index 759834b4..98ea5dca 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -35,7 +35,7 @@ def _get_list_from_config(
 # if not specified: baud = 9600
 
 # Constants - Need to dynamically get them in future
-DRIVER_VERSION = "1.0.20230525dev"
+DRIVER_VERSION = "1.0.20230526dev"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 
@@ -264,8 +264,12 @@ def _get_list_from_config(
 
 
 # --------- Additional settings ---------
-# Specify only one BMS type to load else leave empty to try to load all availabe
-# LltJbd, Ant, Daly, Daly, Jkbms, Lifepower, Renogy, Renogy, Ecs
+# Specify only one BMS type to load else leave empty to try to load all available
+# -- Available BMS:
+# Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos
+# -- Available BMS, but disabled by default:
+# https://louisvdw.github.io/dbus-serialbattery/general/install#how-to-enable-a-disabled-bms
+# Ant, MNB, Sinowealth
 BMS_TYPE = config["DEFAULT"]["BMS_TYPE"]
 
 # Publish the config settings to the dbus path "/Info/Config/"

From 262a200fd2641fcae0f04b30dc46f8af1993d441 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Fri, 26 May 2023 16:56:40 +0200
Subject: [PATCH 20/21] Changed logging level and give better feedback *
 Changed logging level to debug for unneeded messages * Give the user
 feedback, if a BMS is found or not. Useful, if only one BMS is tested.

---
 CHANGELOG.md                               |  3 +-
 etc/dbus-serialbattery/bms/daly.py         | 32 +++++++++++++---------
 etc/dbus-serialbattery/bms/ecs.py          | 23 +++++++++++++---
 etc/dbus-serialbattery/bms/heltecmodbus.py | 10 +++++--
 etc/dbus-serialbattery/bms/hlpdatabms4s.py |  7 +++++
 etc/dbus-serialbattery/bms/lifepower.py    |  2 +-
 etc/dbus-serialbattery/bms/lltjbd.py       |  7 ++---
 etc/dbus-serialbattery/bms/seplos.py       | 14 +++++++---
 8 files changed, 68 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 910781ed..d3c14b0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,7 @@
 * Changed: `reinstall-local.sh` to recreate `/data/conf/serial-starter.d`, if deleted by `disable.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository by @mr-manuel
 * Changed: Added QML to `restore-gui.sh` by @mr-manuel
 * Changed: Bash output by @mr-manuel
+* Changed: CVL calculation improvement. Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced or SoC is below threshold by @mr-manuel
 * Changed: Daly BMS - Fixed BMS alerts by @mr-manuel
 * Changed: Daly BMS - Improved driver stability by @transistorgit & @mr-manuel
 * Changed: Daly BMS - Reworked serial parser by @transistorgit
@@ -78,6 +79,7 @@
 * Changed: Default LINEAR_LIMITATION_ENABLE from False to True by @mr-manuel
 * Changed: Disabled ANT BMS by default https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel
 * Changed: Driver can now also start without serial adapter attached for Bluetooth BMS by @seidler2547
+* Changed: Feedback from BMS driver to know, if BMS is found or not 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
@@ -96,7 +98,6 @@
 * Changed: Moved Bluetooth part to `reinstall-local.sh` by @mr-manuel
 * Changed: Moved BMS scripts to subfolder by @mr-manuel
 * Changed: Removed all wildcard imports and fixed black lint errors by @mr-manuel
-* Changed: CVL calculation improvement. Removed cell voltage penalty. Replaced by automatic voltage calculation. Max voltage is kept until cells are balanced and reset when cells are inbalanced or SoC is below threshold by @mr-manuel
 * Changed: Renamed scripts for better reading #532 by @mr-manuel
 * Changed: Reworked and optimized installation scripts by @mr-manuel
 * Changed: Separate Time-To-Go and Time-To-SoC activation by @mr-manuel
diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py
index 00873b8c..87510c48 100644
--- a/etc/dbus-serialbattery/bms/daly.py
+++ b/etc/dbus-serialbattery/bms/daly.py
@@ -60,13 +60,19 @@ def test_connection(self):
         try:
             with open_serial_port(self.port, self.baud_rate) as ser:
                 result = self.read_status_data(ser)
-                self.read_soc_data(ser)
-                self.read_battery_code(ser)
+                # get first data to show in startup log, only if result is true
+                if result:
+                    self.read_soc_data(ser)
+                    self.read_battery_code(ser)
 
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
 
+        # give the user a feedback that no BMS was found
+        if not result:
+            logger.error(">>> ERROR: No reply - returning")
+
         return result
 
     def get_settings(self):
@@ -179,7 +185,7 @@ def read_status_data(self, ser):
         status_data = self.request_data(ser, self.command_status)
         # check if connection success
         if status_data is False:
-            logger.warning("No data received in read_status_data()")
+            logger.debug("No data received in read_status_data()")
             return False
 
         (
@@ -200,7 +206,7 @@ def read_status_data(self, ser):
             + " cells"
             + (" (" + self.production + ")" if self.production else "")
         )
-        logger.info(self.hardware_version)
+        logger.debug(self.hardware_version)
         return True
 
     def read_soc_data(self, ser):
@@ -359,7 +365,7 @@ def read_cells_volts(self, ser):
         if cells_volts_data is False and self.cells_volts_data_lastreadbad is True:
             # if this read out and the last one were bad, report error.
             # (we don't report single errors, as current daly firmware sends corrupted cells volts data occassionally)
-            logger.warning(
+            logger.debug(
                 "No or invalid data has been received repeatedly in read_cells_volts()"
             )
             return False
@@ -405,7 +411,7 @@ def read_cell_voltage_range_data(self, ser):
         minmax_data = self.request_data(ser, self.command_minmax_cell_volts)
         # check if connection success
         if minmax_data is False:
-            logger.warning("No data received in read_cell_voltage_range_data()")
+            logger.debug("No data received in read_cell_voltage_range_data()")
             return False
 
         (
@@ -472,7 +478,7 @@ def read_capacity(self, ser):
         capa_data = self.request_data(ser, self.command_rated_params)
         # check if connection success
         if capa_data is False:
-            logger.warning("No data received in read_capacity()")
+            logger.debug("No data received in read_capacity()")
             return False
 
         (capacity, cell_volt) = unpack_from(">LL", capa_data)
@@ -487,7 +493,7 @@ def read_production_date(self, ser):
         production = self.request_data(ser, self.command_batt_details)
         # check if connection success
         if production is False:
-            logger.warning("No data received in read_production_date()")
+            logger.debug("No data received in read_production_date()")
             return False
 
         (_, _, year, month, day) = unpack_from(">BBBBB", production)
@@ -499,7 +505,7 @@ def read_battery_code(self, ser):
         data = self.request_data(ser, self.command_batt_code, sentences_to_receive=5)
 
         if data is False:
-            logger.warning("No data received in read_battery_code()")
+            logger.debug("No data received in read_battery_code()")
             return False
 
         battery_code = ""
@@ -507,7 +513,7 @@ def read_battery_code(self, ser):
         for i in range(5):
             nr, part = unpack_from(">B7s", data, i * 8)
             if nr != i + 1:
-                logger.warning("bad battery code index")  # use string anyhow, just warn
+                logger.debug("bad battery code index")  # use string anyhow, just warn
             battery_code += part.decode("utf-8")
 
         if battery_code != "":
@@ -680,7 +686,7 @@ def read_sentence(self, ser, expected_reply, timeout=0.5):
 
         reply = ser.read_until(b"\xA5")
         if not reply or b"\xA5" not in reply:
-            logger.error(
+            logger.debug(
                 f"read_sentence {bytes(expected_reply).hex()}: no sentence start received"
             )
             return False
@@ -693,7 +699,7 @@ def read_sentence(self, ser, expected_reply, timeout=0.5):
             toread = ser.inWaiting()
             time_run = time() - time_start
             if time_run > timeout:
-                logger.warning(f"read_sentence {bytes(expected_reply).hex()}: timeout")
+                logger.debug(f"read_sentence {bytes(expected_reply).hex()}: timeout")
                 return False
 
         reply += ser.read(12)
@@ -702,7 +708,7 @@ def read_sentence(self, ser, expected_reply, timeout=0.5):
         # logger.info(f"reply: {bytes(reply).hex()}")  # debug
 
         if id != 1 or length != 8 or cmd != expected_reply[0]:
-            logger.error(f"read_sentence {bytes(expected_reply).hex()}: wrong header")
+            logger.debug(f"read_sentence {bytes(expected_reply).hex()}: wrong header")
             return False
 
         chk = unpack_from(">B", reply, 12)[0]
diff --git a/etc/dbus-serialbattery/bms/ecs.py b/etc/dbus-serialbattery/bms/ecs.py
index a50e6072..ddfdf901 100644
--- a/etc/dbus-serialbattery/bms/ecs.py
+++ b/etc/dbus-serialbattery/bms/ecs.py
@@ -32,6 +32,7 @@ def test_connection(self):
         # Return True if success, False for failure
 
         # Trying to find Green Meter ID
+        result = False
         try:
             mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS)
             mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN
@@ -44,14 +45,28 @@ def test_connection(self):
                 if tmpId == self.GREENMETER_ID_125A:
                     self.METER_SIZE = "125A"
 
+                # TODO
+                # has this to be true?
+                # if yes then self.get_settings() should only be called, if this is true
                 self.find_LiPro_cells()
 
-                # get first data to show in startup log
-                self.refresh_data()
+                result = self.get_settings()
+
+                # get first data to show in startup log, only if result is true
+                if result:
+                    self.refresh_data()
 
-                return self.get_settings()
         except IOError:
-            return False
+            result = False
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
+
+        # give the user a feedback that no BMS was found
+        if not result:
+            logger.error(">>> ERROR: No reply - returning")
+
+        return result
 
     def find_LiPro_cells(self):
         # test for LiPro cell devices
diff --git a/etc/dbus-serialbattery/bms/heltecmodbus.py b/etc/dbus-serialbattery/bms/heltecmodbus.py
index b6271bfb..e7f866a0 100644
--- a/etc/dbus-serialbattery/bms/heltecmodbus.py
+++ b/etc/dbus-serialbattery/bms/heltecmodbus.py
@@ -36,7 +36,7 @@ def test_connection(self):
         # The result or call should be unique to this BMS. Battery name or version, etc.
         # Return True if success, False for failure
         for self.address in utils.HELTEC_MODBUS_ADDR:
-            logger.info("Testing on slave address " + str(self.address))
+            logger.debug("Testing on slave address " + str(self.address))
             found = False
             if self.address not in locks:
                 locks[self.address] = threading.Lock()
@@ -64,7 +64,7 @@ def test_connection(self):
                         string = mbdev.read_string(7, 13)
                         time.sleep(SLPTIME)
                         found = True
-                        logger.info(
+                        logger.debug(
                             "found in try "
                             + str(n)
                             + "/"
@@ -77,7 +77,7 @@ def test_connection(self):
                             + string
                         )
                     except Exception as e:
-                        logger.warn(
+                        logger.debug(
                             "testing failed ("
                             + str(e)
                             + ") "
@@ -96,6 +96,10 @@ def test_connection(self):
                     self.type = "#" + str(self.address) + "_Heltec_Smart"
                     break
 
+        # give the user a feedback that no BMS was found
+        if not found:
+            logger.error(">>> ERROR: No reply - returning")
+
         return (
             found
             and self.read_status_data()
diff --git a/etc/dbus-serialbattery/bms/hlpdatabms4s.py b/etc/dbus-serialbattery/bms/hlpdatabms4s.py
index d4a350b1..7faf8b2c 100644
--- a/etc/dbus-serialbattery/bms/hlpdatabms4s.py
+++ b/etc/dbus-serialbattery/bms/hlpdatabms4s.py
@@ -24,6 +24,10 @@ def test_connection(self):
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
 
+        # give the user a feedback that no BMS was found
+        if not result:
+            logger.error(">>> ERROR: No reply - returning")
+
         return result
 
     def get_settings(self):
@@ -210,6 +214,9 @@ def read_serial_data2(command, port, baud, time, min_len):
         logger.error(e)
         return False
 
+    except Exception:
+        return False
+
 
 def read_serialport_data2(ser, command, time, min_len):
     try:
diff --git a/etc/dbus-serialbattery/bms/lifepower.py b/etc/dbus-serialbattery/bms/lifepower.py
index 5780d58c..b46421fb 100644
--- a/etc/dbus-serialbattery/bms/lifepower.py
+++ b/etc/dbus-serialbattery/bms/lifepower.py
@@ -159,7 +159,7 @@ def read_serial_data_eg4(self, command):
             self.LENGTH_FIXED,
         )
         if data is False:
-            logger.error(">>> ERROR: Incorrect Data")
+            logger.debug(">>> ERROR: Incorrect Data")
             return False
 
         # 0x0D always terminates the response
diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py
index 2ff418c7..63de584b 100644
--- a/etc/dbus-serialbattery/bms/lltjbd.py
+++ b/etc/dbus-serialbattery/bms/lltjbd.py
@@ -234,11 +234,10 @@ def test_connection(self):
         result = False
         try:
             result = self.get_settings()
+            # get first data to show in startup log, only if result is true
             if result:
-                result = result and self.read_hardware_data()
-            # get first data to show in startup log
-            if result:
-                result = result and self.refresh_data()
+                self.read_hardware_data()
+                self.refresh_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
diff --git a/etc/dbus-serialbattery/bms/seplos.py b/etc/dbus-serialbattery/bms/seplos.py
index 083d6bc4..b7c9a2e1 100644
--- a/etc/dbus-serialbattery/bms/seplos.py
+++ b/etc/dbus-serialbattery/bms/seplos.py
@@ -79,12 +79,18 @@ 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:
-            return self.read_status_data()
+            result = self.read_status_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
-            return False
+            result = False
+
+        # give the user a feedback that no BMS was found
+        if not result:
+            logger.error(">>> ERROR: No reply - returning")
+
+        return result
 
     def get_settings(self):
         # After successful connection get_settings will be called to set up the battery.
@@ -254,7 +260,7 @@ def is_valid_frame(data: bytes) -> bool:
         * not checked: lchksum
         """
         if len(data) < 18:
-            logger.warning("short read, data={}".format(data))
+            logger.debug("short read, data={}".format(data))
             return False
 
         chksum = Seplos.get_checksum(data[1:-5])

From 8f0d676b5e4f911cbda037dcf2d5f2d2f1cafee5 Mon Sep 17 00:00:00 2001
From: Manuel <mr-manuel@outlook.it>
Date: Fri, 26 May 2023 17:08:25 +0200
Subject: [PATCH 21/21] small fixes

---
 etc/dbus-serialbattery/install-qml.sh     | 2 +-
 etc/dbus-serialbattery/reinstall-local.sh | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/etc/dbus-serialbattery/install-qml.sh b/etc/dbus-serialbattery/install-qml.sh
index 6c155b51..287aac83 100755
--- a/etc/dbus-serialbattery/install-qml.sh
+++ b/etc/dbus-serialbattery/install-qml.sh
@@ -7,7 +7,7 @@
 # https://github.com/kwindrem/SetupHelper/blob/ebaa65fcf23e2bea6797f99c1c41174143c1153c/updateFileSets#L56-L81
 function versionStringToNumber ()
 {
-    local local p4="" ; local p5="" ; local p5=""
+    local p4="" ; local p5="" ; local p5=""
     local major=""; local minor=""
 
 	# first character should be 'v' so first awk parameter will be empty and is not prited into the read command
diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh
index bdc0030a..a518a100 100755
--- a/etc/dbus-serialbattery/reinstall-local.sh
+++ b/etc/dbus-serialbattery/reinstall-local.sh
@@ -13,7 +13,7 @@ versionRequired="v2.90"
 # https://github.com/kwindrem/SetupHelper/blob/ebaa65fcf23e2bea6797f99c1c41174143c1153c/updateFileSets#L56-L81
 function versionStringToNumber ()
 {
-    local local p4="" ; local p5="" ; local p5=""
+    local p4="" ; local p5="" ; local p5=""
     local major=""; local minor=""
 
 	# first character should be 'v' so first awk parameter will be empty and is not prited into the read command