diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3c14b0b..30e52815 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,67 @@
 # Changelog
 
-## v1.0.0
+## Breaking changes
 
-### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` gets lost. The changes in the `config.ini` will persists future updates.
+* Driver version greater or equal to `v1.0.20230629beta` and smaller or equal to `v1.0.20230926beta`:
+
+  With `v1.0.20230927beta` the following values changed names:
+  * `BULK_CELL_VOLTAGE` -> `SOC_RESET_VOLTAGE`
+  * `BULK_AFTER_DAYS` -> `SOC_RESET_AFTER_DAYS`
+
+## v1.0.x
+
+* Added: Bluetooth: Show signal strength of BMS in log by @mr-manuel
+* Added: Configure logging level in `config.ini` by @mr-manuel
+* Added: Create unique identifier, if not provided from BMS by @mr-manuel
+* Added: Current average of the last 5 minutes by @mr-manuel
+* Added: Daly BMS - Auto reset SoC when changing to float (can be turned off in the config file) by @transistorgit
+* Added: Daly BMS connect via CAN (experimental, some limits apply) with https://github.com/Louisvdw/dbus-serialbattery/pull/169 by @SamuelBrucksch and @mr-manuel
+* Added: Exclude a device from beeing used by the dbus-serialbattery driver by @mr-manuel
+* Added: Implement callback function for update by @seidler2547
+* Added: JKBMS BLE - Automatic SOC reset with https://github.com/Louisvdw/dbus-serialbattery/pull/736 by @ArendsM
+* Added: JKBMS BLE - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel
+* Added: JKBMS BMS connect via CAN (experimental, some limits apply) by @IrisCrimson and @mr-manuel
+* Added: LLT/JBD BMS - Discharge / Charge Mosfet and disable / enable balancer switching over remote console/GUI with https://github.com/Louisvdw/dbus-serialbattery/pull/761 by @idstein
+* Added: LLT/JBD BMS - Show balancer state in GUI under the IO page with https://github.com/Louisvdw/dbus-serialbattery/pull/763 by @idstein
+* Added: Load to SOC reset voltage every x days to reset the SoC to 100% for some BMS by @mr-manuel
+* Added: Save custom name and make it restart persistant by @mr-manuel
+* Added: Temperature names to dbus and mqtt by @mr-manuel
+* Added: Use current average of the last 300 cycles for time to go and time to SoC calculation by @mr-manuel
+* Added: Validate current, voltage, capacity and SoC for all BMS. This prevents that a device, which is no BMS, is detected as BMS. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/479 by @mr-manuel
+* Changed: `VOLTAGE_DROP` now behaves differently. Before it reduced the voltage for the check, now the voltage for the charger is increased in order to get the target voltage on the BMS by @mr-manuel
+* Changed: Daly BMS - Fix readsentence by @transistorgit
+* Changed: Daly BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/837 by @mr-manuel
+* Changed: Enable BMS that are disabled by default by specifying it in the config file. No more need to edit scripts by @mr-manuel
+* Changed: Fixed Building wheel for dbus-fast won't finish on weak systems https://github.com/Louisvdw/dbus-serialbattery/issues/785 by @mr-manuel
+* Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel
+* Changed: Fixed meaningless Time to Go values by @transistorgit
+* Changed: Fixed typo in `config.ini` sample by @hoschult
+* Changed: For BMS_TYPE now multiple BMS can be specified by @mr-manuel
+* Changed: Improved battery error handling on connection loss by @mr-manuel
+* Changed: Improved battery voltage handling in linear absorption mode by @ogurevich
+* Changed: Improved driver disable script by @md-manuel
+* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel
+* Changed: JKBMS - Driver do not start if manufacturer date in BMS is empty https://github.com/Louisvdw/dbus-serialbattery/issues/823 by @mr-manuel
+* Changed: JKBMS_BLE BMS - Fixed MOSFET Temperature for HW 11 by @jensbehrens & @mr-manuel
+* Changed: JKBMS_BLE BMS - Fixed recognition of newer models where no data is shown by @mr-manuel
+* Changed: JKBMS_BLE BMS - Improved driver by @seidler2547 & @mr-manuel
+* Changed: LLT/JBD BMS - Fix cycle capacity with https://github.com/Louisvdw/dbus-serialbattery/pull/762 by @idstein
+* Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/730 by @mr-manuel
+* Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/769 by @mr-manuel
+* Changed: LLT/JBD BMS - Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/778 with https://github.com/Louisvdw/dbus-serialbattery/pull/798 by @idstein
+* Changed: LLT/JBD BMS - Improved error handling and automatical driver restart in case of error. Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/777 by @mr-manuel
+* Changed: LLT/JBD BMS - SOC different in Xiaoxiang app and dbus-serialbattery with https://github.com/Louisvdw/dbus-serialbattery/pull/760 by @idstein
+* Changed: Make CCL and DCL limiting messages more clear by @mr-manuel
+* Changed: Reduce the big inrush current if the CVL jumps from Bulk/Absorbtion to Float https://github.com/Louisvdw/dbus-serialbattery/issues/659 by @Rikkert-RS & @ogurevich
+* Changed: Sinowealth BMS - Fix not loading https://github.com/Louisvdw/dbus-serialbattery/issues/702 by @mr-manuel
+* Changed: Time-to-Go and Time-to-SoC use the current average of the last 5 minutes for calculation by @mr-manuel
+* Changed: Time-to-SoC calculate only positive points by @mr-manuel
+* Removed: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel
+
+
+## v1.0.20230531
+
+### ATTENTION: Breaking changes! The config is now done in the `config.ini`. All values from the `utils.py` get lost. The changes in the `config.ini` will persists future updates.
 
 * Added: `self.unique_identifier` to the battery class. Used to identify a BMS when multiple BMS are connected - planned for future use by @mr-manuel
 * Added: Alert is triggered, when BMS communication is lost by @mr-manuel
diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py
index eb5b2e2d..6a229e34 100644
--- a/etc/dbus-serialbattery/battery.py
+++ b/etc/dbus-serialbattery/battery.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-from typing import Union, Tuple, List
+from typing import Union, Tuple, List, Callable
 
 from utils import logger
 import utils
@@ -7,6 +7,8 @@
 import math
 from time import time
 from abc import ABC, abstractmethod
+import re
+import sys
 
 
 class Protection(object):
@@ -69,18 +71,20 @@ def __init__(self, port, baud, address):
         self.max_battery_discharge_current = None
         self.has_settings = 0
 
-        self.init_values()
-
-        # used to identify a BMS when multiple BMS are connected - planned for future use
-        self.unique_identifier = None
-
         # fetched from the BMS from a field where the user can input a custom string
         # only if available
         self.custom_field = None
 
+        self.init_values()
+
     def init_values(self):
+        """
+        Used to reset values, if battery unexpectly disconnects
+        """
         self.voltage = None
         self.current = None
+        self.current_avg = None
+        self.current_avg_lst = []
         self.capacity_remain = None
         self.capacity = None
         self.cycles = None
@@ -102,14 +106,22 @@ def init_values(self):
         self.cells: List[Cell] = []
         self.control_charging = None
         self.control_voltage = None
+        self.soc_reset_requested = False
+        self.soc_reset_last_reached = 0
+        self.soc_reset_battery_voltage = None
+        self.max_battery_voltage = None
+        self.min_battery_voltage = None
         self.allow_max_voltage = True
+        self.max_voltage_start_time = None
+        self.transition_start_time = None
+        self.control_voltage_at_transition_start = None
         self.charge_mode = None
+        self.charge_mode_debug = ""
         self.charge_limitation = None
         self.discharge_limitation = None
         self.linear_cvl_last_set = 0
         self.linear_ccl_last_set = 0
         self.linear_dcl_last_set = 0
-        self.max_voltage_start_time = None
         self.control_current = None
         self.control_previous_total = None
         self.control_previous_max = None
@@ -129,11 +141,35 @@ def test_connection(self) -> bool:
         # return false when failed, true if successful
         return False
 
+    def unique_identifier(self) -> str:
+        """
+        Used to identify a BMS when multiple BMS are connected
+        If not provided by the BMS/driver then the hardware version and capacity is used,
+        since it can be changed by small amounts to make a battery unique.
+        On +/- 5 Ah you can identify 11 batteries
+        """
+        string = (
+            "".join(filter(str.isalnum, str(self.hardware_version))) + "_"
+            if self.hardware_version is not None and self.hardware_version != ""
+            else ""
+        )
+        string += str(self.capacity) + "Ah"
+        return string
+
     def connection_name(self) -> str:
         return "Serial " + self.port
 
     def custom_name(self) -> str:
-        return "SerialBattery(" + self.type + ")"
+        """
+        Check if the custom name is present in the config file, else return default name
+        """
+        if len(utils.CUSTOM_BATTERY_NAMES) > 0:
+            for name in utils.CUSTOM_BATTERY_NAMES:
+                tmp = name.split(":")
+                if tmp[0].strip() == self.port:
+                    return tmp[1].strip()
+        else:
+            return "SerialBattery(" + self.type + ")"
 
     def product_name(self) -> str:
         return "SerialBattery(" + self.type + ")"
@@ -150,6 +186,17 @@ def get_settings(self) -> bool:
         """
         return False
 
+    def use_callback(self, callback: Callable) -> bool:
+        """
+        Each driver may override this function to indicate whether it is
+        able to provide value updates on its own.
+
+        :return: false when battery cannot provide updates by itself and will be polled
+                 every poll_interval milliseconds for new values
+                 true if callable should be used for updates as they arrive from the battery
+        """
+        return False
+
     @abstractmethod
     def refresh_data(self) -> bool:
         """
@@ -185,6 +232,7 @@ def manage_charge_voltage(self) -> None:
         manages the charge voltage by setting self.control_voltage
         :return: None
         """
+        self.prepare_voltage_management()
         if utils.CVCM_ENABLE:
             if utils.LINEAR_LIMITATION_ENABLE:
                 self.manage_charge_voltage_linear()
@@ -192,9 +240,47 @@ def manage_charge_voltage(self) -> None:
                 self.manage_charge_voltage_step()
         # on CVCM_ENABLE = False apply max voltage
         else:
-            self.control_voltage = round((utils.MAX_CELL_VOLTAGE * self.cell_count), 3)
+            self.control_voltage = round(self.max_battery_voltage, 3)
             self.charge_mode = "Keep always max voltage"
 
+    def prepare_voltage_management(self) -> None:
+        soc_reset_last_reached_days_ago = (
+            0
+            if self.soc_reset_last_reached == 0
+            else (((int(time()) - self.soc_reset_last_reached) / 60 / 60 / 24))
+        )
+        # set soc_reset_requested to True, if the days are over
+        # it gets set to False once the bulk voltage was reached once
+        if (
+            utils.SOC_RESET_AFTER_DAYS is not False
+            and self.soc_reset_requested is False
+            and self.allow_max_voltage
+            and (
+                self.soc_reset_last_reached == 0
+                or utils.SOC_RESET_AFTER_DAYS < soc_reset_last_reached_days_ago
+            )
+        ):
+            """
+            logger.info(
+                f"set soc_reset_requested to True: first time (0) or {utils.SOC_RESET_AFTER_DAYS}"
+                + f" < {round(soc_reset_last_reached_days_ago, 2)}"
+            )
+            """
+            self.soc_reset_requested = True
+
+        self.soc_reset_battery_voltage = round(
+            utils.SOC_RESET_VOLTAGE * self.cell_count, 2
+        )
+
+        if self.soc_reset_requested:
+            self.max_battery_voltage = self.soc_reset_battery_voltage
+        else:
+            self.max_battery_voltage = round(
+                utils.MAX_CELL_VOLTAGE * self.cell_count, 2
+            )
+
+        self.min_battery_voltage = round(utils.MIN_CELL_VOLTAGE * self.cell_count, 2)
+
     def manage_charge_voltage_linear(self) -> None:
         """
         manages the charge voltage using linear interpolation by setting self.control_voltage
@@ -204,99 +290,148 @@ def manage_charge_voltage_linear(self) -> None:
         voltageSum = 0
         penaltySum = 0
         tDiff = 0
+        current_time = int(time())
+
+        # meassurment and variation tolerance in volts
+        measurementToleranceVariation = 0.5
 
         try:
-            if utils.CVCM_ENABLE:
-                # calculate battery sum
-                for i in range(self.cell_count):
-                    voltage = self.get_cell_voltage(i)
-                    if voltage:
-                        voltageSum += voltage
-
-                        # calculate penalty sum to prevent single cell overcharge by using current cell voltage
-                        if voltage > utils.MAX_CELL_VOLTAGE:
-                            # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
-                            foundHighCellVoltage = True
-                            penaltySum += voltage - utils.MAX_CELL_VOLTAGE - 0.010
-
-                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
+            # calculate battery sum and check for cell overvoltage
+            for i in range(self.cell_count):
+                voltage = self.get_cell_voltage(i)
+                if voltage:
+                    voltageSum += voltage
+
+                    # calculate penalty sum to prevent single cell overcharge by using current cell voltage
                     if (
-                        (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_battery_voltage != self.soc_reset_battery_voltage
+                        and voltage > utils.MAX_CELL_VOLTAGE
                     ):
-                        self.max_voltage_start_time = time()
-
-                    # allow max voltage again, if cells are unbalanced or SoC threshold is reached
+                        # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
+                        foundHighCellVoltage = True
+                        penaltySum += voltage - utils.MAX_CELL_VOLTAGE
                     elif (
-                        utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
-                        or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
-                    ) and not self.allow_max_voltage:
-                        self.allow_max_voltage = True
+                        self.max_battery_voltage == self.soc_reset_battery_voltage
+                        and voltage > utils.SOC_RESET_VOLTAGE
+                    ):
+                        # foundHighCellVoltage: reset to False is not needed, since it is recalculated every second
+                        foundHighCellVoltage = True
+                        penaltySum += voltage - utils.SOC_RESET_VOLTAGE
+
+            voltageDiff = self.get_max_cell_voltage() - self.get_min_cell_voltage()
+
+            if self.max_voltage_start_time is None:
+                # start timer, if max voltage is reached and cells are balanced
+                if (
+                    self.max_battery_voltage <= voltageSum
+                    and voltageDiff <= utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL
+                    and self.allow_max_voltage
+                ):
+                    self.max_voltage_start_time = current_time
+
+                # allow max voltage again, if cells are unbalanced or SoC threshold is reached
+                elif (
+                    utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
+                    or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
+                ) and not self.allow_max_voltage:
+                    self.allow_max_voltage = True
                 else:
-                    tDiff = time() - self.max_voltage_start_time
-                    # if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
-                    # keep max voltage for 300 more seconds
-                    if 300 < tDiff:
-                        self.allow_max_voltage = False
-                        self.max_voltage_start_time = None
+                    pass
+
+            else:
+                tDiff = current_time - self.max_voltage_start_time
+                # keep max voltage for MAX_VOLTAGE_TIME_SEC more seconds
+                if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
+                    self.allow_max_voltage = False
+                    self.max_voltage_start_time = None
+                    if self.soc <= utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT:
+                        # write to log, that reset to float was not possible
+                        logger.error(
+                            f"Could not change to float voltage. Battery SoC ({self.soc}%) is lower"
+                            + f" than SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT ({utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT}%)."
+                            + " Please reset SoC manually or lower the SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT in the"
+                            + ' "config.ini".'
+                        )
+
+                # we don't forget to reset max_voltage_start_time wenn we going to bulk(dynamic) mode
+                # regardless of whether we were in absorption mode or not
+                if (
+                    voltageSum
+                    < self.max_battery_voltage - measurementToleranceVariation
+                ):
+                    self.max_voltage_start_time = None
 
             # INFO: battery will only switch to Absorption, if all cells are balanced.
             #       Reach MAX_CELL_VOLTAGE * cell count if they are all balanced.
             if foundHighCellVoltage and self.allow_max_voltage:
-                # set CVL only once every LINEAR_RECALCULATION_EVERY seconds
-                if (
-                    int(time()) - self.linear_cvl_last_set
-                    >= utils.LINEAR_RECALCULATION_EVERY
-                ):
-                    self.linear_cvl_last_set = int(time())
-
-                    # Keep penalty above min battery voltage and below max battery voltage
-                    self.control_voltage = round(
-                        min(
-                            max(
-                                voltageSum - penaltySum,
-                                utils.MIN_CELL_VOLTAGE * self.cell_count,
-                            ),
-                            utils.MAX_CELL_VOLTAGE * self.cell_count,
+                # Keep penalty above min battery voltage and below max battery voltage
+                control_voltage = round(
+                    min(
+                        max(
+                            voltageSum - penaltySum,
+                            self.min_battery_voltage,
                         ),
-                        3,
-                    )
+                        self.max_battery_voltage,
+                    ),
+                    3,
+                )
+                self.set_cvl_linear(control_voltage)
 
                 self.charge_mode = (
                     "Bulk dynamic"
-                    # + " (vS: "
-                    # + str(round(voltageSum, 2))
-                    # + " - pS: "
-                    # + str(round(penaltySum, 2))
-                    # + ")"
                     if self.max_voltage_start_time is None
                     else "Absorption dynamic"
-                    # + "(vS: "
-                    # + str(round(voltageSum, 2))
-                    # + " - pS: "
-                    # + str(round(penaltySum, 2))
-                    # + ")"
                 )
 
+                if self.max_battery_voltage == self.soc_reset_battery_voltage:
+                    self.charge_mode += " & SoC Reset"
+
             elif self.allow_max_voltage:
-                self.control_voltage = round(
-                    (utils.MAX_CELL_VOLTAGE * self.cell_count), 3
-                )
+                self.control_voltage = round(self.max_battery_voltage, 3)
                 self.charge_mode = (
                     "Bulk" if self.max_voltage_start_time is None else "Absorption"
                 )
 
+                if self.max_battery_voltage == self.soc_reset_battery_voltage:
+                    self.charge_mode += " & SoC Reset"
+
             else:
-                self.control_voltage = round(
-                    (utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3
-                )
-                self.charge_mode = "Float"
+                floatVoltage = round((utils.FLOAT_CELL_VOLTAGE * self.cell_count), 3)
+                chargeMode = "Float"
+                # reset bulk when going into float
+                if self.soc_reset_requested:
+                    # logger.info("set soc_reset_requested to False")
+                    self.soc_reset_requested = False
+                    # IDEA: Save "soc_reset_last_reached" in the dbus path com.victronenergy.settings
+                    # to make it restart persistent
+                    self.soc_reset_last_reached = current_time
+                if self.control_voltage:
+                    # check if battery changed from bulk/absoprtion to float
+                    if not self.charge_mode.startswith("Float"):
+                        self.transition_start_time = current_time
+                        self.initial_control_voltage = self.control_voltage
+                        chargeMode = "Float Transition"
+                        # Assume battery SOC ist 100% at this stage
+                        self.trigger_soc_reset()
+                    elif self.charge_mode.startswith("Float Transition"):
+                        elapsed_time = current_time - self.transition_start_time
+                        # Voltage reduction per second
+                        VOLTAGE_REDUCTION_PER_SECOND = 0.01 / 10
+                        voltage_reduction = min(
+                            VOLTAGE_REDUCTION_PER_SECOND * elapsed_time,
+                            self.initial_control_voltage - floatVoltage,
+                        )
+                        self.set_cvl_linear(
+                            self.initial_control_voltage - voltage_reduction
+                        )
+                        if self.control_voltage <= floatVoltage:
+                            self.control_voltage = floatVoltage
+                            chargeMode = "Float"
+                        else:
+                            chargeMode = "Float Transition"
+                else:
+                    self.control_voltage = floatVoltage
+                self.charge_mode = chargeMode
 
             if (
                 self.allow_max_voltage
@@ -307,10 +442,63 @@ def manage_charge_voltage_linear(self) -> None:
 
             self.charge_mode += " (Linear Mode)"
 
+            # uncomment for enabling debugging infos in GUI
+            """
+            self.charge_mode_debug = (
+                f"max_battery_voltage: {round(self.max_battery_voltage, 2)}V"
+            )
+            self.charge_mode_debug += (
+                f" - VOLTAGE_DROP: {round(utils.VOLTAGE_DROP, 2)}V"
+            )
+            self.charge_mode_debug += f"\nvoltageSum: {round(voltageSum, 2)}V"
+            self.charge_mode_debug += f" • voltageDiff: {round(voltageDiff, 3)}V"
+            self.charge_mode_debug += (
+                f"\ncontrol_voltage: {round(self.control_voltage, 2)}V"
+            )
+            self.charge_mode_debug += f" • penaltySum: {round(penaltySum, 3)}V"
+            self.charge_mode_debug += f"\ntDiff: {tDiff}/{utils.MAX_VOLTAGE_TIME_SEC}"
+            self.charge_mode_debug += f" • SoC: {self.soc}%"
+            self.charge_mode_debug += (
+                f" • Reset SoC: {utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT}%"
+            )
+            self.charge_mode_debug += f"\nallow_max_voltage: {self.allow_max_voltage}"
+            self.charge_mode_debug += (
+                f"\nmax_voltage_start_time: {self.max_voltage_start_time}"
+            )
+            self.charge_mode_debug += f"\ncurrent_time: {current_time}"
+            self.charge_mode_debug += (
+                f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}"
+            )
+            soc_reset_days_ago = round(
+                (current_time - self.soc_reset_last_reached) / 60 / 60 / 24, 2
+            )
+            soc_reset_in_days = round(utils.SOC_RESET_AFTER_DAYS - soc_reset_days_ago, 2)
+            self.charge_mode_debug += "\nsoc_reset_last_reached: " + str(
+                "Never"
+                if self.soc_reset_last_reached == 0
+                else str(soc_reset_days_ago)
+                + " days ago - next in "
+                + str(soc_reset_in_days)
+                + "days"
+            )
+            # """
+
         except TypeError:
             self.control_voltage = None
             self.charge_mode = "--"
 
+    def set_cvl_linear(self, control_voltage) -> bool:
+        """
+        set CVL only once every LINEAR_RECALCULATION_EVERY seconds
+        :return: bool
+        """
+        current_time = int(time())
+        if utils.LINEAR_RECALCULATION_EVERY <= current_time - self.linear_cvl_last_set:
+            self.control_voltage = control_voltage
+            self.linear_cvl_last_set = current_time
+            return True
+        return False
+
     def manage_charge_voltage_step(self) -> None:
         """
         manages the charge voltage using a step function by setting self.control_voltage
@@ -318,54 +506,64 @@ def manage_charge_voltage_step(self) -> None:
         """
         voltageSum = 0
         tDiff = 0
+        current_time = int(time())
 
         try:
-            if utils.CVCM_ENABLE:
-                # calculate battery sum
-                for i in range(self.cell_count):
-                    voltage = self.get_cell_voltage(i)
-                    if voltage:
-                        voltageSum += voltage
-
-                if self.max_voltage_start_time is None:
-                    # check if max voltage is reached and start timer to keep max voltage
-                    if (
-                        utils.MAX_CELL_VOLTAGE * self.cell_count
-                    ) - utils.VOLTAGE_DROP <= voltageSum and self.allow_max_voltage:
-                        # example 2
-                        self.max_voltage_start_time = time()
+            # calculate battery sum
+            for i in range(self.cell_count):
+                voltage = self.get_cell_voltage(i)
+                if voltage:
+                    voltageSum += voltage
+
+            if self.max_voltage_start_time is None:
+                # check if max voltage is reached and start timer to keep max voltage
+                if self.max_battery_voltage <= voltageSum and self.allow_max_voltage:
+                    # example 2
+                    self.max_voltage_start_time = current_time
+
+                # check if reset soc is greater than battery soc
+                # this prevents flapping between max and float voltage
+                elif (
+                    utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
+                    and not self.allow_max_voltage
+                ):
+                    self.allow_max_voltage = True
 
-                    # check if reset soc is greater than battery soc
-                    # this prevents flapping between max and float voltage
-                    elif (
-                        utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
-                        and not self.allow_max_voltage
-                    ):
-                        self.allow_max_voltage = True
+                # do nothing
+                else:
+                    pass
 
-                    # do nothing
-                    else:
-                        pass
+            # timer started
+            else:
+                tDiff = current_time - self.max_voltage_start_time
+                if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
+                    self.allow_max_voltage = False
+                    self.max_voltage_start_time = None
 
-                # timer started
                 else:
-                    tDiff = time() - self.max_voltage_start_time
-                    if utils.MAX_VOLTAGE_TIME_SEC < tDiff:
-                        self.allow_max_voltage = False
-                        self.max_voltage_start_time = None
-
-                    else:
-                        pass
+                    pass
 
             if self.allow_max_voltage:
-                self.control_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
+                self.control_voltage = self.max_battery_voltage
                 self.charge_mode = (
                     "Bulk" if self.max_voltage_start_time is None else "Absorption"
                 )
 
+                if self.max_battery_voltage == self.soc_reset_battery_voltage:
+                    self.charge_mode += " & SoC Reset"
+
             else:
+                # check if battery changed from bulk/absoprtion to float
+                if not self.charge_mode.startswith("Float"):
+                    # Assume battery SOC ist 100% at this stage
+                    self.trigger_soc_reset()
                 self.control_voltage = utils.FLOAT_CELL_VOLTAGE * self.cell_count
                 self.charge_mode = "Float"
+                # reset bulk when going into float
+                if self.soc_reset_requested:
+                    # logger.info("set soc_reset_requested to False")
+                    self.soc_reset_requested = False
+                    self.soc_reset_last_reached = current_time
 
             self.charge_mode += " (Step Mode)"
 
@@ -375,17 +573,24 @@ def manage_charge_voltage_step(self) -> None:
 
     def manage_charge_current(self) -> None:
         # Manage Charge Current Limitations
-        charge_limits = {utils.MAX_BATTERY_CHARGE_CURRENT: "Config Limit"}
+        charge_limits = {utils.MAX_BATTERY_CHARGE_CURRENT: "Max Battery Charge Current"}
 
-        # if values are not the same, then the limit was read also from the BMS
-        if utils.MAX_BATTERY_CHARGE_CURRENT != self.max_battery_charge_current:
-            charge_limits.update({self.max_battery_charge_current: "BMS Limit"})
+        # if BMS limit is lower then config limit and therefore the values are not the same,
+        # then the limit was also read from the BMS
+        if utils.MAX_BATTERY_CHARGE_CURRENT > self.max_battery_charge_current:
+            charge_limits.update({self.max_battery_charge_current: "BMS Settings"})
 
         if utils.CCCM_CV_ENABLE:
             tmp = self.calcMaxChargeCurrentReferringToCellVoltage()
             if self.max_battery_charge_current != tmp:
                 if tmp in charge_limits:
-                    charge_limits.update({tmp: charge_limits[tmp] + ", Cell Voltage"})
+                    # do not add string, if global limitation is applied
+                    if charge_limits[tmp] != "Max Battery Charge Current":
+                        charge_limits.update(
+                            {tmp: charge_limits[tmp] + ", Cell Voltage"}
+                        )
+                    else:
+                        pass
                 else:
                     charge_limits.update({tmp: "Cell Voltage"})
 
@@ -393,7 +598,11 @@ def manage_charge_current(self) -> None:
             tmp = self.calcMaxChargeCurrentReferringToTemperature()
             if self.max_battery_charge_current != tmp:
                 if tmp in charge_limits:
-                    charge_limits.update({tmp: charge_limits[tmp] + ", Temp"})
+                    # do not add string, if global limitation is applied
+                    if charge_limits[tmp] != "Max Battery Charge Current":
+                        charge_limits.update({tmp: charge_limits[tmp] + ", Temp"})
+                    else:
+                        pass
                 else:
                     charge_limits.update({tmp: "Temp"})
 
@@ -401,7 +610,11 @@ def manage_charge_current(self) -> None:
             tmp = self.calcMaxChargeCurrentReferringToSoc()
             if self.max_battery_charge_current != tmp:
                 if tmp in charge_limits:
-                    charge_limits.update({tmp: charge_limits[tmp] + ", SoC"})
+                    # do not add string, if global limitation is applied
+                    if charge_limits[tmp] != "Max Battery Charge Current":
+                        charge_limits.update({tmp: charge_limits[tmp] + ", SoC"})
+                    else:
+                        pass
                 else:
                     charge_limits.update({tmp: "SoC"})
 
@@ -439,19 +652,28 @@ def manage_charge_current(self) -> None:
         #####
 
         # Manage Discharge Current Limitations
-        discharge_limits = {utils.MAX_BATTERY_DISCHARGE_CURRENT: "Config Limit"}
-
-        # if values are not the same, then the limit was read also from the BMS
-        if utils.MAX_BATTERY_DISCHARGE_CURRENT != self.max_battery_discharge_current:
-            discharge_limits.update({self.max_battery_discharge_current: "BMS Limit"})
+        discharge_limits = {
+            utils.MAX_BATTERY_DISCHARGE_CURRENT: "Max Battery Discharge Current"
+        }
+
+        # if BMS limit is lower then config limit and therefore the values are not the same,
+        # then the limit was also read from the BMS
+        if utils.MAX_BATTERY_DISCHARGE_CURRENT > self.max_battery_discharge_current:
+            discharge_limits.update(
+                {self.max_battery_discharge_current: "BMS Settings"}
+            )
 
         if utils.DCCM_CV_ENABLE:
             tmp = self.calcMaxDischargeCurrentReferringToCellVoltage()
             if self.max_battery_discharge_current != tmp:
                 if tmp in discharge_limits:
-                    discharge_limits.update(
-                        {tmp: discharge_limits[tmp] + ", Cell Voltage"}
-                    )
+                    # do not add string, if global limitation is applied
+                    if discharge_limits[tmp] != "Max Battery Discharge Current":
+                        discharge_limits.update(
+                            {tmp: discharge_limits[tmp] + ", Cell Voltage"}
+                        )
+                    else:
+                        pass
                 else:
                     discharge_limits.update({tmp: "Cell Voltage"})
 
@@ -459,7 +681,11 @@ def manage_charge_current(self) -> None:
             tmp = self.calcMaxDischargeCurrentReferringToTemperature()
             if self.max_battery_discharge_current != tmp:
                 if tmp in discharge_limits:
-                    discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"})
+                    # do not add string, if global limitation is applied
+                    if discharge_limits[tmp] != "Max Battery Discharge Current":
+                        discharge_limits.update({tmp: discharge_limits[tmp] + ", Temp"})
+                    else:
+                        pass
                 else:
                     discharge_limits.update({tmp: "Temp"})
 
@@ -467,7 +693,11 @@ def manage_charge_current(self) -> None:
             tmp = self.calcMaxDischargeCurrentReferringToSoc()
             if self.max_battery_discharge_current != tmp:
                 if tmp in discharge_limits:
-                    discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"})
+                    # do not add string, if global limitation is applied
+                    if discharge_limits[tmp] != "Max Battery Discharge Current":
+                        discharge_limits.update({tmp: discharge_limits[tmp] + ", SoC"})
+                    else:
+                        pass
                 else:
                     discharge_limits.update({tmp: "SoC"})
 
@@ -694,6 +924,14 @@ def get_timeToSoc(self, socnum, crntPrctPerSec, onlyNumber=False) -> str:
         else:
             diffSoc = self.soc - socnum
 
+        """
+        calculate only positive SoC points, since negative points have no sense
+        when charging only points above current SoC are shown
+        when discharging only points below current SoC are shown
+        """
+        if diffSoc < 0:
+            return None
+
         ttgStr = None
         if self.soc != socnum and (diffSoc > 0 or utils.TIME_TO_SOC_INC_FROM is True):
             secondstogo = int(diffSoc / crntPrctPerSec)
@@ -930,6 +1168,34 @@ def get_mos_temp(self) -> Union[float, None]:
         else:
             return None
 
+    def validate_data(self) -> bool:
+        """
+        Used to validate the data received from the BMS.
+        If the data is in the thresholds return True,
+        else return False since it's very probably not a BMS
+        """
+        if self.capacity is not None and (self.capacity < 0 or self.capacity > 1000):
+            logger.debug(
+                "Capacity outside of thresholds (from 0 to 1000): " + str(self.capacity)
+            )
+            return False
+        if self.current is not None and abs(self.current) > 1000:
+            logger.debug(
+                "Current outside of thresholds (from -1000 to 1000): "
+                + str(self.current)
+            )
+            return False
+        if self.voltage is not None and (self.voltage < 0 or self.voltage > 100):
+            logger.debug(
+                "Voltage outside of thresholds (form 0 to 100): " + str(self.voltage)
+            )
+            return False
+        if self.soc is not None and (self.soc < 0 or self.soc > 100):
+            logger.debug("SoC outside of thresholds (from 0 to 100): " + str(self.soc))
+            return False
+
+        return True
+
     def log_cell_data(self) -> bool:
         if logger.getEffectiveLevel() > logging.INFO and len(self.cells) == 0:
             return False
@@ -983,11 +1249,150 @@ def log_settings(self) -> None:
         logger.info(
             f"> CCCM SOC: {str(utils.CCCM_SOC_ENABLE).ljust(5)} | DCCM SOC: {utils.DCCM_SOC_ENABLE}"
         )
-        if self.unique_identifier is not None:
-            logger.info(f"Serial Number/Unique Identifier: {self.unique_identifier}")
+        logger.info(f"Serial Number/Unique Identifier: {self.unique_identifier()}")
 
         return
 
+    # save custom name to config file
+    def custom_name_callback(self, path, value):
+        try:
+            if path == "/CustomName":
+                file = open(
+                    "/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "r"
+                )
+                lines = file.readlines()
+                last = len(lines)
+
+                # remove not allowed characters
+                value = value.replace(":", "").replace("=", "").replace(",", "").strip()
+
+                # empty string to save new config file
+                config_file_new = ""
+
+                # make sure we are in the [DEFAULT] section
+                current_line_in_default_section = False
+                default_section_checked = False
+
+                # check if already exists
+                exists = False
+
+                # count lines
+                i = 0
+                # looping through the file
+                for line in lines:
+                    # increment by one
+                    i += 1
+
+                    # stripping line break
+                    line = line.strip()
+
+                    # check, if current line is after the [DEFAULT] section
+                    if line == "[DEFAULT]":
+                        current_line_in_default_section = True
+
+                    # check, if current line starts a new section
+                    if line != "[DEFAULT]" and re.match(r"^\[.*\]", line):
+                        # set default_section_checked to true, if it was already checked and a new section comes on
+                        if current_line_in_default_section and not exists:
+                            default_section_checked = True
+                        current_line_in_default_section = False
+
+                    # check, if the current line is the last line
+                    if i == last:
+                        default_section_checked = True
+
+                    # insert or replace only in [DEFAULT] section
+                    if current_line_in_default_section and re.match(
+                        r"^CUSTOM_BATTERY_NAMES.*", line
+                    ):
+                        # set that the setting was found, else a new one is created
+                        exists = True
+
+                        # remove setting name
+                        line = re.sub(
+                            "^CUSTOM_BATTERY_NAMES\s*=\s*", "", line  # noqa: W605
+                        )
+
+                        # change only the name of the current BMS
+                        result = []
+                        bms_name_list = line.split(",")
+                        for bms_name_pair in bms_name_list:
+                            tmp = bms_name_pair.split(":")
+                            if tmp[0] == self.port:
+                                result.append(tmp[0] + ":" + value)
+                            else:
+                                result.append(bms_name_pair)
+
+                        new_line = "CUSTOM_BATTERY_NAMES = " + ",".join(result)
+
+                    else:
+                        if default_section_checked and not exists:
+                            exists = True
+
+                            # add before current line
+                            if i != last:
+                                new_line = (
+                                    "CUSTOM_BATTERY_NAMES = "
+                                    + self.port
+                                    + ":"
+                                    + value
+                                    + "\n\n"
+                                    + line
+                                )
+
+                            # add at the end if last line
+                            else:
+                                new_line = (
+                                    line
+                                    + "\n\n"
+                                    + "CUSTOM_BATTERY_NAMES = "
+                                    + self.port
+                                    + ":"
+                                    + value
+                                )
+                        else:
+                            new_line = line
+                    # concatenate the new string and add an end-line break
+                    config_file_new = config_file_new + new_line + "\n"
+
+                # close the file
+                file.close()
+                # Open file in write mode
+                write_file = open(
+                    "/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "w"
+                )
+                # overwriting the old file contents with the new/replaced content
+                write_file.write(config_file_new)
+                # close the file
+                write_file.close()
+
+                # logger.error("value (saved): " + str(value))
+
+                """
+                # this removes all comments and tranfsorm the values to lowercase
+                utils.config.set(
+                    "DEFAULT",
+                    "CUSTOM_BATTERY_NAMES",
+                    self.port + ":" + value,
+                )
+
+                # Writing our configuration file to 'example.ini'
+                with open(
+                    "/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "w"
+                ) as configfile:
+                    type(utils.config.write(configfile))
+                """
+
+        except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+            )
+
+        return value
+
     def reset_soc_callback(self, path, value):
         # callback for handling reset soc request
         return
@@ -1000,3 +1405,9 @@ def force_discharging_off_callback(self, path, value):
 
     def turn_balancing_off_callback(self, path, value):
         return
+
+    def trigger_soc_reset(self):
+        """
+        This method can be used to implement SOC reset when the battery is assumed to be full
+        """
+        return
diff --git a/etc/dbus-serialbattery/bms/ant.py b/etc/dbus-serialbattery/bms/ant.py
index 124f036f..ceb6be68 100644
--- a/etc/dbus-serialbattery/bms/ant.py
+++ b/etc/dbus-serialbattery/bms/ant.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
-# disable ANT BMS by default as it causes other issues but can be enabled manually
+# ANT BMS is disabled by default as it causes issues with other devices
+# can be enabled by specifying it in the BMS_TYPE setting in the "config.ini"
 # https://github.com/Louisvdw/dbus-serialbattery/issues/479
 
 from battery import Battery
@@ -9,9 +10,9 @@
 from struct import unpack_from
 
 
-class Ant(Battery):
+class ANT(Battery):
     def __init__(self, port, baud, address):
-        super(Ant, self).__init__(port, baud, address)
+        super(ANT, self).__init__(port, baud, address)
         self.type = self.BATTERYTYPE
 
     command_general = b"\xDB\xDB\x00\x00\x00\x00"
@@ -30,6 +31,7 @@ def test_connection(self):
         result = False
         try:
             result = self.read_status_data()
+            result = result and self.refresh_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
@@ -61,6 +63,7 @@ def read_status_data(self):
 
         voltage = unpack_from(">H", status_data, 4)
         self.voltage = voltage[0] * 0.1
+
         current, self.soc = unpack_from(">lB", status_data, 70)
         self.current = 0.0 if current == 0 else current / -10
 
diff --git a/etc/dbus-serialbattery/bms/battery_template.py b/etc/dbus-serialbattery/bms/battery_template.py
index e32e424b..e859c675 100644
--- a/etc/dbus-serialbattery/bms/battery_template.py
+++ b/etc/dbus-serialbattery/bms/battery_template.py
@@ -28,14 +28,22 @@ def test_connection(self):
         try:
             result = self.read_status_data()
             # get first data to show in startup log, only if result is true
-            if result:
-                self.refresh_data()
+            result = result and self.refresh_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
 
         return result
 
+    def unique_identifier(self) -> str:
+        """
+        Used to identify a BMS when multiple BMS are connected
+        Provide a unique identifier from the BMS to identify a BMS, if multiple same BMS are connected
+        e.g. the serial number
+        If there is no such value, please remove this function
+        """
+        return self.serialnumber
+
     def get_settings(self):
         # After successful  connection get_settings will be call to set up the battery.
         # Set the current limits, populate cell count, etc
@@ -53,11 +61,6 @@ def get_settings(self):
         self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count
         self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
-        # provide a unique identifier from the BMS to identify a BMS, if multiple same BMS are connected
-        # e.g. the serial number
-        # If there is no such value, please leave the line commented. In this case the capacity is used,
-        # since it can be changed by small amounts to make a battery unique. On +/- 5 Ah you can identify 11 batteries
-        # self.unique_identifier = str()
         return True
 
     def refresh_data(self):
@@ -83,6 +86,8 @@ def read_status_data(self):
             self.cycles,
         ) = unpack_from(">bb??bhx", status_data)
 
+        # Integrate a check to be sure, that the received data is from the BMS type you are making this driver for
+
         self.hardware_version = "TemplateBMS " + str(self.cell_count) + " cells"
         logger.info(self.hardware_version)
         return True
diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py
index 87510c48..0c033254 100644
--- a/etc/dbus-serialbattery/bms/daly.py
+++ b/etc/dbus-serialbattery/bms/daly.py
@@ -27,6 +27,7 @@ def __init__(self, port, baud, address):
         self.trigger_force_disable_discharge = None
         self.trigger_force_disable_charge = None
         self.cells_volts_data_lastreadbad = False
+        self.last_charge_mode = self.charge_mode
 
     # command bytes [StartFlag=A5][Address=40][Command=94][DataLength=8][8x zero bytes][checksum]
     command_base = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x81"
@@ -174,6 +175,9 @@ def refresh_data(self):
 
                 self.write_charge_discharge_mos(ser)
 
+                if utils.AUTO_RESET_SOC:
+                    self.update_soc(ser)
+
         except OSError:
             logger.warning("Couldn't open serial port")
 
@@ -181,6 +185,16 @@ def refresh_data(self):
             logger.info("refresh_data: result: " + str(result))
         return result
 
+    def update_soc(self, ser):
+        if self.last_charge_mode is not None and self.charge_mode is not None:
+            if not self.last_charge_mode.startswith(
+                "Float"
+            ) and self.charge_mode.startswith("Float"):
+                # we just entered float mode, so the battery must be full
+                self.soc_to_set = 100
+                self.write_soc_and_datetime(ser)
+        self.last_charge_mode = self.charge_mode
+
     def read_status_data(self, ser):
         status_data = self.request_data(ser, self.command_status)
         # check if connection success
@@ -229,7 +243,10 @@ def read_soc_data(self, ser):
             )
             if crntMinValid < current < crntMaxValid:
                 self.voltage = voltage / 10
-                self.current = current
+                # apply exponential smoothing on the flickering current measurement
+                self.current = (0.1 * current) + (
+                    0.9 * (0 if self.current is None else self.current)
+                )
                 self.soc = soc / 10
                 return True
 
@@ -387,7 +404,7 @@ def read_cells_volts(self, ser):
             for idx in range(self.cell_count):
                 self.cells.append(Cell(True))
 
-        # logger.warning("data " + bytes(cells_volts_data).hex())
+        # logger.warning("data " + bytearray_to_string(cells_volts_data))
 
         # from each of the received sentences, read up to 3 voltages
         for i in range(sentences_expected):
@@ -509,7 +526,7 @@ def read_battery_code(self, ser):
             return False
 
         battery_code = ""
-        # logger.warning("data " + bytes(cells_volts_data).hex())
+        # logger.warning("data " + utils.bytearray_to_string(cells_volts_data))
         for i in range(5):
             nr, part = unpack_from(">B7s", data, i * 8)
             if nr != i + 1:
@@ -520,15 +537,19 @@ def read_battery_code(self, ser):
             self.custom_field = sub(
                 " +",
                 " ",
-                (battery_code.strip()),
-            )
-            self.unique_identifier = self.custom_field.replace(" ", "_")
-        else:
-            self.unique_identifier = (
-                str(self.production) + "_" + str(int(self.capacity))
+                (battery_code.replace("\x00", " ").strip()),
             )
         return True
 
+    def unique_identifier(self) -> str:
+        """
+        Used to identify a BMS when multiple BMS are connected
+        """
+        if self.custom_field != "":
+            return self.custom_field.replace(" ", "_")
+        else:
+            return str(self.production) + "_" + str(int(self.capacity))
+
     def reset_soc_callback(self, path, value):
         if value is None:
             return False
@@ -544,6 +565,10 @@ def write_soc_and_datetime(self, ser):
         if self.soc_to_set is None:
             return False
 
+        # wait shortly, else the Daly is not ready and throws a lot of no reply errors
+        # if you see a lot of errors, try to increase in steps of 0.005
+        sleep(0.020)
+
         cmd = bytearray(13)
         now = datetime.now()
 
@@ -573,7 +598,7 @@ def write_soc_and_datetime(self, ser):
         ser.write(cmd)
 
         reply = self.read_sentence(ser, self.command_set_soc)
-        if reply[0] != 1:
+        if reply is False or reply[0] != 1:
             logger.error("write soc failed")
         return True
 
@@ -606,12 +631,20 @@ def force_discharging_off_callback(self, path, value):
         return False
 
     def write_charge_discharge_mos(self, ser):
+        # wait shortly, else the Daly is not ready and throws a lot of no reply errors
+        # if you see a lot of errors, try to increase in steps of 0.005
+        sleep(0.020)
+
         if (
             self.trigger_force_disable_charge is None
             and self.trigger_force_disable_discharge is None
         ):
             return False
 
+        # wait shortly, else the Daly is not ready and throws a lot of no reply errors
+        # if you see a lot of errors, try to increase in steps of 0.005
+        sleep(0.020)
+
         cmd = bytearray(self.command_base)
 
         if self.trigger_force_disable_charge is not None:
@@ -687,7 +720,7 @@ def read_sentence(self, ser, expected_reply, timeout=0.5):
         reply = ser.read_until(b"\xA5")
         if not reply or b"\xA5" not in reply:
             logger.debug(
-                f"read_sentence {bytes(expected_reply).hex()}: no sentence start received"
+                f"read_sentence {utils.bytearray_to_string(expected_reply)}: no sentence start received"
             )
             return False
 
@@ -699,21 +732,27 @@ def read_sentence(self, ser, expected_reply, timeout=0.5):
             toread = ser.inWaiting()
             time_run = time() - time_start
             if time_run > timeout:
-                logger.debug(f"read_sentence {bytes(expected_reply).hex()}: timeout")
+                logger.debug(
+                    f"read_sentence {utils.bytearray_to_string(expected_reply)}: timeout"
+                )
                 return False
 
         reply += ser.read(12)
         _, id, cmd, length = unpack_from(">BBBB", reply)
 
-        # logger.info(f"reply: {bytes(reply).hex()}")  # debug
+        # logger.info(f"reply: {utils.bytearray_to_string(reply)}")  # debug
 
         if id != 1 or length != 8 or cmd != expected_reply[0]:
-            logger.debug(f"read_sentence {bytes(expected_reply).hex()}: wrong header")
+            logger.debug(
+                f"read_sentence {utils.bytearray_to_string(expected_reply)}: wrong header"
+            )
             return False
 
         chk = unpack_from(">B", reply, 12)[0]
         if sum(reply[:12]) & 0xFF != chk:
-            logger.debug(f"read_sentence {bytes(expected_reply).hex()}: wrong checksum")
+            logger.debug(
+                f"read_sentence {utils.bytearray_to_string(expected_reply)}: wrong checksum"
+            )
             return False
 
         return reply[4:12]
diff --git a/etc/dbus-serialbattery/bms/daly_can.py b/etc/dbus-serialbattery/bms/daly_can.py
new file mode 100644
index 00000000..5b4927ef
--- /dev/null
+++ b/etc/dbus-serialbattery/bms/daly_can.py
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function, unicode_literals
+from battery import Battery, Cell
+from utils import (
+    BATTERY_CAPACITY,
+    INVERT_CURRENT_MEASUREMENT,
+    logger,
+    MAX_BATTERY_CHARGE_CURRENT,
+    MAX_BATTERY_DISCHARGE_CURRENT,
+    MAX_CELL_VOLTAGE,
+    MIN_CELL_VOLTAGE,
+)
+from struct import unpack_from
+import can
+
+"""
+https://github.com/Louisvdw/dbus-serialbattery/pull/169
+"""
+
+
+class Daly_Can(Battery):
+    def __init__(self, port, baud, address):
+        super(Daly_Can, self).__init__(port, baud, address)
+        self.charger_connected = None
+        self.load_connected = None
+        self.cell_min_voltage = None
+        self.cell_max_voltage = None
+        self.cell_min_no = None
+        self.cell_max_no = None
+        self.poll_interval = 1000
+        self.poll_step = 0
+        self.type = self.BATTERYTYPE
+        self.can_bus = None
+
+    # command bytes [Priority=18][Command=94][BMS ID=01][Uplink ID=40]
+    command_base = 0x18940140
+    command_soc = 0x18900140
+    command_minmax_cell_volts = 0x18910140
+    command_minmax_temp = 0x18920140
+    command_fet = 0x18930140
+    command_status = 0x18940140
+    command_cell_volts = 0x18950140
+    command_temp = 0x18960140
+    command_cell_balance = 0x18970140
+    command_alarm = 0x18980140
+
+    response_base = 0x18944001
+    response_soc = 0x18904001
+    response_minmax_cell_volts = 0x18914001
+    response_minmax_temp = 0x18924001
+    response_fet = 0x18934001
+    response_status = 0x18944001
+    response_cell_volts = 0x18954001
+    response_temp = 0x18964001
+    response_cell_balance = 0x18974001
+    response_alarm = 0x18984001
+
+    BATTERYTYPE = "Daly_Can"
+    LENGTH_CHECK = 4
+    LENGTH_POS = 3
+    CURRENT_ZERO_CONSTANT = 30000
+    TEMP_ZERO_CONSTANT = 40
+
+    def test_connection(self):
+        result = False
+
+        # TODO handle errors?
+        can_filters = [
+            {"can_id": self.response_base, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_soc, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_minmax_cell_volts, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_minmax_temp, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_fet, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_status, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_cell_volts, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_temp, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_cell_balance, "can_mask": 0xFFFFFFF},
+            {"can_id": self.response_alarm, "can_mask": 0xFFFFFFF},
+        ]
+        self.can_bus = can.Bus(
+            interface="socketcan",
+            channel=self.port,
+            receive_own_messages=False,
+            can_filters=can_filters,
+        )
+
+        result = self.read_status_data(self.can_bus)
+
+        return result
+
+    def get_settings(self):
+        self.capacity = BATTERY_CAPACITY
+        self.max_battery_current = MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        return True
+
+    def refresh_data(self):
+        result = False
+
+        result = self.read_soc_data(self.can_bus)
+        result = result and self.read_fed_data(self.can_bus)
+        if self.poll_step == 0:
+            # This must be listed in step 0 as get_min_cell_voltage and get_max_cell_voltage in battery.py
+            # needs it at first cycle for publish_dbus in dbushelper.py
+            result = result and self.read_cell_voltage_range_data(self.can_bus)
+        elif self.poll_step == 1:
+            result = result and self.read_alarm_data(self.can_bus)
+        elif self.poll_step == 2:
+            result = result and self.read_cells_volts(self.can_bus)
+        elif self.poll_step == 3:
+            result = result and self.read_temperature_range_data(self.can_bus)
+            # else:          # A placeholder to remind this is the last step. Add any additional steps before here
+            # This is last step so reset poll_step
+            self.poll_step = -1
+
+        self.poll_step += 1
+
+        return result
+
+    def read_status_data(self, can_bus):
+        status_data = self.read_bus_data_daly(can_bus, self.command_status)
+        # check if connection success
+        if status_data is False:
+            logger.debug("read_status_data")
+            return False
+
+        (
+            self.cell_count,
+            self.temp_sensors,
+            self.charger_connected,
+            self.load_connected,
+            state,
+            self.cycles,
+        ) = unpack_from(">bb??bhx", status_data)
+
+        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+
+        self.hardware_version = "DalyBMS " + str(self.cell_count) + " cells"
+        logger.info(self.hardware_version)
+        return True
+
+    def read_soc_data(self, ser):
+        # Ensure data received is valid
+        crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1)
+        crntMaxValid = MAX_BATTERY_CHARGE_CURRENT * 1.3
+        triesValid = 2
+        while triesValid > 0:
+            soc_data = self.read_bus_data_daly(ser, self.command_soc)
+            # check if connection success
+            if soc_data is False:
+                return False
+
+            voltage, tmp, current, soc = unpack_from(">hhhh", soc_data)
+            current = (
+                (current - self.CURRENT_ZERO_CONSTANT)
+                / -10
+                * INVERT_CURRENT_MEASUREMENT
+            )
+            # logger.info("voltage: " + str(voltage) + ", current: " + str(current) + ", soc: " + str(soc))
+            if crntMinValid < current < crntMaxValid:
+                self.voltage = voltage / 10
+                self.current = current
+                self.soc = soc / 10
+                return True
+
+            logger.warning("read_soc_data - triesValid " + str(triesValid))
+            triesValid -= 1
+
+        return False
+
+    def read_alarm_data(self, ser):
+        alarm_data = self.read_bus_data_daly(ser, self.command_alarm)
+        # check if connection success
+        if alarm_data is False:
+            logger.warning("read_alarm_data")
+            return False
+
+        (
+            al_volt,
+            al_temp,
+            al_crnt_soc,
+            al_diff,
+            al_mos,
+            al_misc1,
+            al_misc2,
+            al_fault,
+        ) = unpack_from(">bbbbbbbb", alarm_data)
+
+        if al_volt & 48:
+            # High voltage levels - Alarm
+            self.voltage_high = 2
+        elif al_volt & 15:
+            # High voltage Warning levels - Pre-alarm
+            self.voltage_high = 1
+        else:
+            self.voltage_high = 0
+
+        if al_volt & 128:
+            # Low voltage level - Alarm
+            self.voltage_low = 2
+        elif al_volt & 64:
+            # Low voltage Warning level - Pre-alarm
+            self.voltage_low = 1
+        else:
+            self.voltage_low = 0
+
+        if al_temp & 2:
+            # High charge temp - Alarm
+            self.temp_high_charge = 2
+        elif al_temp & 1:
+            # High charge temp - Pre-alarm
+            self.temp_high_charge = 1
+        else:
+            self.temp_high_charge = 0
+
+        if al_temp & 8:
+            # Low charge temp - Alarm
+            self.temp_low_charge = 2
+        elif al_temp & 4:
+            # Low charge temp - Pre-alarm
+            self.temp_low_charge = 1
+        else:
+            self.temp_low_charge = 0
+
+        if al_temp & 32:
+            # High discharge temp - Alarm
+            self.temp_high_discharge = 2
+        elif al_temp & 16:
+            # High discharge temp - Pre-alarm
+            self.temp_high_discharge = 1
+        else:
+            self.temp_high_discharge = 0
+
+        if al_temp & 128:
+            # Low discharge temp - Alarm
+            self.temp_low_discharge = 2
+        elif al_temp & 64:
+            # Low discharge temp - Pre-alarm
+            self.temp_low_discharge = 1
+        else:
+            self.temp_low_discharge = 0
+
+        # if al_crnt_soc & 2:
+        #    # High charge current - Alarm
+        #    self.current_over = 2
+        # elif al_crnt_soc & 1:
+        #    # High charge current - Pre-alarm
+        #    self.current_over = 1
+        # else:
+        #    self.current_over = 0
+
+        # if al_crnt_soc & 8:
+        #    # High discharge current - Alarm
+        #    self.current_over = 2
+        # elif al_crnt_soc & 4:
+        #    # High discharge current - Pre-alarm
+        #    self.current_over = 1
+        # else:
+        #    self.current_over = 0
+
+        if al_crnt_soc & 2 or al_crnt_soc & 8:
+            # High charge/discharge current - Alarm
+            self.current_over = 2
+        elif al_crnt_soc & 1 or al_crnt_soc & 4:
+            # High charge/discharge current - Pre-alarm
+            self.current_over = 1
+        else:
+            self.current_over = 0
+
+        if al_crnt_soc & 128:
+            # Low SoC - Alarm
+            self.soc_low = 2
+        elif al_crnt_soc & 64:
+            # Low SoC Warning level - Pre-alarm
+            self.soc_low = 1
+        else:
+            self.soc_low = 0
+
+        return True
+
+    def read_cells_volts(self, can_bus):
+        if self.cell_count is not None:
+            cells_volts_data = self.read_bus_data_daly(
+                can_bus, self.command_cell_volts, 6
+            )
+            if cells_volts_data is False:
+                logger.warning("read_cells_volts")
+                return False
+
+            frameCell = [0, 0, 0]
+            lowMin = MIN_CELL_VOLTAGE / 2
+            frame = 0
+            bufIdx = 0
+
+            if len(self.cells) != self.cell_count:
+                # init the numbers of cells
+                self.cells = []
+                for idx in range(self.cell_count):
+                    self.cells.append(Cell(True))
+
+            while bufIdx < len(cells_volts_data):
+                frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from(
+                    ">Bhhh", cells_volts_data, bufIdx
+                )
+                for idx in range(3):
+                    cellnum = ((frame - 1) * 3) + idx  # daly is 1 based, driver 0 based
+                    if cellnum >= self.cell_count:
+                        break
+                    cellVoltage = frameCell[idx] / 1000
+                    self.cells[cellnum].voltage = (
+                        None if cellVoltage < lowMin else cellVoltage
+                    )
+                bufIdx += 8
+
+        return True
+
+    def read_cell_voltage_range_data(self, ser):
+        minmax_data = self.read_bus_data_daly(ser, self.command_minmax_cell_volts)
+        # check if connection success
+        if minmax_data is False:
+            logger.warning("read_cell_voltage_range_data")
+            return False
+
+        (
+            cell_max_voltage,
+            self.cell_max_no,
+            cell_min_voltage,
+            self.cell_min_no,
+        ) = unpack_from(">hbhb", minmax_data)
+        # Daly cells numbers are 1 based and not 0 based
+        self.cell_min_no -= 1
+        self.cell_max_no -= 1
+        # Voltage is returned in mV
+        self.cell_max_voltage = cell_max_voltage / 1000
+        self.cell_min_voltage = cell_min_voltage / 1000
+        return True
+
+    def read_temperature_range_data(self, ser):
+        minmax_data = self.read_bus_data_daly(ser, self.command_minmax_temp)
+        # check if connection success
+        if minmax_data is False:
+            logger.debug("read_temperature_range_data")
+            return False
+
+        max_temp, max_no, min_temp, min_no = unpack_from(">bbbb", minmax_data)
+        self.temp1 = min_temp - self.TEMP_ZERO_CONSTANT
+        self.temp2 = max_temp - self.TEMP_ZERO_CONSTANT
+        return True
+
+    def read_fed_data(self, ser):
+        fed_data = self.read_bus_data_daly(ser, self.command_fet)
+        # check if connection success
+        if fed_data is False:
+            logger.debug("read_fed_data")
+            return False
+
+        (
+            status,
+            self.charge_fet,
+            self.discharge_fet,
+            bms_cycles,
+            capacity_remain,
+        ) = unpack_from(">b??BL", fed_data)
+        self.capacity_remain = capacity_remain / 1000
+        return True
+
+    def read_bus_data_daly(self, can_bus, command, expectedMessageCount=1):
+        # TODO handling of error cases
+        message = can.Message(arbitration_id=command)
+        can_bus.send(message, timeout=0.2)
+        response = bytearray()
+
+        # TODO use async notifier instead of this where we expect a specific frame to be received
+        # this could end up in a deadlock if a package is not received
+        count = 0
+        for msg in can_bus:
+            # print(f"{msg.arbitration_id:X}: {msg.data}")
+            # logger.info('Frame: ' + ", ".join(hex(b) for b in msg.data))
+            response.extend(msg.data)
+            count += 1
+            if count == expectedMessageCount:
+                break
+        return response
diff --git a/etc/dbus-serialbattery/bms/heltecmodbus.py b/etc/dbus-serialbattery/bms/heltecmodbus.py
index e7f866a0..1ceaa43e 100644
--- a/etc/dbus-serialbattery/bms/heltecmodbus.py
+++ b/etc/dbus-serialbattery/bms/heltecmodbus.py
@@ -30,6 +30,7 @@ class HeltecModbus(Battery):
     def __init__(self, port, baud, address):
         super(HeltecModbus, self).__init__(port, baud, address)
         self.type = "Heltec_Smart"
+        self.unique_identifier_tmp = ""
 
     def test_connection(self):
         # call a function that will connect to the battery, send a command and retrieve the result.
@@ -174,7 +175,7 @@ def read_status_data(self):
                     time.sleep(SLPTIME)
 
                     serial1 = mbdev.read_registers(2, number_of_registers=4)
-                    self.unique_identifier = "-".join(
+                    self.unique_identifier_tmp = "-".join(
                         "{:04x}".format(x) for x in serial1
                     )
                     time.sleep(SLPTIME)
@@ -234,7 +235,7 @@ def read_status_data(self):
             logger.info(self.hardware_version)
             logger.info("Heltec-" + self.hwTypeName)
             logger.info("  Dev name: " + self.devName)
-            logger.info("  Serial: " + self.unique_identifier)
+            logger.info("  Serial: " + self.unique_identifier_tmp)
             logger.info("  Made on: " + self.production_date)
             logger.info("  Cell count: " + str(self.cell_count))
             logger.info("  Cell type: " + self.cellType)
@@ -245,6 +246,12 @@ def read_status_data(self):
 
         return True
 
+    def unique_identifier(self) -> str:
+        """
+        Used to identify a BMS when multiple BMS are connected
+        """
+        return self.unique_identifier_tmp
+
     def read_soc_data(self):
         mbdev = mbdevs[self.address]
 
diff --git a/etc/dbus-serialbattery/bms/hlpdatabms4s.py b/etc/dbus-serialbattery/bms/hlpdatabms4s.py
index 7faf8b2c..9f33dc25 100644
--- a/etc/dbus-serialbattery/bms/hlpdatabms4s.py
+++ b/etc/dbus-serialbattery/bms/hlpdatabms4s.py
@@ -54,21 +54,8 @@ def refresh_data(self):
             pass
         return result
 
-        # def log_settings(self):
-        #    logger.info(f'Battery {self.type} connected to dbus from {self.port}')
-        #    logger.info(f'=== Settings ===')
-        #    cell_counter = len(self.cells)
-        #    logger.info(f'> Connection voltage {self.voltage}V | current {self.current}A | SOC {self.soc}%')
-        #    logger.info(f'> Cell count {self.cell_count} | cells populated {cell_counter}')
-        #    logger.info(f'> CCCM SOC {CCCM_SOC_ENABLE} | DCCM SOC {DCCM_SOC_ENABLE}')
-        #    logger.info(f'> CCCM CV {CCCM_CV_ENABLE} | DCCM CV {DCCM_CV_ENABLE}')
-        #    logger.info(f'> CCCM T {CCCM_T_ENABLE} | DCCM T {DCCM_T_ENABLE}')
-        #    logger.info(f'> MIN_CELL_VOLTAGE {MIN_CELL_VOLTAGE}V | MAX_CELL_VOLTAGE {MAX_CELL_VOLTAGE}V')
-
-        return
-
     def read_test_data(self):
-        test_data = self.read_serial_data_HLPdataBMS4S(b"pv\n", 1, 15)
+        test_data = self.read_serial_data_HLPdataBMS4S(b"pv\n", 0.2, 12)
         if test_data is False:
             return False
         s1 = str(test_data)
@@ -196,19 +183,15 @@ def manage_charge_current(self):
         self.control_discharge_current = 1000
 
     def read_serial_data_HLPdataBMS4S(self, command, time, min_len):
-        data = read_serial_data2(command, self.port, self.baud_rate, time, min_len)
-        if data is False:
-            return False
+        data = read_serial_data(command, self.port, self.baud_rate, time, min_len)
         return data
 
 
-def read_serial_data2(command, port, baud, time, min_len):
+def read_serial_data(command, port, baud, time, min_len):
     try:
         with serial.Serial(port, baudrate=baud, timeout=0.5) as ser:
-            ret = read_serialport_data2(ser, command, time, min_len)
-            if ret is True:
-                return ret
-        return False
+            ret = read_serialport_data(ser, command, time, min_len)
+        return ret
 
     except serial.SerialException as e:
         logger.error(e)
@@ -218,8 +201,11 @@ def read_serial_data2(command, port, baud, time, min_len):
         return False
 
 
-def read_serialport_data2(ser, command, time, min_len):
+def read_serialport_data(ser, command, time, min_len):
     try:
+        if min_len == 12:
+            ser.write(b"\n")
+            sleep(0.2)
         cnt = 0
         while cnt < 3:
             cnt += 1
@@ -227,7 +213,8 @@ def read_serialport_data2(ser, command, time, min_len):
             ser.flushInput()
             ser.write(command)
             sleep(time)
-            res = ser.read(1000)
+            toread = ser.inWaiting()
+            res = ser.read(toread)
             if len(res) >= min_len:
                 return res
         return False
diff --git a/etc/dbus-serialbattery/bms/jkbms.py b/etc/dbus-serialbattery/bms/jkbms.py
index 0aca8876..0a391d39 100644
--- a/etc/dbus-serialbattery/bms/jkbms.py
+++ b/etc/dbus-serialbattery/bms/jkbms.py
@@ -10,6 +10,7 @@ class Jkbms(Battery):
     def __init__(self, port, baud, address):
         super(Jkbms, self).__init__(port, baud, address)
         self.type = self.BATTERYTYPE
+        self.unique_identifier_tmp = ""
 
     BATTERYTYPE = "Jkbms"
     LENGTH_CHECK = 1
@@ -126,6 +127,9 @@ def read_status_data(self):
             unpack_from(">H", self.get_data(status_data, b"\x99", offset, 2))[0]
         )
 
+        # the JKBMS resets to
+        # 95% SoC, if all cell voltages are above or equal to OVPR (Over Voltage Protection Recovery)
+        # 100% Soc, if all cell voltages are above or equal to OVP (Over Voltage Protection)
         offset = cellbyte_count + 18
         self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0]
 
@@ -171,11 +175,14 @@ def read_status_data(self):
         self.custom_field = tmp if tmp != "Input Us" else None
 
         # production date
-        offset = cellbyte_count + 164
-        tmp = unpack_from(">4s", self.get_data(status_data, b"\xB5", offset, 4))[
-            0
-        ].decode()
-        self.production = "20" + tmp + "01" if tmp and tmp != "" else None
+        try:
+            offset = cellbyte_count + 164
+            tmp = unpack_from(">4s", self.get_data(status_data, b"\xB5", offset, 4))[
+                0
+            ].decode()
+            self.production = "20" + tmp + "01" if tmp and tmp != "" else None
+        except UnicodeDecodeError:
+            self.production = None
 
         offset = cellbyte_count + 174
         self.version = unpack_from(
@@ -183,9 +190,9 @@ def read_status_data(self):
         )[0].decode()
 
         offset = cellbyte_count + 197
-        self.unique_identifier = sub(
+        self.unique_identifier_tmp = sub(
             " +",
-            " ",
+            "_",
             (
                 unpack_from(">24s", self.get_data(status_data, b"\xBA", offset, 24))[0]
                 .decode()
@@ -208,6 +215,12 @@ def read_status_data(self):
         # logger.info(self.hardware_version)
         return True
 
+    def unique_identifier(self) -> str:
+        """
+        Used to identify a BMS when multiple BMS are connected
+        """
+        return self.unique_identifier_tmp
+
     def to_fet_bits(self, byte_data):
         tmp = bin(byte_data)[2:].rjust(3, utils.zero_char)
         self.charge_fet = is_bit_set(tmp[2])
@@ -271,6 +284,8 @@ def to_protection_bits(self, byte_data):
         # MOSFET temperature alarm
         self.protection.temp_high_internal = 2 if is_bit_set(tmp[pos - 1]) else 0
         # charge over voltage alarm
+        # TODO: check if "self.soc_reset_requested is False" works,
+        # else use "self.soc_reset_last_reached < int(time()) - (60 * 60)"
         self.protection.voltage_high = 2 if is_bit_set(tmp[pos - 2]) else 0
         # discharge under voltage alarm
         self.protection.voltage_low = 2 if is_bit_set(tmp[pos - 3]) else 0
@@ -328,6 +343,8 @@ def read_serial_data_jkbms(self, command: str) -> bool:
 
         s = sum(data[0:-4])
 
+        logger.debug("bytearray: " + utils.bytearray_to_string(data))
+
         if start == 0x4E57 and end == 0x68 and s == crc_lo:
             return data[10 : length - 7]
         elif s != crc_lo:
diff --git a/etc/dbus-serialbattery/bms/jkbms_ble.py b/etc/dbus-serialbattery/bms/jkbms_ble.py
index 276103c4..46742807 100644
--- a/etc/dbus-serialbattery/bms/jkbms_ble.py
+++ b/etc/dbus-serialbattery/bms/jkbms_ble.py
@@ -1,12 +1,15 @@
 # -*- coding: utf-8 -*-
 from battery import Battery, Cell
+from typing import Callable
 from utils import logger
+import utils
+from time import sleep, time
 from bms.jkbms_brn import Jkbms_Brn
-from bleak import BleakScanner, BleakError
-import asyncio
-import time
 import os
 
+# from bleak import BleakScanner, BleakError
+# import asyncio
+
 
 class Jkbms_Ble(Battery):
     BATTERYTYPE = "Jkbms_Ble"
@@ -17,93 +20,60 @@ def __init__(self, port, baud, address):
         self.address = address
         self.type = self.BATTERYTYPE
         self.jk = Jkbms_Brn(address)
+        self.unique_identifier_tmp = ""
 
         logger.info("Init of Jkbms_Ble at " + address)
 
     def connection_name(self) -> str:
         return "BLE " + self.address
 
+    def custom_name(self) -> str:
+        return "SerialBattery(" + self.type + ") " + self.address[-5:]
+
     def test_connection(self):
         # call a function that will connect to the battery, send a command and retrieve the result.
         # The result or call should be unique to this BMS. Battery name or version, etc.
         # Return True if success, False for failure
-
-        # check if device with given mac is found, otherwise abort
-
-        logger.info("Test of Jkbms_Ble at " + self.jk.address)
+        result = False
+        logger.info("Test of Jkbms_Ble at " + self.address)
         try:
-            loop = asyncio.get_event_loop()
-            t = loop.create_task(BleakScanner.discover())
-            devices = loop.run_until_complete(t)
-        except BleakError as err:
-            logger.error(str(err))
-            return False
-        except Exception as err:
-            logger.error(f"Unexpected {err=}, {type(err)=}")
-            return False
+            if self.address and self.address != "":
+                result = True
 
-        found = False
-        for d in devices:
-            if d.address == self.jk.address:
-                found = True
-        if not found:
-            logger.error("No Jkbms_Ble found at " + self.jk.address)
-            return False
-
-        """
-        # before indipended service, has to be checked
-
-        logger.info("test of jkbmsble")
-        tries = 0
-        while True:
-            try:
-                loop = asyncio.get_event_loop()
-                t = loop.create_task(
-                    BleakScanner.find_device_by_address(self.jk.address)
-                )
-                device = loop.run_until_complete(t)
-
-                if device is None:
-                    logger.info("jkbmsble not found")
-                    if tries > 2:
-                        return False
-                else:
-                    # device found, exit loop and continue test
-                    break
-            except BleakError as e:
-                if tries > 2:
-                    return False
-                # recover from error if tries left
-                logger.error(str(e))
-                self.reset_bluetooth()
-            tries += 1
-        """
+            if result:
+                # start scraping
+                self.jk.start_scraping()
+                tries = 1
 
-        # device was found, presumeably a jkbms so start scraping
-        self.jk.start_scraping()
-        tries = 1
+                while self.jk.get_status() is None and tries < 20:
+                    sleep(0.5)
+                    tries += 1
 
-        while self.jk.get_status() is None and tries < 20:
-            time.sleep(0.5)
-            tries += 1
+                # load initial data, from here on get_status has valid values to be served to the dbus
+                status = self.jk.get_status()
 
-        # load initial data, from here on get_status has valid values to be served to the dbus
-        status = self.jk.get_status()
-        if status is None:
-            self.jk.stop_scraping()
-            return False
+                if status is None:
+                    self.jk.stop_scraping()
+                    result = False
 
-        if not status["device_info"]["vendor_id"].startswith(("JK-", "JK_")):
-            self.jk.stop_scraping()
-            return False
+                if result and not status["device_info"]["vendor_id"].startswith(
+                    ("JK-", "JK_")
+                ):
+                    self.jk.stop_scraping()
+                    result = False
 
-        logger.info("Jkbms_Ble found!")
+                # get first data to show in startup log
+                if result:
+                    self.get_settings()
+                    self.refresh_data()
+            if not result:
+                logger.error("No BMS found at " + self.address)
 
-        # get first data to show in startup log
-        self.get_settings()
-        self.refresh_data()
+        except Exception as err:
+            logger.error(f"Unexpected {err=}, {type(err)=}")
+            result = False
 
-        return True
+        return result
 
     def get_settings(self):
         # After successful  connection get_settings will be call to set up the battery.
@@ -116,6 +86,11 @@ def get_settings(self):
         self.max_battery_voltage = st["cell_ovp"] * self.cell_count
         self.min_battery_voltage = st["cell_uvp"] * self.cell_count
 
+        # Persist initial OVP and OPVR settings of JK BMS BLE
+        if self.jk.ovp_initial_voltage is None or self.jk.ovpr_initial_voltage is None:
+            self.jk.ovp_initial_voltage = st["cell_ovp"]
+            self.jk.ovpr_initial_voltage = st["cell_ovpr"]
+
         # "User Private Data" field in APP
         tmp = self.jk.get_status()["device_info"]["production"]
         self.custom_field = tmp if tmp != "Input Us" else None
@@ -123,7 +98,9 @@ def get_settings(self):
         tmp = self.jk.get_status()["device_info"]["manufacturing_date"]
         self.production = "20" + tmp if tmp and tmp != "" else None
 
-        self.unique_identifier = self.jk.get_status()["device_info"]["serial_number"]
+        self.unique_identifier_tmp = self.jk.get_status()["device_info"][
+            "serial_number"
+        ]
 
         for c in range(self.cell_count):
             self.cells.append(Cell(False))
@@ -141,6 +118,16 @@ def get_settings(self):
         logger.info("BAT: " + self.hardware_version)
         return True
 
+    def unique_identifier(self) -> str:
+        """
+        Used to identify a BMS when multiple BMS are connected
+        """
+        return self.unique_identifier_tmp
+
+    def use_callback(self, callback: Callable) -> bool:
+        self.jk.set_callback(callback)
+        return callback is not None
+
     def refresh_data(self):
         # call all functions that will refresh the battery data.
         # This will be called for every iteration (1 second)
@@ -151,16 +138,31 @@ def refresh_data(self):
         st = self.jk.get_status()
         if st is None:
             return False
-        if time.time() - st["last_update"] > 30:
-            # if data not updated for more than 30s, sth is wrong, then fail
-            logger.info("Jkbms_Ble: Bluetooth died")
 
-            # if the thread is still alive but data too old there is sth
+        last_update = int(time() - st["last_update"])
+        if last_update >= 15 and last_update % 15 == 0:
+            logger.info(
+                f"Jkbms_Ble: Bluetooth connection interrupted. Got no fresh data since {last_update}s."
+            )
+            # show Bluetooth signal strength (RSSI)
+            bluetoothctl_info = os.popen(
+                "bluetoothctl info "
+                + self.address
+                + ' | grep -i -E "device|name|alias|pair|trusted|blocked|connected|rssi|power"'
+            )
+            logger.info(bluetoothctl_info.read())
+            bluetoothctl_info.close()
+
+            # if the thread is still alive but data too old there is something
             # wrong with the bt-connection; restart whole stack
-            if not self.resetting:
+            if not self.resetting and last_update >= 60:
+                logger.error(
+                    "Jkbms_Ble: Bluetooth died. Restarting Bluetooth system driver."
+                )
                 self.reset_bluetooth()
+                sleep(2)
                 self.jk.start_scraping()
-                time.sleep(2)
+                sleep(2)
 
             return False
         else:
@@ -237,20 +239,29 @@ def refresh_data(self):
         return True
 
     def reset_bluetooth(self):
-        logger.info("Reset of Bluetooth triggered")
+        logger.info("Reset of system Bluetooth daemon triggered")
         self.resetting = True
-        # if self.jk.is_running():
-        # self.jk.stop_scraping()
-        logger.info("Scraping ended, issuing sys-commands")
+        if self.jk.is_running():
+            if self.jk.stop_scraping():
+                logger.info("Scraping stopped, issuing sys-commands")
+            else:
+                logger.warning("Scraping was unable to stop, issuing sys-commands")
+
         # process kill is needed, since the service/bluetooth driver is probably freezed
         os.system('pkill -f "bluetoothd"')
         # stop will not work, if service/bluetooth driver is stuck
         # os.system("/etc/init.d/bluetooth stop")
-        time.sleep(2)
+        sleep(2)
         os.system("rfkill block bluetooth")
         os.system("rfkill unblock bluetooth")
         os.system("/etc/init.d/bluetooth start")
-        logger.info("Bluetooth should have been restarted")
+        logger.info("System Bluetooth daemon should have been restarted")
 
     def get_balancing(self):
         return 1 if self.balancing else 0
+
+    def trigger_soc_reset(self):
+        if utils.AUTO_RESET_SOC:
+            self.jk.max_cell_voltage = self.get_max_cell_voltage()
+            self.jk.trigger_soc_reset = True
+        return
diff --git a/etc/dbus-serialbattery/bms/jkbms_brn.py b/etc/dbus-serialbattery/bms/jkbms_brn.py
index 917f291f..f0c29ee1 100644
--- a/etc/dbus-serialbattery/bms/jkbms_brn.py
+++ b/etc/dbus-serialbattery/bms/jkbms_brn.py
@@ -1,13 +1,21 @@
-import asyncio
-from bleak import BleakScanner, BleakClient
-import time
-from logging import info, debug
-import logging
 from struct import unpack_from, calcsize
+from bleak import BleakScanner, BleakClient
+from time import sleep, time
+import asyncio
 import threading
 
-logging.basicConfig(level=logging.INFO)
+# if used as standalone script then use custom logger
+# else import logger from utils
+if __name__ == "__main__":
+    import logging
+
+    logger = logging.basicConfig(level=logging.DEBUG)
+
+    def bytearray_to_string(data):
+        return "".join("\\x" + format(byte, "02x") for byte in data)
 
+else:
+    from utils import bytearray_to_string, logger
 
 # zero means parse all incoming data (every second)
 CELL_INFO_REFRESH_S = 0
@@ -22,6 +30,8 @@
 FRAME_VERSION_JK02_32S = 0x03
 PROTOCOL_VERSION_JK02 = 0x02
 
+JK_REGISTER_OVPR = 0x05
+JK_REGISTER_OVP = 0x04
 
 protocol_version = PROTOCOL_VERSION_JK02
 
@@ -55,8 +65,7 @@
     [["settings", "balancing_switch"], 126, "4?"],
 ]
 
-
-TRANSLATE_CELL_INFO = [
+TRANSLATE_CELL_INFO_24S = [
     [["cell_info", "voltages", 32], 6, "<H", 0.001],
     [["cell_info", "average_cell_voltage"], 58, "<H", 0.001],
     [["cell_info", "delta_cell_voltage"], 60, "<H", 0.001],
@@ -80,6 +89,30 @@
     [["cell_info", "balancing_active"], 191, "1?"],
 ]
 
+TRANSLATE_CELL_INFO_32S = [
+    [["cell_info", "voltages", 32], 6, "<H", 0.001],
+    [["cell_info", "average_cell_voltage"], 58, "<H", 0.001],
+    [["cell_info", "delta_cell_voltage"], 60, "<H", 0.001],
+    [["cell_info", "max_voltage_cell"], 62, "<B"],
+    [["cell_info", "min_voltage_cell"], 63, "<B"],
+    [["cell_info", "resistances", 32], 64, "<H", 0.001],
+    [["cell_info", "total_voltage"], 118, "<H", 0.001],
+    [["cell_info", "current"], 126, "<l", 0.001],
+    [["cell_info", "temperature_sensor_1"], 130, "<H", 0.1],
+    [["cell_info", "temperature_sensor_2"], 132, "<H", 0.1],
+    [["cell_info", "temperature_mos"], 112, "<H", 0.1],
+    [["cell_info", "balancing_current"], 138, "<H", 0.001],
+    [["cell_info", "balancing_action"], 140, "<B", 0.001],
+    [["cell_info", "battery_soc"], 141, "B"],
+    [["cell_info", "capacity_remain"], 142, "<L", 0.001],
+    [["cell_info", "capacity_nominal"], 146, "<L", 0.001],
+    [["cell_info", "cycle_count"], 150, "<L"],
+    [["cell_info", "cycle_capacity"], 154, "<L", 0.001],
+    [["cell_info", "charging_switch_enabled"], 166, "1?"],
+    [["cell_info", "discharging_switch_enabled"], 167, "1?"],
+    [["cell_info", "balancing_active"], 191, "1?"],
+]
+
 
 class Jkbms_Brn:
     # entries for translating the bytearray to py-object via unpack
@@ -91,14 +124,61 @@ class Jkbms_Brn:
     waiting_for_response = ""
     last_cell_info = 0
 
+    _new_data_callback = None
+
+    # Variables to control automatic SOC reset for BLE connected JK BMS
+    # max_cell_voltage will be updated when a SOC reset is requested
+    max_cell_voltage = None
+    # OVP and OVPR will be persisted after the first successful readout of the BMS settings
+    ovp_initial_voltage = None
+    ovpr_initial_voltage = None
+
+    # will be set by get_bms_max_cell_count()
+    bms_max_cell_count = None
+
+    # translate info placeholder, since it depends on the bms_max_cell_count
+    translate_cell_info = []
+
     def __init__(self, addr):
         self.address = addr
         self.bt_thread = threading.Thread(target=self.connect_and_scrape)
+        self.trigger_soc_reset = False
 
     async def scanForDevices(self):
         devices = await BleakScanner.discover()
         for d in devices:
-            print(d)
+            logger.debug(d)
+
+    # check where the bms data starts and
+    # if the bms is a 24s or 32s type
+    def get_bms_max_cell_count(self):
+        fb = self.frame_buffer
+        logger.debug(self.frame_buffer)
+
+        # old check to recognize 32s
+        # what does this check validate?
+        # unfortunately does not work on every system
+        # has32s = fb[189] == 0x00 and fb[189 + 32] > 0
+
+        # logger can be removed after releasing next stable
+        # current version v1.0.20231102dev
+        logger.debug(f"fb[38]: {fb[36]}.{fb[37]}.{fb[38]}.{fb[39]}.{fb[40]}")
+        logger.debug(f"fb[54]: {fb[52]}.{fb[53]}.{fb[54]}.{fb[55]}.{fb[56]}")
+        logger.debug(f"fb[70]: {fb[68]}.{fb[69]}.{fb[70]}.{fb[71]}.{fb[72]}")
+        logger.debug(f"fb[134]: {fb[132]}.{fb[133]}.{fb[134]}.{fb[135]}.{fb[136]}")
+        logger.debug(f"fb[144]: {fb[142]}.{fb[143]}.{fb[144]}.{fb[145]}.{fb[146]}")
+        logger.debug(f"fb[289]: {fb[287]}.{fb[288]}.{fb[289]}.{fb[290]}.{fb[291]}")
+
+        # if BMS has a max of 32s the data at fb[287] is not empty
+        if fb[287] > 0:
+            self.bms_max_cell_count = 32
+            self.translate_cell_info = TRANSLATE_CELL_INFO_32S
+        # if BMS has a max of 24s the data ends at fb[219]
+        else:
+            self.bms_max_cell_count = 24
+            self.translate_cell_info = TRANSLATE_CELL_INFO_24S
+
+        logger.debug(f"bms_max_cell_count recognized: {self.bms_max_cell_count}")
 
     # iterative implementation maybe later due to referencing
     def translate(self, fb, translation, o, f32s=False, i=0):
@@ -185,42 +265,45 @@ def decode_device_info_jk02(self):
 
     def decode_cellinfo_jk02(self):
         fb = self.frame_buffer
-        has32s = fb[189] == 0x00 and fb[189 + 32] > 0
-        for t in TRANSLATE_CELL_INFO:
+        has32s = self.bms_max_cell_count == 32
+        for t in self.translate_cell_info:
             self.translate(fb, t, self.bms_status, f32s=has32s)
         self.decode_warnings(fb)
-        debug(self.bms_status)
+        logger.debug("decode_cellinfo_jk02(): self.frame_buffer")
+        logger.debug(self.frame_buffer)
+        logger.debug(self.bms_status)
 
     def decode_settings_jk02(self):
         fb = self.frame_buffer
         for t in TRANSLATE_SETTINGS:
             self.translate(fb, t, self.bms_status)
-        debug(self.bms_status)
+        logger.debug(self.bms_status)
 
     def decode(self):
         # check what kind of info the frame contains
         info_type = self.frame_buffer[4]
+        self.get_bms_max_cell_count()
         if info_type == 0x01:
-            info("Processing frame with settings info")
+            logger.debug("Processing frame with settings info")
             if protocol_version == PROTOCOL_VERSION_JK02:
                 self.decode_settings_jk02()
                 # adapt translation table for cell array lengths
                 ccount = self.bms_status["settings"]["cell_count"]
-                for i, t in enumerate(TRANSLATE_CELL_INFO):
+                for i, t in enumerate(self.translate_cell_info):
                     if t[0][-2] == "voltages" or t[0][-2] == "voltages":
-                        TRANSLATE_CELL_INFO[i][0][-1] = ccount
-                self.bms_status["last_update"] = time.time()
+                        self.translate_cell_info[i][0][-1] = ccount
+                self.bms_status["last_update"] = time()
 
         elif info_type == 0x02:
             if (
                 CELL_INFO_REFRESH_S == 0
-                or time.time() - self.last_cell_info > CELL_INFO_REFRESH_S
+                or time() - self.last_cell_info > CELL_INFO_REFRESH_S
             ):
-                self.last_cell_info = time.time()
-                info("processing frame with battery cell info")
+                self.last_cell_info = time()
+                logger.debug("processing frame with battery cell info")
                 if protocol_version == PROTOCOL_VERSION_JK02:
                     self.decode_cellinfo_jk02()
-                    self.bms_status["last_update"] = time.time()
+                    self.bms_status["last_update"] = time()
                 # power is calculated from voltage x current as
                 # register 122 contains unsigned power-value
                 self.bms_status["cell_info"]["power"] = (
@@ -231,18 +314,27 @@ def decode(self):
                     self.waiting_for_response = ""
 
         elif info_type == 0x03:
-            info("processing frame with device info")
+            logger.debug("processing frame with device info")
             if protocol_version == PROTOCOL_VERSION_JK02:
                 self.decode_device_info_jk02()
-                self.bms_status["last_update"] = time.time()
+                self.bms_status["last_update"] = time()
             else:
                 return
             if self.waiting_for_response == "device_info":
                 self.waiting_for_response = ""
 
+    def set_callback(self, callback):
+        self._new_data_callback = callback
+
     def assemble_frame(self, data: bytearray):
+        logger.debug(
+            f"--> assemble_frame() -> self.frame_buffer (before extend) -> lenght:  {len(self.frame_buffer)}"
+        )
+        logger.debug(self.frame_buffer)
         if len(self.frame_buffer) > MAX_RESPONSE_SIZE:
-            info("data dropped because it alone was longer than max frame length")
+            logger.debug(
+                "data dropped because it alone was longer than max frame length"
+            )
             self.frame_buffer = []
 
         if data[0] == 0x55 and data[1] == 0xAA and data[2] == 0xEB and data[3] == 0x90:
@@ -251,19 +343,26 @@ def assemble_frame(self, data: bytearray):
 
         self.frame_buffer.extend(data)
 
+        logger.debug(
+            f"--> assemble_frame() -> self.frame_buffer (after extend) -> lenght:  {len(self.frame_buffer)}"
+        )
+        logger.debug(self.frame_buffer)
         if len(self.frame_buffer) >= MIN_RESPONSE_SIZE:
             # check crc; always at position 300, independent of
             # actual frame-lentgh, so crc up to 299
             ccrc = self.crc(self.frame_buffer, 300 - 1)
             rcrc = self.frame_buffer[300 - 1]
-            debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}")
+            logger.debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}")
             if ccrc == rcrc:
-                debug("great success! frame complete and sane, lets decode")
+                logger.debug("great success! frame complete and sane, lets decode")
                 self.decode()
                 self.frame_buffer = []
+                if self._new_data_callback is not None:
+                    self._new_data_callback()
 
     def ncallback(self, sender: int, data: bytearray):
-        debug(f"------> NEW PACKAGE!laenge:  {len(data)}")
+        logger.debug(f"--> NEW PACKAGE! lenght:  {len(data)}")
+        logger.debug("ncallback(): " + bytearray_to_string(data))
         self.assemble_frame(data)
 
     def crc(self, arr: bytearray, length: int) -> int:
@@ -273,7 +372,12 @@ def crc(self, arr: bytearray, length: int) -> int:
         return crc.to_bytes(2, "little")[0]
 
     async def write_register(
-        self, address, vals: bytearray, length: int, bleakC: BleakClient
+        self,
+        address,
+        vals: bytearray,
+        length: int,
+        bleakC: BleakClient,
+        awaitresponse: bool,
     ):
         frame = bytearray(20)
         frame[0] = 0xAA  # start sequence
@@ -296,15 +400,17 @@ async def write_register(
         frame[17] = 0x00
         frame[18] = 0x00
         frame[19] = self.crc(frame, len(frame) - 1)
-        debug("Write register: ", frame)
-        await bleakC.write_gatt_char(CHAR_HANDLE, frame, False)
+        logger.debug("Write register: " + str(address) + " " + str(frame))
+        await bleakC.write_gatt_char(CHAR_HANDLE, frame, response=awaitresponse)
+        if awaitresponse:
+            await asyncio.sleep(5)
 
     async def request_bt(self, rtype: str, client):
-        timeout = time.time()
+        timeout = time()
 
-        while self.waiting_for_response != "" and time.time() - timeout < 10:
+        while self.waiting_for_response != "" and time() - timeout < 10:
             await asyncio.sleep(1)
-            print(self.waiting_for_response)
+            logger.debug(self.waiting_for_response)
 
         if rtype == "cell_info":
             cmd = COMMAND_CELL_INFO
@@ -315,7 +421,7 @@ async def request_bt(self, rtype: str, client):
         else:
             return
 
-        await self.write_register(cmd, b"\0\0\0\0", 0x00, client)
+        await self.write_register(cmd, b"\0\0\0\0", 0x00, client, False)
 
     def get_status(self):
         if "settings" in self.bms_status and "cell_info" in self.bms_status:
@@ -326,14 +432,18 @@ def get_status(self):
     def connect_and_scrape(self):
         asyncio.run(self.asy_connect_and_scrape())
 
+    # self.bt_thread
     async def asy_connect_and_scrape(self):
-        print("connect and scrape on address: " + self.address)
+        logger.debug(
+            "--> asy_connect_and_scrape(): Connect and scrape on address: "
+            + self.address
+        )
         self.run = True
         while self.run and self.main_thread.is_alive():  # autoreconnect
             client = BleakClient(self.address)
-            print("btloop")
+            logger.debug("--> asy_connect_and_scrape(): btloop")
             try:
-                print("reconnect")
+                logger.debug("--> asy_connect_and_scrape(): reconnect")
                 await client.connect()
                 self.bms_status["model_nbr"] = (
                     await client.read_gatt_char(MODEL_NBR_UUID)
@@ -344,27 +454,36 @@ async def asy_connect_and_scrape(self):
 
                 await self.request_bt("cell_info", client)
                 # await self.enable_charging(client)
-                # last_dev_info = time.time()
+                # last_dev_info = time()
                 while client.is_connected and self.run and self.main_thread.is_alive():
+                    if self.trigger_soc_reset:
+                        self.trigger_soc_reset = False
+                        await self.reset_soc_jk(client)
                     await asyncio.sleep(0.01)
-            except Exception as e:
-                info("error while connecting to bt: " + str(e))
+            except Exception as err:
                 self.run = False
+                logger.info(
+                    f"--> asy_connect_and_scrape(): error while connecting to bt: {err}"
+                )
             finally:
+                self.run = False
                 if client.is_connected:
                     try:
                         await client.disconnect()
-                    except Exception as e:
-                        info("error while disconnecting: " + str(e))
+                    except Exception as err:
+                        logger.info(
+                            f"--> asy_connect_and_scrape(): error while disconnecting: {err}"
+                        )
 
-        print("Exiting bt-loop")
+        logger.info("--> asy_connect_and_scrape(): Exit")
 
     def start_scraping(self):
         self.main_thread = threading.current_thread()
         if self.is_running():
+            logger.debug("screaping thread already running")
             return
         self.bt_thread.start()
-        info(
+        logger.debug(
             "scraping thread started -> main thread id: "
             + str(self.main_thread.ident)
             + " scraping thread: "
@@ -373,10 +492,10 @@ def start_scraping(self):
 
     def stop_scraping(self):
         self.run = False
-        stop = time.time()
+        stop = time()
         while self.is_running():
-            time.sleep(0.1)
-            if time.time() - stop > 10:
+            sleep(0.1)
+            if time() - stop > 10:
                 return False
         return True
 
@@ -388,17 +507,58 @@ async def enable_charging(self, c):
         # data is 01 00 00 00 for on  00 00 00 00 for off;
         # the following bytes up to 19 are unclear and changing
         # dynamically -> auth-mechanism?
-        await self.write_register(0x1D, b"\x01\x00\x00\x00", 4, c)
-        await self.write_register(0x1E, b"\x01\x00\x00\x00", 4, c)
-        await self.write_register(0x1F, b"\x01\x00\x00\x00", 4, c)
-        await self.write_register(0x40, b"\x01\x00\x00\x00", 4, c)
+        await self.write_register(0x1D, b"\x01\x00\x00\x00", 4, c, True)
+        await self.write_register(0x1E, b"\x01\x00\x00\x00", 4, c, True)
+        await self.write_register(0x1F, b"\x01\x00\x00\x00", 4, c, True)
+        await self.write_register(0x40, b"\x01\x00\x00\x00", 4, c, True)
+
+    def jk_float_to_hex_little(self, val: float):
+        intval = int(val * 1000)
+        hexval = f"{intval:0>8X}"
+        return bytearray.fromhex(hexval)[::-1]
+
+    async def reset_soc_jk(self, c):
+        # Lowering OVPR / OVP based on the maximum cell voltage at the time
+        # That will trigger a High Voltage Alert and resets SOC to 100%
+        ovp_trigger = round(self.max_cell_voltage - 0.05, 3)
+        ovpr_trigger = round(self.max_cell_voltage - 0.10, 3)
+        await self.write_register(
+            JK_REGISTER_OVPR, self.jk_float_to_hex_little(ovpr_trigger), 0x04, c, True
+        )
+        await self.write_register(
+            JK_REGISTER_OVP, self.jk_float_to_hex_little(ovp_trigger), 0x04, c, True
+        )
+
+        # Give BMS some time to recognize
+        await asyncio.sleep(5)
+
+        # Set values back to initial values
+        await self.write_register(
+            JK_REGISTER_OVP,
+            self.jk_float_to_hex_little(self.ovp_initial_voltage),
+            0x04,
+            c,
+            True,
+        )
+        await self.write_register(
+            JK_REGISTER_OVPR,
+            self.jk_float_to_hex_little(self.ovpr_initial_voltage),
+            0x04,
+            c,
+            True,
+        )
+
+        logger.info("JK BMS SOC reset finished.")
 
 
-"""
 if __name__ == "__main__":
-    jk = Jkbms_Brn("C8:47:8C:00:00:00")
-    jk.start_scraping()
-    while True:
-        print(jk.get_status())
-        time.sleep(5)
-"""
+    import sys
+
+    jk = Jkbms_Brn(sys.argv[1])
+    if not jk.test_connection():
+        logger.error(">>> ERROR: Unable to connect")
+    else:
+        jk.start_scraping()
+        while True:
+            logger.debug(jk.get_status())
+            sleep(5)
diff --git a/etc/dbus-serialbattery/bms/jkbms_can.py b/etc/dbus-serialbattery/bms/jkbms_can.py
new file mode 100644
index 00000000..2021771d
--- /dev/null
+++ b/etc/dbus-serialbattery/bms/jkbms_can.py
@@ -0,0 +1,267 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function, unicode_literals
+from battery import Battery, Cell
+from utils import (
+    is_bit_set,
+    logger,
+    MAX_BATTERY_CHARGE_CURRENT,
+    MAX_BATTERY_DISCHARGE_CURRENT,
+    MAX_CELL_VOLTAGE,
+    MIN_CELL_VOLTAGE,
+    zero_char,
+)
+from struct import unpack_from
+import can
+import time
+
+"""
+https://github.com/Louisvdw/dbus-serialbattery/compare/dev...IrisCrimson:dbus-serialbattery:jkbms_can
+
+# Restrictions seen from code:
+-
+"""
+
+
+class Jkbms_Can(Battery):
+    def __init__(self, port, baud, address):
+        super(Jkbms_Can, self).__init__(port, baud, address)
+        self.can_bus = False
+        self.cell_count = 1
+        self.poll_interval = 1500
+        self.type = self.BATTERYTYPE
+        self.last_error_time = time.time()
+        self.error_active = False
+
+    def __del__(self):
+        if self.can_bus:
+            self.can_bus.shutdown()
+            self.can_bus = False
+            logger.debug("bus shutdown")
+
+    BATTERYTYPE = "Jkbms_Can"
+    CAN_BUS_TYPE = "socketcan"
+
+    CURRENT_ZERO_CONSTANT = 400
+    BATT_STAT = "BATT_STAT"
+    CELL_VOLT = "CELL_VOLT"
+    CELL_TEMP = "CELL_TEMP"
+    ALM_INFO = "ALM_INFO"
+
+    MESSAGES_TO_READ = 100
+
+    CAN_FRAMES = {
+        BATT_STAT: 0x02F4,
+        CELL_VOLT: 0x04F4,
+        CELL_TEMP: 0x05F4,
+        ALM_INFO: 0x07F4,
+    }
+
+    def test_connection(self):
+        # call a function that will connect to the battery, send a command and retrieve the result.
+        # The result or call should be unique to this BMS. Battery name or version, etc.
+        # Return True if success, False for failure
+        return self.read_status_data()
+
+    def get_settings(self):
+        # After successful  connection get_settings will be call to set up the battery.
+        # Set the current limits, populate cell count, etc
+        # Return True if success, False for failure
+        self.max_battery_current = MAX_BATTERY_CHARGE_CURRENT
+        self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT
+        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
+        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count
+
+        # init the cell array add only missing Cell instances
+        missing_instances = self.cell_count - len(self.cells)
+        if missing_instances > 0:
+            for c in range(missing_instances):
+                self.cells.append(Cell(False))
+
+        self.hardware_version = "JKBMS CAN " + str(self.cell_count) + " cells"
+        return True
+
+    def refresh_data(self):
+        # call all functions that will refresh the battery data.
+        # This will be called for every iteration (1 second)
+        # Return True if success, False for failure
+        result = self.read_status_data()
+
+        return result
+
+    def read_status_data(self):
+        status_data = self.read_serial_data_jkbms_CAN()
+        # check if connection success
+        if status_data is False:
+            return False
+
+        return True
+
+    def to_fet_bits(self, byte_data):
+        tmp = bin(byte_data)[2:].rjust(2, zero_char)
+        self.charge_fet = is_bit_set(tmp[1])
+        self.discharge_fet = is_bit_set(tmp[0])
+
+    def to_protection_bits(self, byte_data):
+        tmp = bin(byte_data | 0xFF00000000)
+        pos = len(tmp)
+        logger.debug(tmp)
+        self.protection.cell_overvoltage = 2 if int(tmp[pos - 2 : pos], 2) > 0 else 0
+        self.protection.voltage_cell_low = (
+            2 if int(tmp[pos - 4 : pos - 2], 2) > 0 else 0
+        )
+        self.protection.voltage_high = 2 if int(tmp[pos - 6 : pos - 4], 4) > 0 else 0
+        self.protection.voltage_low = 2 if int(tmp[pos - 8 : pos - 6], 2) > 0 else 0
+        self.protection.cell_imbalance = 2 if int(tmp[pos - 10 : pos - 8], 2) > 0 else 0
+        self.protection.current_under = 2 if int(tmp[pos - 12 : pos - 10], 2) > 0 else 0
+        self.protection.current_over = 2 if int(tmp[pos - 14 : pos - 12], 2) > 0 else 0
+
+        # there is just a BMS and Battery temp alarm (not for charg and discharge)
+        self.protection.temp_high_charge = (
+            2 if int(tmp[pos - 16 : pos - 14], 2) > 0 else 0
+        )
+        self.protection.temp_high_discharge = (
+            2 if int(tmp[pos - 16 : pos - 14], 2) > 0 else 0
+        )
+        self.protection.temp_low_charge = (
+            2 if int(tmp[pos - 18 : pos - 16], 2) > 0 else 0
+        )
+        self.protection.temp_low_discharge = (
+            2 if int(tmp[pos - 18 : pos - 16], 2) > 0 else 0
+        )
+        self.protection.temp_high_charge = (
+            2 if int(tmp[pos - 20 : pos - 18], 2) > 0 else 0
+        )
+        self.protection.temp_high_discharge = (
+            2 if int(tmp[pos - 20 : pos - 18], 2) > 0 else 0
+        )
+        self.protection.soc_low = 2 if int(tmp[pos - 22 : pos - 20], 2) > 0 else 0
+        self.protection.internal_failure = (
+            2 if int(tmp[pos - 24 : pos - 22], 2) > 0 else 0
+        )
+        self.protection.internal_failure = (
+            2 if int(tmp[pos - 26 : pos - 24], 2) > 0 else 0
+        )
+        self.protection.internal_failure = (
+            2 if int(tmp[pos - 28 : pos - 26], 2) > 0 else 0
+        )
+        self.protection.internal_failure = (
+            2 if int(tmp[pos - 30 : pos - 28], 2) > 0 else 0
+        )
+
+    def reset_protection_bits(self):
+        self.protection.cell_overvoltage = 0
+        self.protection.voltage_cell_low = 0
+        self.protection.voltage_high = 0
+        self.protection.voltage_low = 0
+        self.protection.cell_imbalance = 0
+        self.protection.current_under = 0
+        self.protection.current_over = 0
+
+        # there is just a BMS and Battery temp alarm (not for charg and discharge)
+        self.protection.temp_high_charge = 0
+        self.protection.temp_high_discharge = 0
+        self.protection.temp_low_charge = 0
+        self.protection.temp_low_discharge = 0
+        self.protection.temp_high_charge = 0
+        self.protection.temp_high_discharge = 0
+        self.protection.soc_low = 0
+        self.protection.internal_failure = 0
+        self.protection.internal_failure = 0
+        self.protection.internal_failure = 0
+        self.protection.internal_failure = 0
+
+    def read_serial_data_jkbms_CAN(self):
+        if self.can_bus is False:
+            logger.debug("Can bus init")
+            # intit the can interface
+            try:
+                self.can_bus = can.interface.Bus(
+                    bustype=self.CAN_BUS_TYPE, channel=self.port, bitrate=self.baud_rate
+                )
+            except can.CanError as e:
+                logger.error(e)
+
+            if self.can_bus is None:
+                return False
+
+            logger.debug("Can bus init done")
+
+        # reset errors after timeout
+        if ((time.time() - self.last_error_time) > 120.0) and self.error_active is True:
+            self.error_active = False
+            self.reset_protection_bits()
+
+        # read msgs until we get one we want
+        messages_to_read = self.MESSAGES_TO_READ
+        while messages_to_read > 0:
+            msg = self.can_bus.recv(1)
+            if msg is None:
+                logger.info("No CAN Message received")
+                return False
+
+            if msg is not None:
+                # print("message received")
+                messages_to_read -= 1
+                # print(messages_to_read)
+                if msg.arbitration_id == self.CAN_FRAMES[self.BATT_STAT]:
+                    voltage = unpack_from("<H", bytes([msg.data[0], msg.data[1]]))[0]
+                    self.voltage = voltage / 10
+
+                    current = unpack_from("<H", bytes([msg.data[2], msg.data[3]]))[0]
+                    self.current = (current / 10) - 400
+
+                    self.soc = unpack_from("<B", bytes([msg.data[4]]))[0]
+
+                    self.time_to_go = (
+                        unpack_from("<H", bytes([msg.data[6], msg.data[7]]))[0] * 36
+                    )
+
+                    # print(self.voltage)
+                    # print(self.current)
+                    # print(self.soc)
+                    # print(self.time_to_go)
+
+                elif msg.arbitration_id == self.CAN_FRAMES[self.CELL_VOLT]:
+                    max_cell_volt = (
+                        unpack_from("<H", bytes([msg.data[0], msg.data[1]]))[0] / 1000
+                    )
+                    max_cell_nr = unpack_from("<B", bytes([msg.data[2]]))[0]
+                    max_cell_cnt = max(max_cell_nr, self.cell_count)
+
+                    min_cell_volt = (
+                        unpack_from("<H", bytes([msg.data[3], msg.data[4]]))[0] / 1000
+                    )
+                    min_cell_nr = unpack_from("<B", bytes([msg.data[5]]))[0]
+                    max_cell_cnt = max(min_cell_nr, max_cell_cnt)
+
+                    if max_cell_cnt > self.cell_count:
+                        self.cell_count = max_cell_cnt
+                        self.get_settings()
+
+                    for c_nr in range(len(self.cells)):
+                        self.cells[c_nr].balance = False
+
+                    if self.cell_count == len(self.cells):
+                        self.cells[max_cell_nr - 1].voltage = max_cell_volt
+                        self.cells[max_cell_nr - 1].balance = True
+
+                        self.cells[min_cell_nr - 1].voltage = min_cell_volt
+                        self.cells[min_cell_nr - 1].balance = True
+
+                elif msg.arbitration_id == self.CAN_FRAMES[self.CELL_TEMP]:
+                    max_temp = unpack_from("<B", bytes([msg.data[0]]))[0] - 50
+                    min_temp = unpack_from("<B", bytes([msg.data[2]]))[0] - 50
+                    self.to_temp(1, max_temp if max_temp <= 100 else 100)
+                    self.to_temp(2, min_temp if min_temp <= 100 else 100)
+                    # print(max_temp)
+                    # print(min_temp)
+                elif msg.arbitration_id == self.CAN_FRAMES[self.ALM_INFO]:
+                    alarms = unpack_from(
+                        "<L",
+                        bytes([msg.data[0], msg.data[1], msg.data[2], msg.data[3]]),
+                    )[0]
+                    print("alarms %d" % (alarms))
+                    self.last_error_time = time.time()
+                    self.error_active = True
+                    self.to_protection_bits(alarms)
+        return True
diff --git a/etc/dbus-serialbattery/bms/lltjbd.py b/etc/dbus-serialbattery/bms/lltjbd.py
index 63de584b..24399a40 100644
--- a/etc/dbus-serialbattery/bms/lltjbd.py
+++ b/etc/dbus-serialbattery/bms/lltjbd.py
@@ -2,7 +2,7 @@
 from battery import Protection, Battery, Cell
 from utils import is_bit_set, read_serial_data, logger
 import utils
-from struct import unpack_from
+from struct import unpack_from, pack
 import struct
 
 # Protocol registers
@@ -68,8 +68,10 @@
 REG_CELL_V_DELAYS = 0x3D
 REG_CHGOC_DELAYS = 0x3E
 REG_DSGOC_DELAYS = 0x3F
-REG_GPSOFF = 0x40
-REG_GPSOFF_TIME = 0x41
+# Cut-off voltage turns off GPS protection board
+REG_GPS_OFF = 0x40
+# Cut-off voltage delay for GPS protection board
+REG_GPS_OFF_TIME = 0x41
 REG_CAP_90 = 0x42
 REG_CAP_70 = 0x43
 REG_CAP_50 = 0x44
@@ -141,6 +143,27 @@
 CMD_EXIT_FACTORY_MODE = b"\x00\x00"
 CMD_EXIT_AND_SAVE_FACTORY_MODE = b"\x28\x28"
 
+# Weak current switch function
+FUNC_SW_EN = 0x0001  # bit 0
+# Load lock function used to disconnect the load when short circuit is required to recover
+FUNC_LOAD_EN = 0x0002  # bit 1
+# Enable balancer function
+FUNC_BALANCE_EN = 0x0004  # bit 2
+# Charge balance, only turn on balance when charging
+FUNC_BALANCE_CHARGING_ONLY = 0x0008  # bit 3
+# LED power indicator function
+FUNC_LED = 0x0010  # bit 4
+# Compatible with LED modes
+FUNC_LED_NUM = 0x0020  # bit 5
+# With history recording
+FUNC_RTC = 0x0040  # bit 6
+# whether it is necessary to set the range when it is currently used for FCC update
+FUNC_EDV = 0x0080  # bit 7
+# Additional GPS protection board is connected
+FUNC_GPS_EN = 0x0100  # bit 8
+# Enable onboard buzzer / GPS protection board buzzer?
+FUNC_BUZZER_EN = 0x0200  # bit 9
+
 
 def checksum(payload):
     return (0x10000 - sum(payload)) % 0x10000
@@ -217,6 +240,10 @@ def __init__(self, port, baud, address):
         self.soc_to_set = None
         self.factory_mode = False
         self.writable = False
+        self.trigger_force_disable_discharge = None
+        self.trigger_force_disable_charge = None
+        self.trigger_disable_balancer = None
+        self.cycle_capacity = None
 
     # degree_sign = u'\N{DEGREE SIGN}'
     BATTERYTYPE = "LLT/JBD"
@@ -233,11 +260,14 @@ def test_connection(self):
         # Return True if success, False for failure
         result = False
         try:
-            result = self.get_settings()
-            # get first data to show in startup log, only if result is true
-            if result:
+            # 1) Read name of BMS
+            # 2) Try read BMS settings
+            # 3) Refresh general data
+            result = (
                 self.read_hardware_data()
-                self.refresh_data()
+                and self.get_settings()
+                and self.refresh_data()
+            )
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
@@ -253,6 +283,9 @@ def get_settings(self):
         self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT
         self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT
         with self.eeprom(writable=False):
+            cycle_cap = self.read_serial_data_llt(readCmd(REG_CYCLE_CAP))
+            if cycle_cap:
+                self.cycle_capacity = float(unpack_from(">H", cycle_cap)[0])
             charge_over_current = self.read_serial_data_llt(readCmd(REG_CHGOC))
             if charge_over_current:
                 self.max_battery_charge_current = float(
@@ -263,6 +296,10 @@ def get_settings(self):
                 self.max_battery_discharge_current = float(
                     unpack_from(">h", discharge_over_current)[0] / -100.0
                 )
+            func_config = self.read_serial_data_llt(readCmd(REG_FUNC_CONFIG))
+            if func_config:
+                self.func_config = unpack_from(">H", func_config)[0]
+                self.balance_fet = (self.func_config & FUNC_BALANCE_EN) != 0
 
         return True
 
@@ -288,10 +325,123 @@ def write_soc(self):
             pack_voltage = struct.pack(">H", int(self.voltage * 10))
             self.read_serial_data_llt(writeCmd(REG_CAP_100, pack_voltage))
 
+    def force_charging_off_callback(self, path, value):
+        if value is None:
+            return False
+
+        if value == 0:
+            self.trigger_force_disable_charge = False
+            return True
+
+        if value == 1:
+            self.trigger_force_disable_charge = True
+            return True
+
+        return False
+
+    def force_discharging_off_callback(self, path, value):
+        if value is None:
+            return False
+
+        if value == 0:
+            self.trigger_force_disable_discharge = False
+            return True
+
+        if value == 1:
+            self.trigger_force_disable_discharge = True
+            return True
+
+        return False
+
+    def write_charge_discharge_mos(self):
+        if (
+            self.trigger_force_disable_charge is None
+            and self.trigger_force_disable_discharge is None
+        ):
+            return False
+
+        charge_disabled = 0 if self.charge_fet else 1
+        if self.trigger_force_disable_charge is not None and self.control_allow_charge:
+            charge_disabled = 1 if self.trigger_force_disable_charge else 0
+            logger.info(
+                f"write force disable charging: {'true' if self.trigger_force_disable_charge else 'false'}"
+            )
+        self.trigger_force_disable_charge = None
+
+        discharge_disabled = 0 if self.discharge_fet else 1
+        if (
+            self.trigger_force_disable_discharge is not None
+            and self.control_allow_discharge
+        ):
+            discharge_disabled = 1 if self.trigger_force_disable_discharge else 0
+            logger.info(
+                f"write force disable discharging: {'true' if self.trigger_force_disable_discharge else 'false'}"
+            )
+        self.trigger_force_disable_discharge = None
+
+        mosdata = pack(">BB", 0, charge_disabled | (discharge_disabled << 1))
+
+        reply = self.read_serial_data_llt(writeCmd(REG_CTRL_MOSFET, mosdata))
+
+        if reply is False:
+            logger.error("write force disable charge/discharge failed")
+            return False
+
+    def turn_balancing_off_callback(self, path, value):
+        if value is None:
+            return False
+
+        if value == 0:
+            self.trigger_disable_balancer = False
+            return True
+
+        if value == 1:
+            self.trigger_disable_balancer = True
+            return True
+
+        return False
+
+    def write_balancer(self):
+        if self.trigger_disable_balancer is None:
+            return False
+
+        disable_balancer = self.trigger_disable_balancer
+        logger.info(
+            f"write disable balancer: {'true' if self.trigger_disable_balancer else 'false'}"
+        )
+        self.trigger_disable_balancer = None
+        new_func_config = None
+
+        with self.eeprom():
+            func_config = self.read_serial_data_llt(readCmd(REG_FUNC_CONFIG))
+            if func_config:
+                self.func_config = unpack_from(">H", func_config)[0]
+                balancer_enabled = self.func_config & FUNC_BALANCE_EN
+                # Balance is enabled, force disable OR balancer is disabled and force enable
+                if (balancer_enabled != 0 and disable_balancer) or (
+                    balancer_enabled == 0 and not disable_balancer
+                ):
+                    new_func_config = self.func_config ^ FUNC_BALANCE_EN
+
+        if new_func_config:
+            new_func_config_bytes = pack(">H", new_func_config)
+            with self.eeprom(writable=True):
+                reply = self.read_serial_data_llt(
+                    writeCmd(REG_FUNC_CONFIG, new_func_config_bytes)
+                )
+                if reply is False:
+                    logger.error("write force disable balancer failed")
+                    return False
+                else:
+                    self.func_config = new_func_config
+                    self.balance_fet = (self.func_config & FUNC_BALANCE_EN) != 0
+
+        return True
+
     def refresh_data(self):
-        result = self.read_gen_data()
-        result = result and self.read_cell_data()
-        return result
+        self.write_charge_discharge_mos()
+        self.write_balancer()
+        return self.read_gen_data() and self.read_cell_data()
 
     def to_protection_bits(self, byte_data):
         tmp = bin(byte_data)[2:].rjust(13, utils.zero_char)
@@ -381,7 +531,7 @@ def to_fet_bits(self, byte_data):
     def read_gen_data(self):
         gen_data = self.read_serial_data_llt(self.command_general)
         # check if connect success
-        if gen_data is False or len(gen_data) < 27:
+        if gen_data is False or len(gen_data) < 23:
             return False
 
         (
@@ -402,7 +552,10 @@ def read_gen_data(self):
         ) = unpack_from(">HhHHHHhHHBBBBB", gen_data)
         self.voltage = voltage / 100
         self.current = current / 100
-        self.soc = round(100 * capacity_remain / capacity, 2)
+        # https://github.com/Louisvdw/dbus-serialbattery/issues/769#issuecomment-1720805325
+        if not self.cycle_capacity or self.cycle_capacity < capacity_remain:
+            self.cycle_capacity = capacity
+        self.soc = round(100 * capacity_remain / self.cycle_capacity, 2)
         self.capacity_remain = capacity_remain / 100
         self.capacity = capacity / 100
         self.to_cell_bits(balance, balance2)
@@ -416,6 +569,13 @@ def read_gen_data(self):
 
         # 0 = MOS, 1 = temp 1, 2 = temp 2
         for t in range(self.temp_sensors):
+            if len(gen_data) < 23 + (2 * t) + 2:
+                logger.warn(
+                    "Expected %d temperature sensors, but received only %d sensor readings!",
+                    self.temp_sensors,
+                    t,
+                )
+                return True
             temp1 = unpack_from(">H", gen_data, 23 + (2 * t))[0]
             self.to_temp(t, utils.kelvin_to_celsius(temp1 / 10))
 
@@ -450,13 +610,13 @@ def read_hardware_data(self):
 
     @staticmethod
     def validate_packet(data):
-        if not data:
-            return False
-
         if data is False:
             return False
 
         start, op, status, payload_length = unpack_from("BBBB", data)
+
+        logger.debug("bytearray: " + utils.bytearray_to_string(data))
+
         if start != 0xDD:
             logger.error(
                 ">>> ERROR: Invalid response packet. Expected begin packet character 0xDD"
diff --git a/etc/dbus-serialbattery/bms/lltjbd_ble.py b/etc/dbus-serialbattery/bms/lltjbd_ble.py
index de995492..449e7d0b 100644
--- a/etc/dbus-serialbattery/bms/lltjbd_ble.py
+++ b/etc/dbus-serialbattery/bms/lltjbd_ble.py
@@ -2,11 +2,15 @@
 import asyncio
 import atexit
 import functools
+import os
 import threading
+import sys
 from asyncio import CancelledError
+from time import sleep
 from typing import Union, Optional
 from utils import logger
 from bleak import BleakClient, BleakScanner, BLEDevice
+from bleak.exc import BleakDBusError
 from bms.lltjbd import LltJbdProtection, LltJbd
 
 BLE_SERVICE_UUID = "0000ff00-0000-1000-8000-00805f9b34fb"
@@ -55,25 +59,72 @@ async def bt_main_loop(self):
             self.device = await BleakScanner.find_device_by_address(
                 self.address, cb=dict(use_bdaddr=True)
             )
-        except Exception as e:
-            logger.error(">>> ERROR: Bluetooth stack failed.", e)
+
+        except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"BleakScanner(): Exception occurred: {repr(exception_object)} of type {exception_type} "
+                f"in {file} line #{line}"
+            )
             self.device = None
             await asyncio.sleep(0.5)
+            # allow the bluetooth connection to recover
+            sleep(5)
 
         if not self.device:
             self.run = False
             return
 
-        async with BleakClient(
-            self.device, disconnected_callback=self.on_disconnect
-        ) as client:
-            self.bt_client = client
-            self.bt_loop = asyncio.get_event_loop()
-            self.response_queue = asyncio.Queue()
-            self.ready_event.set()
-            while self.run and client.is_connected and self.main_thread.is_alive():
-                await asyncio.sleep(0.1)
-        self.bt_loop = None
+        try:
+            async with BleakClient(
+                self.device, disconnected_callback=self.on_disconnect
+            ) as client:
+                self.bt_client = client
+                self.bt_loop = asyncio.get_event_loop()
+                self.response_queue = asyncio.Queue()
+                self.ready_event.set()
+                while self.run and client.is_connected and self.main_thread.is_alive():
+                    await asyncio.sleep(0.1)
+            self.bt_loop = None
+
+        # Exception occurred: TimeoutError() of type <class 'asyncio.exceptions.TimeoutError'>
+        except asyncio.exceptions.TimeoutError:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"BleakClient(): asyncio.exceptions.TimeoutError: {repr(exception_object)} of type {exception_type} "
+                f"in {file} line #{line}"
+            )
+            # needed?
+            self.run = False
+            return
+
+        except TimeoutError:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"BleakClient(): TimeoutError: {repr(exception_object)} of type {exception_type} "
+                f"in {file} line #{line}"
+            )
+            # needed?
+            self.run = False
+            return
+
+        except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"BleakClient(): Exception occurred: {repr(exception_object)} of type {exception_type} "
+                f"in {file} line #{line}"
+            )
+            # needed?
+            self.run = False
+            return
 
     def background_loop(self):
         while self.run and self.main_thread.is_alive():
@@ -110,8 +161,13 @@ def test_connection(self):
                 result = super().test_connection()
             if not result:
                 logger.error("No BMS found at " + self.address)
-        except Exception as err:
-            logger.error(f"Unexpected {err=}, {type(err)=}")
+        except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+            )
             result = False
 
         return result
@@ -154,8 +210,23 @@ async def async_read_serial_data_llt(self, command):
         except asyncio.TimeoutError:
             logger.error(">>> ERROR: No reply - returning")
             return False
-        except Exception as e:
-            logger.error(">>> ERROR: No reply - returning", e)
+        except BleakDBusError:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"BleakDBusError: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+            )
+            self.reset_bluetooth()
+            return False
+        except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+            )
+            self.reset_bluetooth()
             return False
 
     def read_serial_data_llt(self, command):
@@ -165,19 +236,54 @@ def read_serial_data_llt(self, command):
             data = asyncio.run(self.async_read_serial_data_llt(command))
             return self.validate_packet(data)
         except CancelledError as e:
-            logger.error(">>> ERROR: No reply - canceled - returning", e)
+            logger.error(">>> ERROR: No reply - canceled - returning")
+            logger.error(e)
             return False
-        except Exception as e:
-            logger.error(">>> ERROR: No reply - returning", e)
+        # except Exception as e:
+        #     logger.error(">>> ERROR: No reply - returning")
+        #     logger.error(e)
+        #     return False
+        except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+            )
             return False
 
+    def reset_bluetooth(self):
+        logger.error("Reset of system Bluetooth daemon triggered")
+        self.bt_loop = False
+
+        # process kill is needed, since the service/bluetooth driver is probably freezed
+        # os.system('pkill -f "bluetoothd"')
+        # stop will not work, if service/bluetooth driver is stuck
+        os.system("/etc/init.d/bluetooth stop")
+        sleep(2)
+        os.system("rfkill block bluetooth")
+        os.system("rfkill unblock bluetooth")
+        os.system("/etc/init.d/bluetooth start")
+        logger.error("System Bluetooth daemon should have been restarted")
+        sleep(5)
+        sys.exit(1)
 
-if __name__ == "__main__":
-    import sys
 
+if __name__ == "__main__":
     bat = LltJbd_Ble("Foo", -1, sys.argv[1])
     if not bat.test_connection():
         logger.error(">>> ERROR: Unable to connect")
     else:
+        # Allow to change charge / discharge FET
+        bat.control_allow_charge = True
+        bat.control_allow_discharge = True
+
+        bat.trigger_disable_balancer = True
+        bat.trigger_force_disable_charge = True
+        bat.trigger_force_disable_discharge = True
+        bat.refresh_data()
+        bat.trigger_disable_balancer = False
+        bat.trigger_force_disable_charge = False
+        bat.trigger_force_disable_discharge = False
         bat.refresh_data()
         bat.get_settings()
diff --git a/etc/dbus-serialbattery/bms/mnb.py b/etc/dbus-serialbattery/bms/mnb.py
index 84365866..ac95608d 100644
--- a/etc/dbus-serialbattery/bms/mnb.py
+++ b/etc/dbus-serialbattery/bms/mnb.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
 
-# disable MNB battery by default
-# https://github.com/Louisvdw/dbus-serialbattery/commit/65241cbff36feb861ff43dbbcfb2b495f14a01ce
-# remove duplicate MNB lines
-# https://github.com/Louisvdw/dbus-serialbattery/commit/23afec33c2fd87fd4d4c53516f0a25f290643c82
+# # MNB is disabled by default
+# can be enabled by specifying it in the BMS_TYPE setting in the "config.ini"
+# https://github.com/Louisvdw/dbus-serialbattery/issues/590
+# https://community.victronenergy.com/comments/231924/view.html
 
 from battery import Protection, Battery, Cell
 from utils import logger
diff --git a/etc/dbus-serialbattery/bms/renogy.py b/etc/dbus-serialbattery/bms/renogy.py
index acfe2335..e920a771 100644
--- a/etc/dbus-serialbattery/bms/renogy.py
+++ b/etc/dbus-serialbattery/bms/renogy.py
@@ -48,8 +48,7 @@ def test_connection(self):
         try:
             result = self.read_gen_data()
             # get first data to show in startup log
-            if result:
-                self.refresh_data()
+            result = result and self.refresh_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
@@ -145,6 +144,8 @@ def read_cell_data(self):
                 self.cells[c].voltage = 0
         return True
 
+    """
+    # Did not found who changed this. "command_env_temp_count" is missing
     def read_temp_data(self):
         # Check to see how many Enviromental Temp Sensors this battery has, it may have none.
         num_env_temps = self.read_serial_data_renogy(self.command_env_temp_count)
@@ -172,6 +173,17 @@ def read_temp_data(self):
             logger.info("temp2 = %s °C", temp2)
 
         return True
+    """
+
+    def read_temp_data(self):
+        temp1 = self.read_serial_data_renogy(self.command_bms_temp1)
+        temp2 = self.read_serial_data_renogy(self.command_bms_temp2)
+        if temp1 is False:
+            return False
+        self.temp1 = unpack(">H", temp1)[0] / 10
+        self.temp2 = unpack(">H", temp2)[0] / 10
+
+        return True
 
     def read_bms_config(self):
         return True
diff --git a/etc/dbus-serialbattery/bms/seplos.py b/etc/dbus-serialbattery/bms/seplos.py
index b7c9a2e1..0a0c3fe2 100644
--- a/etc/dbus-serialbattery/bms/seplos.py
+++ b/etc/dbus-serialbattery/bms/seplos.py
@@ -115,7 +115,6 @@ def refresh_data(self):
         # This will be called for every iteration (self.poll_interval)
         # Return True if success, False for failure
         result_status = self.read_status_data()
-        # sleep(0.5)
         result_alarm = self.read_alarm_data()
 
         return result_status and result_alarm
@@ -129,15 +128,20 @@ def decode_alarm_byte(data_byte: int, alarm_bit: int, warn_bit: int):
         return Protection.OK
 
     def read_alarm_data(self):
+        logger.debug("read alarm data")
         data = self.read_serial_data_seplos(
             self.encode_cmd(address=0x00, cid2=self.COMMAND_ALARM, info=b"01")
         )
-        # check if connection success
-        if data is False:
+        # check if we could successfully read data and we have the expected length of 98 bytes
+        if data is False or len(data) != 98:
             return False
 
-        logger.debug("alarm info raw {}".format(data))
-        return self.decode_alarm_data(bytes.fromhex(data.decode("ascii")))
+        try:
+            logger.debug("alarm info raw {}".format(data))
+            return self.decode_alarm_data(bytes.fromhex(data.decode("ascii")))
+        except (ValueError, UnicodeDecodeError) as e:
+            logger.warning("could not hex-decode raw alarm data", exc_info=e)
+            return False
 
     def decode_alarm_data(self, data: bytes):
         logger.debug("alarm info decoded {}".format(data))
@@ -191,14 +195,21 @@ def decode_alarm_data(self, data: bytes):
 
     def read_status_data(self):
         logger.debug("read status data")
+
         data = self.read_serial_data_seplos(
             self.encode_cmd(address=0x00, cid2=0x42, info=b"01")
         )
 
-        # check if connection success
-        if data is False:
+        # check if reading data was successful and has the expected data length of 150 byte
+        if data is False or len(data) != 150:
+            return False
+
+        if not self.decode_status_data(data):
             return False
 
+        return True
+
+    def decode_status_data(self, data):
         cell_count_offset = 4
         voltage_offset = 6
         temps_offset = 72
@@ -218,7 +229,6 @@ def read_status_data(self):
                 ) / 10
                 self.cells[i].temp = temp
                 logger.debug("Temp cell[{}]={}°C".format(i, temp))
-
         self.temp1 = (
             Seplos.int_from_2byte_hex_ascii(data, temps_offset + 4 * 4) - 2731
         ) / 10
@@ -234,7 +244,6 @@ def read_status_data(self):
         self.soc = Seplos.int_from_2byte_hex_ascii(data, offset=114) / 10
         self.cycles = Seplos.int_from_2byte_hex_ascii(data, offset=122)
         self.hardware_version = "Seplos BMS {} cells".format(self.cell_count)
-
         logger.debug("Current = {}A , Voltage = {}V".format(self.current, self.voltage))
         logger.debug(
             "Capacity = {}/{}Ah , SOC = {}%".format(
@@ -297,7 +306,9 @@ def read_serial_data_seplos(self, command):
             return_data = data[length_pos + 3 : -5]
             info_length = Seplos.int_from_2byte_hex_ascii(b"0" + data[length_pos:], 0)
             logger.debug(
-                "return info data of length {} : {}".format(info_length, return_data)
+                "returning info data of length {}, info_length is {} : {}".format(
+                    len(return_data), info_length, return_data
+                )
             )
 
             return return_data
diff --git a/etc/dbus-serialbattery/bms/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py
index 7a9d9fdb..8e4e900e 100755
--- a/etc/dbus-serialbattery/bms/sinowealth.py
+++ b/etc/dbus-serialbattery/bms/sinowealth.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
-# disable Sinowealth by default as it causes other issues but can be enabled manually
+# Sinowealth is disabled by default as it causes issues with other devices
+# can be enabled by specifying it in the BMS_TYPE setting in the "config.ini"
 # https://github.com/Louisvdw/dbus-serialbattery/commit/7aab4c850a5c8d9c205efefc155fe62bb527da8e
 
 from battery import Battery, Cell
@@ -44,8 +45,8 @@ def test_connection(self):
         result = False
         try:
             result = self.read_status_data()
-            result = result and self.read_remaining_capacity()
-            result = result and self.read_pack_config_data()
+            result = result and self.get_settings()
+            result = result and self.refresh_data()
         except Exception as err:
             logger.error(f"Unexpected {err=}, {type(err)=}")
             result = False
@@ -64,9 +65,10 @@ def get_settings(self):
         self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count
 
         self.hardware_version = "Daly/Sinowealth BMS " + str(self.cell_count) + " cells"
-        logger.info(self.hardware_version)
+        logger.debug(self.hardware_version)
 
-        self.read_capacity()
+        if not self.read_capacity():
+            return False
 
         for c in range(self.cell_count):
             self.cells.append(Cell(False))
@@ -95,7 +97,7 @@ def read_status_data(self):
         # [1]     -       FAST_DSG MID_DSG  SLOW_DSG DSGING   CHGING  DSGMOS  CHGMOS
         self.discharge_fet = bool(status_data[1] >> 1 & int(1))  # DSGMOS
         self.charge_fet = bool(status_data[1] & int(1))  # CHGMOS
-        logger.info(
+        logger.debug(
             ">>> INFO: Discharge fet: %s, charge fet: %s",
             self.discharge_fet,
             self.charge_fet,
@@ -145,8 +147,9 @@ def read_soc(self):
         # check if connection success
         if soc_data is False:
             return False
-        logger.info(">>> INFO: current SOC: %u", soc_data[1])
-        self.soc = soc_data[1]
+        logger.debug(">>> INFO: current SOC: %u", soc_data[1])
+        soc = soc_data[1]
+        self.soc = soc
         return True
 
     def read_cycle_count(self):
@@ -156,7 +159,7 @@ def read_cycle_count(self):
         if cycle_count is False:
             return False
         self.cycles = int(unpack_from(">H", cycle_count[:2])[0])
-        logger.info(">>> INFO: current cycle count: %u", self.cycles)
+        logger.debug(">>> INFO: current cycle count: %u", self.cycles)
         return True
 
     def read_pack_voltage(self):
@@ -165,8 +168,8 @@ def read_pack_voltage(self):
             return False
         pack_voltage = unpack_from(">H", pack_voltage_data[:-1])
         pack_voltage = pack_voltage[0] / 1000
-        logger.info(">>> INFO: current pack voltage: %f", pack_voltage)
         self.voltage = pack_voltage
+        logger.debug(">>> INFO: current pack voltage: %f", self.voltage)
         return True
 
     def read_pack_current(self):
@@ -175,7 +178,8 @@ def read_pack_current(self):
             return False
         current = unpack_from(">i", current_data[:-1])
         current = current[0] / 1000
-        logger.info(">>> INFO: current pack current: %f", current)
+        logger.debug(">>> INFO: current pack current: %f", current)
+
         self.current = current
         return True
 
@@ -187,7 +191,9 @@ def read_remaining_capacity(self):
             return False
         remaining_capacity = unpack_from(">i", remaining_capacity_data[:-1])
         self.capacity_remain = remaining_capacity[0] / 1000
-        logger.info(">>> INFO: remaining battery capacity: %f Ah", self.capacity_remain)
+        logger.debug(
+            ">>> INFO: remaining battery capacity: %f Ah", self.capacity_remain
+        )
         return True
 
     def read_capacity(self):
@@ -195,8 +201,10 @@ def read_capacity(self):
         if capacity_data is False:
             return False
         capacity = unpack_from(">i", capacity_data[:-1])
-        logger.info(">>> INFO: Battery capacity: %f Ah", capacity[0] / 1000)
-        self.capacity = capacity[0] / 1000
+        capacity = capacity[0] / 1000
+        logger.debug(">>> INFO: Battery capacity: %f Ah", capacity)
+
+        self.capacity = capacity
         return True
 
     def read_pack_config_data(self):
@@ -210,12 +218,12 @@ def read_pack_config_data(self):
         if self.cell_count < 1 or self.cell_count > 32:
             logger.error(">>> ERROR: No valid cell count returnd: %u", self.cell_count)
             return False
-        logger.info(">>> INFO: Number of cells: %u", self.cell_count)
+        logger.debug(">>> INFO: Number of cells: %u", self.cell_count)
         temp_sens_mask = int(~(1 << 6))
         self.temp_sensors = (
             1 if (pack_config_data[1] & temp_sens_mask) else 2
         )  # one means two
-        logger.info(">>> INFO: Number of temperatur sensors: %u", self.temp_sensors)
+        logger.debug(">>> INFO: Number of temperatur sensors: %u", self.temp_sensors)
         return True
 
     def read_cell_data(self):
@@ -235,7 +243,7 @@ def read_cell_voltage(self, cell_index):
         cell_voltage = unpack_from(">H", cell_data[:-1])
         cell_voltage = cell_voltage[0] / 1000
 
-        logger.info(">>> INFO: Cell %u voltage: %f V", cell_index, cell_voltage)
+        logger.debug(">>> INFO: Cell %u voltage: %f V", cell_index, cell_voltage)
         return cell_voltage
 
     def read_temperature_data(self):
@@ -248,7 +256,7 @@ def read_temperature_data(self):
 
         temp_ext1 = unpack_from(">H", temp_ext1_data[:-1])
         self.to_temp(1, kelvin_to_celsius(temp_ext1[0] / 10))
-        logger.info(">>> INFO: BMS external temperature 1: %f C", self.temp1)
+        logger.debug(">>> INFO: BMS external temperature 1: %f C", self.temp1)
 
         if self.temp_sensors == 2:
             temp_ext2_data = self.read_serial_data_sinowealth(self.command_temp_ext2)
@@ -257,7 +265,7 @@ def read_temperature_data(self):
 
             temp_ext2 = unpack_from(">H", temp_ext2_data[:-1])
             self.to_temp(2, kelvin_to_celsius(temp_ext2[0] / 10))
-            logger.info(">>> INFO: BMS external temperature 2: %f C", self.temp2)
+            logger.debug(">>> INFO: BMS external temperature 2: %f C", self.temp2)
 
         # Internal temperature 1 seems to give a logical value
         temp_int1_data = self.read_serial_data_sinowealth(self.command_temp_int1)
@@ -265,7 +273,7 @@ def read_temperature_data(self):
             return False
 
         temp_int1 = unpack_from(">H", temp_int1_data[:-1])
-        logger.info(
+        logger.debug(
             ">>> INFO: BMS internal temperature 1: %f C",
             kelvin_to_celsius(temp_int1[0] / 10),
         )
@@ -276,7 +284,7 @@ def read_temperature_data(self):
             return False
 
         temp_int2 = unpack_from(">H", temp_int2_data[:-1])
-        logger.info(
+        logger.debug(
             ">>> INFO: BMS internal temperature 2: %f C",
             kelvin_to_celsius(temp_int2[0] / 10),
         )
diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini
index e7c967ff..af546e91 100644
--- a/etc/dbus-serialbattery/config.default.ini
+++ b/etc/dbus-serialbattery/config.default.ini
@@ -1,5 +1,12 @@
 [DEFAULT]
 
+; --------- Set logging level ---------
+; ERROR: Only errors are logged
+; WARNING: Errors and warnings are logged
+; INFO: Errors, warnings and info messages are logged
+; DEBUG: Errors, warnings, info and debug messages are logged
+LOGGING = INFO
+
 ; --------- Battery Current limits ---------
 MAX_BATTERY_CHARGE_CURRENT    = 50.0
 MAX_BATTERY_DISCHARGE_CURRENT = 60.0
@@ -8,19 +15,55 @@ MAX_BATTERY_DISCHARGE_CURRENT = 60.0
 ; Description: Cell min/max voltages which are used to calculate the min/max battery voltage
 ; Example: 16 cells * 3.45V/cell = 55.2V max charge voltage. 16 cells * 2.90V = 46.4V min discharge voltage
 MIN_CELL_VOLTAGE   = 2.900
-; Max voltage can seen as absorption voltage
+; Max voltage (can seen as absorption voltage)
 MAX_CELL_VOLTAGE   = 3.450
+; Float voltage (can be seen as resting voltage)
 FLOAT_CELL_VOLTAGE = 3.375
 
+; --------- SOC reset voltage ---------
+; Description: May be needed to reset the SoC to 100% once in a while for some BMS, because of SoC drift.
+;              Specify the cell voltage where the SoC should be reset to 100% by the BMS.
+;                - JKBMS: SoC is reset to 100% if one cell reaches OVP (over voltage protection) voltage
+;              As you have to adopt this value to your system, I reccomend to start with
+;              OVP voltage - 0.030 (see Example).
+;                - Try to increase (add) by 0.005 in steps, if the system does not switch to float mode, even if
+;                  the target voltage SOC_RESET_VOLTAGE * CELL_COUNT is reached.
+;                - Try to decrease (lower) by 0.005 in steps, if the system hits the OVP too fast, before all
+;                  cells could be balanced and the system goes into protection mode multiple times.
+; Example: If OVP is 3.650, then start with 3.620 and increase/decrease by 0.005
+; Note: The value has to be higher as the MAX_CELL_VOLTAGE
+SOC_RESET_VOLTAGE  = 3.650
+; Specify after how many days the soc reset voltage should be reached again
+; The timer is reset when the soc reset voltage is reached
+; Leave empty if you don't want to use this
+; Example: Value is set to 15
+; day 1: soc reset reached once
+; day 16: soc reset reached twice
+; day 31: soc reset not reached since it's very cloudy
+; day 34: soc reset reached since the sun came out
+; day 49: soc reset reached again, since last time it took 3 days to reach soc reset voltage
+SOC_RESET_AFTER_DAYS =
+
 ; --------- Bluetooth BMS ---------
-; Description: List the Bluetooth BMS here that you want to install
+; Description: Specify the Bluetooth BMS and it's MAC address that you want to install. Leave emty to disable
 ; -- Available Bluetooth BMS:
 ; Jkbms_Ble, LltJbd_Ble
-; Example:
-;     1 BMS: Jkbms_Ble C8:47:8C:00:00:00
-;     3 BMS: Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22
+; Example for one BMS:
+; BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00
+; Example for multiple BMS:
+; BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22
 BLUETOOTH_BMS =
 
+; --------- CAN BMS ---------
+; Description: Specify the CAN port(s) where the BMS is connected to. Leave empty to disable
+; -- Available CAN BMS:
+; Daly_Can, Jkbms_Can
+; Example for one CAN port:
+; CAN_PORT = can0
+; Example for multiple CAN ports:
+; CAN_PORT = can0, can8, can9
+CAN_PORT =
+
 ; --------- BMS disconnect behaviour ---------
 ; Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the
 ;              BMS on purpose, then you have to restart the driver/system to reset the block.
@@ -60,11 +103,11 @@ LINEAR_RECALCULATION_ON_PERC_CHANGE = 5
 ;                      it switches back to max voltage.
 ; Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to
 ;          float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is
-;          once below 90%
+;          once below 80%
 ;          OR
 ;          The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float
 ;          voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of
-;          55.2V again if max cell difference is above 0.080V or SoC below 90%.
+;          55.2V again if max cell difference is above 0.080V or SoC below 80%.
 ; Charge voltage control management enable (True/False).
 CVCM_ENABLE = True
 
@@ -76,11 +119,15 @@ CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL = 0.010
 ; e.g. 3.2 V * 5 / 100 = 0.160 V
 CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = 0.080
 
-; -- CVL reset based on SoC option (step mode)
-; Specify how long the max voltage should be kept, if reached then switch to float voltage
+; -- CVL reset based on SoC option (step mode & linear mode)
+; Specify how long the max voltage should be kept
+;     Step mode: If reached then switch to float voltage
+;     Linear mode: If cells are balanced keep max voltage for further MAX_VOLTAGE_TIME_SEC seconds
 MAX_VOLTAGE_TIME_SEC = 900
-; Specify SoC where CVL limit is reset to max voltage, if value gets below
-SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90
+; Specify SoC where CVL limit is reset to max voltage
+;     Step mode: If SoC gets below
+;     Linear mode: If cells are unbalanced or if SoC gets below
+SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 80
 
 
 ; --------- Cell Voltage Current limitation (affecting CCL/DCL) ---------
@@ -131,7 +178,7 @@ CCCM_SOC_ENABLE = True
 ; Discharge current control management enable (True/False).
 DCCM_SOC_ENABLE = True
 
-; Charge current soc limits
+; Charge current SoC limits
 CC_SOC_LIMIT1 = 98
 CC_SOC_LIMIT2 = 95
 CC_SOC_LIMIT3 = 91
@@ -141,7 +188,7 @@ CC_CURRENT_LIMIT1_FRACTION = 0.1
 CC_CURRENT_LIMIT2_FRACTION = 0.3
 CC_CURRENT_LIMIT3_FRACTION = 0.5
 
-; Discharge current soc limits
+; Discharge current SoC limits
 DC_SOC_LIMIT1 = 10
 DC_SOC_LIMIT2 = 20
 DC_SOC_LIMIT3 = 30
@@ -183,14 +230,28 @@ TIME_TO_SOC_INC_FROM = False
 
 
 ; --------- Additional settings ---------
-; Specify only one BMS type to load else leave empty to try to load all available
+; Specify one or more BMS types to load else leave empty to try to load all available
 ; -- Available BMS:
 ; Daly, Ecs, HeltecModbus, HLPdataBMS4S, Jkbms, Lifepower, LltJbd, Renogy, Seplos
-; -- Available BMS, but disabled by default:
-; https://louisvdw.github.io/dbus-serialbattery/general/install#how-to-enable-a-disabled-bms
-; Ant, MNB, Sinowealth
+; -- Available BMS, but disabled by default (just enter one or more below and it will be enabled):
+; ANT, MNB, Sinowealth
 BMS_TYPE =
 
+; Exclute this serial devices from the driver startup
+; Example: /dev/ttyUSB2, /dev/ttyUSB4
+EXCLUDED_DEVICES =
+
+; Enter custom battery names here or change it over the GUI
+; Example:
+;     /dev/ttyUSB0:My first battery
+;     /dev/ttyUSB0:My first battery,/dev/ttyUSB1:My second battery
+CUSTOM_BATTERY_NAMES =
+
+; Auto reset SoC
+; If on, then SoC is reset to 100%, if the value switches from absorption to float voltage
+; Currently only working for Daly BMS and JK BMS BLE
+AUTO_RESET_SOC = True
+
 ; Publish the config settings to the dbus path "/Info/Config/"
 PUBLISH_CONFIG_VALUES = 1
 
@@ -254,16 +315,18 @@ LIPRO_CELL_COUNT = 15
 HELTEC_MODBUS_ADDR = 1
 
 
-; --------- Battery monitor specific settings ---------
-; If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported
-; from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage,
-; since max voltage is never reached.
+; --------- Voltage drop ---------
+; If you have a voltage drop between the BMS and the charger because of wire size or length
+; then you can specify the voltage drop here. The driver will then add the voltage drop
+; to the calculated CVL to compensate.
 ; Example:
 ;     cell count: 16
 ;     MAX_CELL_VOLTAGE = 3.45
 ;     max voltage calculated = 16 * 3.45 = 55.20
-;     CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS
-;     now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures
-;     55.05 V the max voltage is never reached for the driver and max voltage is kept forever.
-;     Set VOLTAGE_DROP to 0.15
+;     CVL is set to 55.20 V and the battery is now charged until the charger reaches 55.20 V.
+;     The BMS now measures 55.05 V since there is a voltage drop of 0.15 V on the cable.
+;     Since the dbus-serialbattery reads the voltage of 55.05 V from the BMS the max voltage
+;     of 55.20 V is never reached and max voltage is kept forever.
+;     By setting the VOLTAGE_DROP to 0.15 V the voltage on the charger is increased and the
+;     target voltage on the BMS is reached.
 VOLTAGE_DROP = 0.00
diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py
index 4bca9b35..69c90437 100644
--- a/etc/dbus-serialbattery/dbus-serialbattery.py
+++ b/etc/dbus-serialbattery/dbus-serialbattery.py
@@ -5,7 +5,6 @@
 from time import sleep
 from dbus.mainloop.glib import DBusGMainLoop
 
-# from threading import Thread  ## removed with https://github.com/Louisvdw/dbus-serialbattery/pull/582
 import sys
 
 if sys.version_info.major == 2:
@@ -32,9 +31,13 @@
 from bms.renogy import Renogy
 from bms.seplos import Seplos
 
-# from bms.ant import Ant
-# from bms.mnb import MNB
-# from bms.sinowealth import Sinowealth
+# enabled only if explicitly set in config under "BMS_TYPE"
+if "ANT" in utils.BMS_TYPE:
+    from bms.ant import ANT
+if "MNB" in utils.BMS_TYPE:
+    from bms.mnb import MNB
+if "Sinowealth" in utils.BMS_TYPE:
+    from bms.sinowealth import Sinowealth
 
 supported_bms_types = [
     {"bms": Daly, "baud": 9600, "address": b"\x40"},
@@ -48,21 +51,30 @@
     {"bms": Renogy, "baud": 9600, "address": b"\x30"},
     {"bms": Renogy, "baud": 9600, "address": b"\xF7"},
     {"bms": Seplos, "baud": 19200},
-    # {"bms": Ant, "baud": 19200},
-    # {"bms": MNB, "baud": 9600},
-    # {"bms": Sinowealth},
 ]
+
+# enabled only if explicitly set in config under "BMS_TYPE"
+if "ANT" in utils.BMS_TYPE:
+    supported_bms_types.append({"bms": ANT, "baud": 19200})
+if "MNB" in utils.BMS_TYPE:
+    supported_bms_types.append({"bms": MNB, "baud": 9600})
+if "Sinowealth" in utils.BMS_TYPE:
+    supported_bms_types.append({"bms": Sinowealth, "baud": 9600})
+
 expected_bms_types = [
     battery_type
     for battery_type in supported_bms_types
-    if battery_type["bms"].__name__ == utils.BMS_TYPE or utils.BMS_TYPE == ""
+    if battery_type["bms"].__name__ in utils.BMS_TYPE or len(utils.BMS_TYPE) == 0
 ]
 
-print("")
+logger.info("")
 logger.info("Starting dbus-serialbattery")
 
 
 def main():
+    # NameError: free variable 'expected_bms_types' referenced before assignment in enclosing scope
+    global expected_bms_types
+
     def poll_battery(loop):
         helper.publish_battery(loop)
         return True
@@ -70,19 +82,33 @@ def poll_battery(loop):
     def get_battery(_port) -> Union[Battery, None]:
         # all the different batteries the driver support and need to test for
         # try to establish communications with the battery 3 times, else exit
-        count = 3
-        while count > 0:
+        retry = 1
+        retries = 3
+        while retry <= retries:
+            logger.info(
+                "-- Testing BMS: " + str(retry) + " of " + str(retries) + " rounds"
+            )
             # create a new battery object that can read the battery and run connection test
             for test in expected_bms_types:
                 # noinspection PyBroadException
                 try:
-                    logger.info("Testing " + test["bms"].__name__)
+                    logger.info(
+                        "Testing "
+                        + test["bms"].__name__
+                        + (
+                            ' at address "'
+                            + utils.bytearray_to_string(test["address"])
+                            + '"'
+                            if "address" in test
+                            else ""
+                        )
+                    )
                     batteryClass = test["bms"]
                     baud = test["baud"]
                     battery: Battery = batteryClass(
                         port=_port, baud=baud, address=test.get("address")
                     )
-                    if battery.test_connection():
+                    if battery.test_connection() and battery.validate_data():
                         logger.info(
                             "Connection established to " + battery.__class__.__name__
                         )
@@ -90,9 +116,19 @@ def get_battery(_port) -> Union[Battery, None]:
                 except KeyboardInterrupt:
                     return None
                 except Exception:
+                    (
+                        exception_type,
+                        exception_object,
+                        exception_traceback,
+                    ) = sys.exc_info()
+                    file = exception_traceback.tb_frame.f_code.co_filename
+                    line = exception_traceback.tb_lineno
+                    logger.error(
+                        f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+                    )
                     # Ignore any malfunction test_function()
                     pass
-            count -= 1
+            retry += 1
             sleep(0.5)
 
         return None
@@ -100,11 +136,21 @@ def get_battery(_port) -> Union[Battery, None]:
     def get_port() -> str:
         # Get the port we need to use from the argument
         if len(sys.argv) > 1:
-            return sys.argv[1]
+            port = sys.argv[1]
+            if port not in utils.EXCLUDED_DEVICES:
+                return port
+            else:
+                logger.debug(
+                    "Stopping dbus-serialbattery: "
+                    + str(port)
+                    + " is excluded trough the config file"
+                )
+                sleep(60)
+                sys.exit(0)
         else:
             # just for MNB-SPI
             logger.info("No Port needed")
-            return "/dev/tty/USB9"
+            return "/dev/ttyUSB9"
 
     logger.info("dbus-serialbattery v" + str(utils.DRIVER_VERSION))
 
@@ -125,10 +171,35 @@ def get_port() -> str:
 
         class_ = eval(port)
         testbms = class_("", 9600, sys.argv[2])
-        if testbms.test_connection() is True:
+        if testbms.test_connection():
             logger.info("Connection established to " + testbms.__class__.__name__)
             battery = testbms
+    elif port.startswith("can"):
+        """
+        Import CAN classes only, if it's a can port, else the driver won't start due to missing python modules
+        This prevent problems when using the driver only with a serial connection
+        """
+        from bms.daly_can import Daly_Can
+        from bms.jkbms_can import Jkbms_Can
+
+        # only try CAN BMS on CAN port
+        supported_bms_types = [
+            {"bms": Daly_Can, "baud": 250000},
+            {"bms": Jkbms_Can, "baud": 250000},
+        ]
+
+        expected_bms_types = [
+            battery_type
+            for battery_type in supported_bms_types
+            if battery_type["bms"].__name__ in utils.BMS_TYPE
+            or len(utils.BMS_TYPE) == 0
+        ]
+
+        battery = get_battery(port)
     else:
+        # wait some seconds to be sure that the serial connection is ready
+        # else the error throw a lot of timeouts
+        sleep(16)
         battery = get_battery(port)
 
     # exit if no battery could be found
@@ -151,8 +222,12 @@ def get_port() -> str:
         logger.error("ERROR >>> Problem with battery set up at " + port)
         sys.exit(1)
 
-    # Poll the battery at INTERVAL and run the main loop
-    gobject.timeout_add(battery.poll_interval, lambda: poll_battery(mainloop))
+    # try using active callback on this battery
+    if not battery.use_callback(lambda: poll_battery(mainloop)):
+        # if not possible, poll the battery every poll_interval milliseconds
+        gobject.timeout_add(battery.poll_interval, lambda: poll_battery(mainloop))
+
+    # Run the main loop
     try:
         mainloop.run()
     except KeyboardInterrupt:
diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py
index 0cca3187..5e47fef4 100644
--- a/etc/dbus-serialbattery/dbushelper.py
+++ b/etc/dbus-serialbattery/dbushelper.py
@@ -2,7 +2,7 @@
 import sys
 import os
 import platform
-import dbus
+import dbus  # pyright: ignore[reportMissingImports]
 import traceback
 from time import time
 
@@ -14,8 +14,10 @@
         "/opt/victronenergy/dbus-systemcalc-py/ext/velib_python",
     ),
 )
-from vedbus import VeDbusService  # noqa: E402
-from settingsdevice import SettingsDevice  # noqa: E402
+from vedbus import VeDbusService  # noqa: E402 # pyright: ignore[reportMissingImports]
+from settingsdevice import (  # noqa: E402 # pyright: ignore[reportMissingImports]
+    SettingsDevice,
+)
 from utils import logger, publish_config_variables  # noqa: E402
 import utils  # noqa: E402
 
@@ -33,7 +35,7 @@ def __init__(self, battery):
         self.battery = battery
         self.instance = 1
         self.settings = None
-        self.error_count = 0
+        self.error = {"count": 0, "timestamp_first": None, "timestamp_last": None}
         self.block_because_disconnect = False
         self._dbusservice = VeDbusService(
             "com.victronenergy.battery."
@@ -54,32 +56,6 @@ def setup_instance(self):
                 0,
                 0,
             ],
-            # 'CellVoltageMin': [path + '/CellVoltageMin', 2.8, 0.0, 5.0],
-            # 'CellVoltageMax': [path + '/CellVoltageMax', 3.45, 0.0, 5.0],
-            # 'CellVoltageFloat': [path + '/CellVoltageFloat', 3.35, 0.0, 5.0],
-            # 'VoltageMaxTime': [path + '/VoltageMaxTime', 900, 0, 0],
-            # 'VoltageResetSocLimit': [path + '/VoltageResetSocLimit', 90, 0, 100],
-            # 'MaxChargeCurrent': [path + '/MaxCurrentCharge', 5, 0.0, 500],
-            # 'MaxDischargeCurrent': [path + '/MaxCurrentDischarge', 7, 0.0, 500],
-            # 'AllowDynamicChargeCurrent': [path + '/AllowDynamicChargeCurrent', 1, 0, 1],
-            # 'AllowDynamicDischargeCurrent': [path + '/AllowDynamicDischargeCurrent', 1, 0, 1],
-            # 'AllowDynamicChargeVoltage': [path + '/AllowDynamicChargeVoltage', 0, 0, 1],
-            # 'SocLowWarning': [path + '/SocLowWarning', 20, 0, 100],
-            # 'SocLowAlarm': [path + '/SocLowAlarm', 10, 0, 100],
-            # 'Capacity': [path + '/Capacity', '', 0, 500],
-            # 'EnableInvertedCurrent': [path + '/EnableInvertedCurrent', 0, 0, 1],
-            # 'CCMSocLimitCharge1': [path + '/CCMSocLimitCharge1', 98, 0, 100],
-            # 'CCMSocLimitCharge2': [path + '/CCMSocLimitCharge2', 95, 0, 100],
-            # 'CCMSocLimitCharge3': [path + '/CCMSocLimitCharge3', 91, 0, 100],
-            # 'CCMSocLimitDischarge1': [path + '/CCMSocLimitDischarge1', 10, 0, 100],
-            # 'CCMSocLimitDischarge2': [path + '/CCMSocLimitDischarge2', 20, 0, 100],
-            # 'CCMSocLimitDischarge3': [path + '/CCMSocLimitDischarge3', 30, 0, 100],
-            # 'CCMCurrentLimitCharge1': [path + '/CCMCurrentLimitCharge1', 5, 0, 100],
-            # 'CCMCurrentLimitCharge2': [path + '/CCMCurrentLimitCharge2', '', 0, 100],
-            # 'CCMCurrentLimitCharge3': [path + '/CCMCurrentLimitCharge3', '', 0, 100],
-            # 'CCMCurrentLimitDischarge1': [path + '/CCMCurrentLimitDischarge1', 5, 0, 100],
-            # 'CCMCurrentLimitDischarge2': [path + '/CCMCurrentLimitDischarge2', '', 0, 100],
-            # 'CCMCurrentLimitDischarge3': [path + '/CCMCurrentLimitDischarge3', '', 0, 100],
         }
 
         self.settings = SettingsDevice(get_bus(), settings, self.handle_changed_setting)
@@ -123,10 +99,13 @@ def setup_vedbus(self):
         self._dbusservice.add_path("/HardwareVersion", self.battery.hardware_version)
         self._dbusservice.add_path("/Connected", 1)
         self._dbusservice.add_path(
-            "/CustomName", self.battery.custom_name(), writeable=True
+            "/CustomName",
+            self.battery.custom_name(),
+            writeable=True,
+            onchangecallback=self.battery.custom_name_callback,
         )
         self._dbusservice.add_path(
-            "/Serial", self.battery.unique_identifier, writeable=True
+            "/Serial", self.battery.unique_identifier(), writeable=True
         )
         self._dbusservice.add_path(
             "/DeviceName", self.battery.custom_field, writeable=True
@@ -156,6 +135,7 @@ def setup_vedbus(self):
         )
 
         self._dbusservice.add_path("/Info/ChargeMode", None, writeable=True)
+        self._dbusservice.add_path("/Info/ChargeModeDebug", None, writeable=True)
         self._dbusservice.add_path("/Info/ChargeLimitation", None, writeable=True)
         self._dbusservice.add_path("/Info/DischargeLimitation", None, writeable=True)
 
@@ -230,9 +210,13 @@ def setup_vedbus(self):
         self._dbusservice.add_path("/System/MaxTemperatureCellId", None, writeable=True)
         self._dbusservice.add_path("/System/MOSTemperature", None, writeable=True)
         self._dbusservice.add_path("/System/Temperature1", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature1Name", None, writeable=True)
         self._dbusservice.add_path("/System/Temperature2", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature2Name", None, writeable=True)
         self._dbusservice.add_path("/System/Temperature3", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature3Name", None, writeable=True)
         self._dbusservice.add_path("/System/Temperature4", None, writeable=True)
+        self._dbusservice.add_path("/System/Temperature4Name", None, writeable=True)
         self._dbusservice.add_path(
             "/System/MaxCellVoltage",
             None,
@@ -331,6 +315,12 @@ def setup_vedbus(self):
             # Create TimeToGo item
             if utils.TIME_TO_GO_ENABLE:
                 self._dbusservice.add_path("/TimeToGo", None, writeable=True)
+                self._dbusservice.add_path(
+                    "/CurrentAvg",
+                    None,
+                    writeable=True,
+                    gettextcallback=lambda p, v: "{:0.2f}A".format(v),
+                )
 
             # Create TimeToSoc items
             if len(utils.TIME_TO_SOC_POINTS) > 0:
@@ -358,9 +348,10 @@ def publish_battery(self, loop):
         # This is called every battery.poll_interval milli second as set up per battery type to read and update the data
         try:
             # Call the battery's refresh_data function
-            success = self.battery.refresh_data()
-            if success:
-                self.error_count = 0
+            result = self.battery.refresh_data()
+            if result:
+                # reset error variables
+                self.error["count"] = 0
                 self.battery.online = True
 
                 # unblock charge/discharge, if it was blocked when battery went offline
@@ -368,9 +359,18 @@ def publish_battery(self, loop):
                     self.block_because_disconnect = False
 
             else:
-                self.error_count += 1
-                # If the battery is offline for more than 10 polls (polled every second for most batteries)
-                if self.error_count >= 10:
+                # update error variables
+                if self.error["count"] == 0:
+                    self.error["timestamp_first"] = int(time())
+                self.error["timestamp_last"] = int(time())
+                self.error["count"] += 1
+
+                time_since_first_error = (
+                    self.error["timestamp_last"] - self.error["timestamp_first"]
+                )
+
+                # if the battery did not update in 10 second, it's assumed to be offline
+                if time_since_first_error >= 10:
                     self.battery.online = False
                     self.battery.init_values()
 
@@ -378,8 +378,8 @@ def publish_battery(self, loop):
                     if utils.BLOCK_ON_DISCONNECT:
                         self.block_because_disconnect = True
 
-                # Has it completely failed
-                if self.error_count >= 60:
+                # if the battery did not update in 60 second, it's assumed to be completely failed
+                if time_since_first_error >= 60:
                     loop.quit()
 
             # This is to mannage CVCL
@@ -473,12 +473,20 @@ def publish_dbus(self):
         ] = self.battery.get_max_temp_id()
         self._dbusservice["/System/MOSTemperature"] = self.battery.get_mos_temp()
         self._dbusservice["/System/Temperature1"] = self.battery.temp1
+        self._dbusservice["/System/Temperature1Name"] = utils.TEMP_1_NAME
         self._dbusservice["/System/Temperature2"] = self.battery.temp2
+        self._dbusservice["/System/Temperature2Name"] = utils.TEMP_2_NAME
         self._dbusservice["/System/Temperature3"] = self.battery.temp3
+        self._dbusservice["/System/Temperature3Name"] = utils.TEMP_3_NAME
         self._dbusservice["/System/Temperature4"] = self.battery.temp4
+        self._dbusservice["/System/Temperature4Name"] = utils.TEMP_4_NAME
 
         # Voltage control
-        self._dbusservice["/Info/MaxChargeVoltage"] = self.battery.control_voltage
+        self._dbusservice["/Info/MaxChargeVoltage"] = (
+            round(self.battery.control_voltage + utils.VOLTAGE_DROP, 2)
+            if self.battery.control_voltage is not None
+            else None
+        )
 
         # Charge control
         self._dbusservice[
@@ -490,6 +498,7 @@ def publish_dbus(self):
 
         # Voltage and charge control info
         self._dbusservice["/Info/ChargeMode"] = self.battery.charge_mode
+        self._dbusservice["/Info/ChargeModeDebug"] = self.battery.charge_mode_debug
         self._dbusservice["/Info/ChargeLimitation"] = self.battery.charge_limitation
         self._dbusservice[
             "/Info/DischargeLimitation"
@@ -511,7 +520,15 @@ def publish_dbus(self):
         self._dbusservice[
             "/Alarms/LowCellVoltage"
         ] = self.battery.protection.voltage_cell_low
-        self._dbusservice["/Alarms/HighVoltage"] = self.battery.protection.voltage_high
+        # disable high voltage warning temporarly, if loading to bulk voltage and bulk voltage reached is 30 minutes ago
+        self._dbusservice["/Alarms/HighVoltage"] = (
+            self.battery.protection.voltage_high
+            if (
+                self.battery.soc_reset_requested is False
+                and self.battery.soc_reset_last_reached < int(time()) - (60 * 30)
+            )
+            else 0
+        )
         self._dbusservice["/Alarms/LowSoc"] = self.battery.protection.soc_low
         self._dbusservice[
             "/Alarms/HighChargeCurrent"
@@ -575,6 +592,30 @@ def publish_dbus(self):
 
         # Update TimeToGo and/or TimeToSoC
         try:
+            # calculate current average for the last 300 cycles
+            # if Time-To-Go or Time-To-SoC is enabled
+            if utils.TIME_TO_GO_ENABLE or len(utils.TIME_TO_SOC_POINTS) > 0:
+                if self.battery.current is not None:
+                    self.battery.current_avg_lst.append(self.battery.current)
+
+                # delete oldest value
+                if len(self.battery.current_avg_lst) > 300:
+                    del self.battery.current_avg_lst[0]
+
+            """
+            logger.info(
+                str(self.battery.capacity)
+                + " - "
+                + str(utils.TIME_TO_GO_ENABLE)
+                + " - "
+                + str(len(utils.TIME_TO_SOC_POINTS))
+                + " - "
+                + str(int(time()) - self.battery.time_to_soc_update)
+                + " - "
+                + str(utils.TIME_TO_SOC_RECALCULATE_EVERY)
+            )
+            """
+
             if (
                 self.battery.capacity is not None
                 and (utils.TIME_TO_GO_ENABLE or len(utils.TIME_TO_SOC_POINTS) > 0)
@@ -584,22 +625,34 @@ def publish_dbus(self):
                 )
             ):
                 self.battery.time_to_soc_update = int(time())
+
+                self.battery.current_avg = round(
+                    sum(self.battery.current_avg_lst)
+                    / len(self.battery.current_avg_lst),
+                    2,
+                )
+
+                self._dbusservice["/CurrentAvg"] = self.battery.current_avg
+
                 crntPrctPerSec = (
-                    abs(self.battery.current / (self.battery.capacity / 100)) / 3600
+                    abs(self.battery.current_avg / (self.battery.capacity / 100)) / 3600
                 )
 
                 # Update TimeToGo item
-                if utils.TIME_TO_GO_ENABLE:
+                if utils.TIME_TO_GO_ENABLE and crntPrctPerSec is not None:
                     # Update TimeToGo item, has to be a positive int since it's used from dbus-systemcalc-py
+                    time_to_go = self.battery.get_timeToSoc(
+                        # switch value depending on charging/discharging
+                        utils.SOC_LOW_WARNING if self.battery.current_avg < 0 else 100,
+                        crntPrctPerSec,
+                        True,
+                    )
+
+                    # Check that time_to_go is not None and current is not near zero
                     self._dbusservice["/TimeToGo"] = (
-                        abs(
-                            int(
-                                self.battery.get_timeToSoc(
-                                    utils.SOC_LOW_WARNING, crntPrctPerSec, True
-                                )
-                            )
-                        )
-                        if self.battery.current
+                        abs(int(time_to_go))
+                        if time_to_go is not None
+                        and abs(self.battery.current_avg) > 0.1
                         else None
                     )
 
@@ -608,11 +661,17 @@ def publish_dbus(self):
                     for num in utils.TIME_TO_SOC_POINTS:
                         self._dbusservice["/TimeToSoC/" + str(num)] = (
                             self.battery.get_timeToSoc(num, crntPrctPerSec)
-                            if self.battery.current
+                            if self.battery.current_avg
                             else None
                         )
 
         except Exception:
+            exception_type, exception_object, exception_traceback = sys.exc_info()
+            file = exception_traceback.tb_frame.f_code.co_filename
+            line = exception_traceback.tb_lineno
+            logger.error(
+                f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
+            )
             pass
 
         if self.battery.soc is not None:
diff --git a/etc/dbus-serialbattery/disable.sh b/etc/dbus-serialbattery/disable.sh
index f1902881..3beacfad 100755
--- a/etc/dbus-serialbattery/disable.sh
+++ b/etc/dbus-serialbattery/disable.sh
@@ -8,22 +8,35 @@ bash /opt/victronenergy/swupdate-scripts/remount-rw.sh
 
 # remove driver from serial starter
 rm -f /data/conf/serial-starter.d/dbus-serialbattery.conf
+# remove serial-starter.d if empty
+rmdir /data/conf/serial-starter.d >/dev/null 2>&1
 # kill serial starter, to reload changes
 pkill -f "/opt/victronenergy/serial-starter/serial-starter.sh"
 
 # remove services
 rm -rf /service/dbus-serialbattery.*
 rm -rf /service/dbus-blebattery.*
+rm -rf /service/dbus-canbattery.*
 
 # kill driver, if running
-pkill -f "python .*/dbus-serialbattery.py"
-pkill -f "blebattery"
+# serial
+pkill -f "supervise dbus-serialbattery.*"
+pkill -f "multilog .* /var/log/dbus-serialbattery.*"
+pkill -f "python .*/dbus-serialbattery.py /dev/tty.*"
+# bluetooth
+pkill -f "supervise dbus-blebattery.*"
+pkill -f "multilog .* /var/log/dbus-blebattery.*"
+pkill -f "python .*/dbus-serialbattery.py .*_Ble.*"
+# can
+pkill -f "supervise dbus-canbattery.*"
+pkill -f "multilog .* /var/log/dbus-canbattery.*"
+pkill -f "python .*/dbus-serialbattery.py can.*"
 
 # remove install script from rc.local
 sed -i "/bash \/data\/etc\/dbus-serialbattery\/reinstall-local.sh/d" /data/rc.local
 
 # remove cronjob
-sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root
+sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root >/dev/null 2>&1
 
 
 ### needed for upgrading from older versions | start ###
diff --git a/etc/dbus-serialbattery/qml/PageBattery.qml b/etc/dbus-serialbattery/qml/PageBattery.qml
index 286ce74c..ea7b44f5 100644
--- a/etc/dbus-serialbattery/qml/PageBattery.qml
+++ b/etc/dbus-serialbattery/qml/PageBattery.qml
@@ -94,6 +94,12 @@ MbPage {
             ]
         }
 
+        MbItemValue {
+            description: qsTr("Current (last 5 minutes avg.)")
+            item.bind: service.path("/CurrentAvg")
+            show: item.seen
+        }
+
         MbItemValue {
             id: soc
 
diff --git a/etc/dbus-serialbattery/qml/PageBatteryParameters.qml b/etc/dbus-serialbattery/qml/PageBatteryParameters.qml
index b95161a3..c402e446 100644
--- a/etc/dbus-serialbattery/qml/PageBatteryParameters.qml
+++ b/etc/dbus-serialbattery/qml/PageBatteryParameters.qml
@@ -6,6 +6,8 @@ MbPage {
 
 	property variant service
 
+	property VBusItem chargeModeDebug: VBusItem { bind: service.path("/Info/ChargeModeDebug") }
+
 	model: VisibleItemModel {
 
         MbItemValue {
@@ -14,6 +16,13 @@ MbPage {
             show: item.valid
         }
 
+		// show debug informations
+		MbItemText {
+			text: chargeModeDebug.value
+			wrapMode: Text.WordWrap
+			show: chargeModeDebug.value != ""
+		}
+
 		MbItemValue {
 			description: qsTr("Charge Voltage Limit (CVL)")
 			item.bind: service.path("/Info/MaxChargeVoltage")
diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh
index a518a100..ffb4b25a 100755
--- a/etc/dbus-serialbattery/reinstall-local.sh
+++ b/etc/dbus-serialbattery/reinstall-local.sh
@@ -3,8 +3,6 @@
 # remove comment for easier troubleshooting
 #set -x
 
-DRIVERNAME=dbus-serialbattery
-
 
 # check if minimum required Venus OS is installed | start
 versionRequired="v2.90"
@@ -66,15 +64,15 @@ fi
 bash /opt/victronenergy/swupdate-scripts/remount-rw.sh
 
 # install
-rm -rf /opt/victronenergy/service/$DRIVERNAME
-rm -rf /opt/victronenergy/service-templates/$DRIVERNAME
-rm -rf /opt/victronenergy/$DRIVERNAME
-mkdir /opt/victronenergy/$DRIVERNAME
-mkdir /opt/victronenergy/$DRIVERNAME/bms
-cp -f /data/etc/$DRIVERNAME/* /opt/victronenergy/$DRIVERNAME &>/dev/null
-cp -f /data/etc/$DRIVERNAME/bms/* /opt/victronenergy/$DRIVERNAME/bms &>/dev/null
-cp -rf /data/etc/$DRIVERNAME/service /opt/victronenergy/service-templates/$DRIVERNAME
-bash /data/etc/$DRIVERNAME/install-qml.sh
+rm -rf /opt/victronenergy/service/dbus-serialbattery
+rm -rf /opt/victronenergy/service-templates/dbus-serialbattery
+rm -rf /opt/victronenergy/dbus-serialbattery
+mkdir /opt/victronenergy/dbus-serialbattery
+mkdir /opt/victronenergy/dbus-serialbattery/bms
+cp -f /data/etc/dbus-serialbattery/* /opt/victronenergy/dbus-serialbattery &>/dev/null
+cp -f /data/etc/dbus-serialbattery/bms/* /opt/victronenergy/dbus-serialbattery/bms &>/dev/null
+cp -rf /data/etc/dbus-serialbattery/service /opt/victronenergy/service-templates/dbus-serialbattery
+bash /data/etc/dbus-serialbattery/install-qml.sh
 
 # check if serial-starter.d was deleted
 serialstarter_path="/data/conf/serial-starter.d"
@@ -105,10 +103,10 @@ if [ ! -f "$filename" ]; then
     echo "#!/bin/bash" > "$filename"
     chmod 755 "$filename"
 fi
-grep -qxF "bash /data/etc/$DRIVERNAME/reinstall-local.sh" $filename || echo "bash /data/etc/$DRIVERNAME/reinstall-local.sh" >> $filename
+grep -qxF "bash /data/etc/dbus-serialbattery/reinstall-local.sh" $filename || echo "bash /data/etc/dbus-serialbattery/reinstall-local.sh" >> $filename
 
 # add empty config.ini, if it does not exist to make it easier for users to add custom settings
-filename="/data/etc/$DRIVERNAME/config.ini"
+filename="/data/etc/dbus-serialbattery/config.ini"
 if [ ! -f "$filename" ]; then
     {
         echo "[DEFAULT]"
@@ -117,13 +115,18 @@ if [ ! -f "$filename" ]; then
         echo "; and insert them below to persist future driver updates."
         echo
         echo "; Example (remove the semicolon \";\" to uncomment and activate the value/setting):"
-        echo "; MAX_BATTERY_CURRENT = 50.0"
+        echo "; MAX_BATTERY_CHARGE_CURRENT = 50.0"
         echo "; MAX_BATTERY_DISCHARGE_CURRENT = 60.0"
         echo
         echo
     } > $filename
 fi
 
+# kill driver, if running. It gets restarted by the service daemon
+pkill -f "supervise dbus-serialbattery.*"
+pkill -f "multilog .* /var/log/dbus-serialbattery.*"
+pkill -f "python .*/dbus-serialbattery.py /dev/tty.*"
+
 
 
 ### BLUETOOTH PART | START ###
@@ -141,32 +144,93 @@ IFS="," read -r -a bms_array <<< "$bluetooth_bms_clean"
 #declare -p bms_array
 # readarray -td, bms_array <<< "$bluetooth_bms_clean,"; unset 'bms_array[-1]'; declare -p bms_array;
 
-length=${#bms_array[@]}
-# echo $length
+bluetooth_length=${#bms_array[@]}
+# echo $bluetooth_length
+
+# stop all dbus-blebattery services, if at least one exists
+if [ -d "/service/dbus-blebattery.0" ]; then
+    svc -u /service/dbus-blebattery.*
+
+    # always remove existing blebattery services to cleanup
+    rm -rf /service/dbus-blebattery.*
+
+    # kill all blebattery processes that remain
+    pkill -f "supervise dbus-blebattery.*"
+    pkill -f "multilog .* /var/log/dbus-blebattery.*"
+    pkill -f "python .*/dbus-serialbattery.py .*_Ble"
+
+    # kill opened bluetoothctl processes
+    pkill -f "^bluetoothctl "
+fi
 
-# always remove existing blebattery services to cleanup
-rm -rf /service/dbus-blebattery.*
 
-# kill all blebattery processes
-pkill -f "blebattery"
+if [ "$bluetooth_length" -gt 0 ]; then
 
-if [ "$length" -gt 0 ]; then
+    echo
+    echo "Found $bluetooth_length Bluetooth BMS in the config file!"
+    echo
 
-    echo "Found $length Bluetooth BMS in the config file!"
-    echo ""
+    /etc/init.d/bluetooth stop
+    echo
 
     # install required packages
     # TO DO: Check first if packages are already installed
-    echo "Installing required packages..."
+    echo "Installing required packages to use Bluetooth connection..."
+
+    # dbus-fast: skip compiling/building the wheel
+    # else weak system crash and are not able to install it,
+    # see https://github.com/Bluetooth-Devices/dbus-fast/issues/237
+    # and https://github.com/Louisvdw/dbus-serialbattery/issues/785
+    export SKIP_CYTHON=false
+
     opkg update
     opkg install python3-misc python3-pip
+
+    echo
     pip3 install bleak
 
-    # setup cronjob to restart Bluetooth
-    grep -qxF "5 0,12 * * * /etc/init.d/bluetooth restart" /var/spool/cron/root || echo "5 0,12 * * * /etc/init.d/bluetooth restart" >> /var/spool/cron/root
+    # # ONLY FOR TESTING if there are version issues
+    # echo
+    # echo "Available bleak versions:"
+    # curl --silent https://api.github.com/repos/hbldh/bleak/releases | grep '"name": "v' | sed "s/    \"name\": \"v//g" | sed "s/\",//g"
+    # echo
+    # read -r -p "Specify the bleak version to install: " bleak_version
+    # pip3 install bleak=="$bleak_version"
+    # echo
+    # echo
+    # echo "Available dbus-fast versions:"
+    # curl --silent https://api.github.com/repos/Bluetooth-Devices/dbus-fast/releases | grep '"name": "v' | sed "s/    \"name\": \"v//g" | sed "s/\",//g"
+    # echo
+    # read -r -p "Specify the dbus-fast version to install: " dbus_fast_version
+    # pip3 install dbus-fast=="$dbus_fast_version"
+    # echo
+
+    echo "done."
+    echo
+
+    /etc/init.d/bluetooth start
+    echo
 
     # function to install ble battery
     install_blebattery_service() {
+        if [ -z "$1" ]; then
+            echo "ERROR: BMS unique number is empty. Aborting installation."
+            echo
+            exit 1
+        fi
+        if [ -z "$2" ]; then
+            echo "ERROR: BMS type for battery $1 is empty. Aborting installation."
+            echo
+            exit 1
+        fi
+        if [ -z "$3" ]; then
+            echo "ERROR: BMS MAC address for battery $1 with BMS type $2 is empty. Aborting installation."
+            echo
+            exit 1
+        fi
+
+        echo "Installing \"$2\" with MAC address \"$3\" as dbus-blebattery.$1"
+
         mkdir -p "/service/dbus-blebattery.$1/log"
         {
             echo "#!/bin/sh"
@@ -177,36 +241,175 @@ if [ "$length" -gt 0 ]; then
         {
             echo "#!/bin/sh"
             echo "exec 2>&1"
+            echo "echo"
+            echo "echo \"INFO:Bluetooth details\""
+            # close all open connections, else the driver can't connect
             echo "bluetoothctl disconnect $3"
+
+            # enable bluetoothctl scan in background to display signal strength (RSSI), else it's missing
+            echo "bluetoothctl scan on | grep \"$3\" | grep \"RSSI\" &"
+            # with multiple Bluetooth BMS one scan for all should be enough. Check if that can be changed globally, maybe with a cronjob after reboot?
+            # echo "bluetoothctl scan on > /dev/null &"
+
+            # wait 5 seconds to finish the scan
+            echo "sleep 5"
+            # display some Bluetooth device details
+            echo "bluetoothctl info $3 | grep -E \"Device|Alias|Pair|Trusted|Blocked|Connected|RSSI|Power\""
+            echo "echo"
             echo "python /opt/victronenergy/dbus-serialbattery/dbus-serialbattery.py $2 $3"
+            echo "pkill -f \"bluetoothctl scan on\""
         } > "/service/dbus-blebattery.$1/run"
         chmod 755 "/service/dbus-blebattery.$1/run"
     }
 
-    echo "Packages installed."
-    echo ""
-
+    # Example
     # install_blebattery_service 0 Jkbms_Ble C8:47:8C:00:00:00
     # install_blebattery_service 1 Jkbms_Ble C8:47:8C:00:00:11
 
-    for (( i=0; i<length; i++ ));
+    for (( i=0; i<bluetooth_length; i++ ));
     do
-        echo "Installing ${bms_array[$i]} as dbus-blebattery.$i"
-        install_blebattery_service $i "${bms_array[$i]}"
+        # split BMS type and MAC address
+        IFS=' ' read -r -a bms <<< "${bms_array[$i]}"
+        install_blebattery_service $i "${bms[0]}" "${bms[1]}"
     done
 
+    echo
+
+    # setup cronjob to restart Bluetooth
+    # remove if not needed anymore, has to be checked first --> seems that it's not needed anymore
+    # grep -qxF "5 0,12 * * * /etc/init.d/bluetooth restart" /var/spool/cron/root || echo "5 0,12 * * * /etc/init.d/bluetooth restart" >> /var/spool/cron/root
+
+    # remove cronjob
+    sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root >/dev/null 2>&1
+
 else
 
     # remove cronjob
-    sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root
+    sed -i "/5 0,12 \* \* \* \/etc\/init.d\/bluetooth restart/d" /var/spool/cron/root >/dev/null 2>&1
 
+    echo
     echo "No Bluetooth battery configuration found in \"/data/etc/dbus-serialbattery/config.ini\"."
     echo "You can ignore this, if you are using only a serial connection."
+    echo
 
 fi
 ### BLUETOOTH PART | END ###
 
 
+
+### CAN PART | START ###
+
+# get CAN port(s) from config file
+can_port=$(awk -F "=" '/^CAN_PORT/ {print $2}' /data/etc/dbus-serialbattery/config.ini)
+#echo $can_port
+
+# clear whitespaces
+can_port_clean="$(echo $can_port | sed 's/\s*,\s*/,/g')"
+#echo $can_port_clean
+
+# split into array
+IFS="," read -r -a can_array <<< "$can_port_clean"
+#declare -p can_array
+# readarray -td, can_array <<< "$can_port_clean,"; unset 'can_array[-1]'; declare -p can_array;
+
+can_lenght=${#can_array[@]}
+# echo $can_lenght
+
+# stop all dbus-canbattery services, if at least one exists
+if [ -d "/service/dbus-canbattery.0" ]; then
+    svc -u /service/dbus-canbattery.*
+
+    # always remove existing canbattery services to cleanup
+    rm -rf /service/dbus-canbattery.*
+
+    # kill all canbattery processes that remain
+    pkill -f "supervise dbus-canbattery.*"
+    pkill -f "multilog .* /var/log/dbus-canbattery.*"
+    pkill -f "python .*/dbus-serialbattery.py .*_Ble"
+
+    # kill opened bluetoothctl processes
+    pkill -f "^bluetoothctl "
+fi
+
+
+if [ "$can_lenght" -gt 0 ]; then
+
+    echo
+    echo "Found $can_lenght CAN port(s) in the config file!"
+    echo
+
+    # install required packages
+    # TO DO: Check first if packages are already installed
+    echo "Installing required packages to use CAN connection..."
+
+    opkg update
+    opkg install python3-misc python3-pip
+
+    echo
+    pip3 install python-can
+
+    echo "done."
+    echo
+
+    # function to install can battery
+    install_canbattery_service() {
+        if [ -z "$1" ]; then
+            echo "ERROR: CAN port is empty. Aborting installation."
+            echo
+            exit 1
+        fi
+        #if [ -z "$2" ]; then
+        #    echo "ERROR: BMS type for can port $1 is empty. Aborting installation."
+        #    echo
+        #    exit 1
+        #fi
+
+        echo "Installing CAN port \"$1\" as dbus-canbattery.$1"
+
+        mkdir -p "/service/dbus-canbattery.$1/log"
+        {
+            echo "#!/bin/sh"
+            echo "exec multilog t s25000 n4 /var/log/dbus-canbattery.$1"
+        } > "/service/dbus-canbattery.$1/log/run"
+        chmod 755 "/service/dbus-canbattery.$1/log/run"
+
+        {
+            echo "#!/bin/sh"
+            echo "exec 2>&1"
+            echo "echo"
+            echo "python /opt/victronenergy/dbus-serialbattery/dbus-serialbattery.py $1"
+        } > "/service/dbus-canbattery.$1/run"
+        chmod 755 "/service/dbus-canbattery.$1/run"
+    }
+
+    # Example
+    # install_canbattery_service can0
+    # install_canbattery_service can9
+
+    for (( i=0; i<can_lenght; i++ ));
+    do
+        # # split CAN port and BMS type
+        # IFS=' ' read -r -a bms <<< "${can_array[$i]}"
+        # install_canbattery_service $i "${bms[0]}" "${bms[1]}"
+        install_canbattery_service "${can_array[$i]}"
+    done
+
+    # root@mutliplus-ii-gx:~# cansequence
+    # interface = can0, family = 29, type = 3, proto = 1
+    # write: No buffer space available
+
+else
+
+    echo
+    echo "No CAN port configuration found in \"/data/etc/dbus-serialbattery/config.ini\"."
+    echo "You can ignore this, if you are using only a serial connection."
+    echo
+
+fi
+### CAN PART | END ###
+
+
+
 ### 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-*
@@ -218,28 +421,23 @@ sed -i "/^sh \/data\/etc\/dbus-serialbattery\/installble.sh/d" /data/rc.local
 ### needed for upgrading from older versions | end ###
 
 
-# kill driver, if running. It gets restarted by the service daemon
-pkill -f "python .*/$DRIVERNAME.py"
-
-# restart bluetooth service, if Bluetooth BMS configured
-if [ "$length" -gt 0 ]; then
-    /etc/init.d/bluetooth restart
-fi
-
 
 # install notes
 echo
 echo
+echo "#################"
+echo "# Install notes #"
+echo "#################"
+echo
 echo "SERIAL battery connection: The installation is complete. You don't have to do anything more."
 echo
 echo "BLUETOOTH battery connection: There are a few more steps to complete installation."
 echo
-echo "    1. Please add the Bluetooth BMS to the config file \"/data/etc/dbus-serialbattery/config.ini\" by adding \"BLUETOOTH_BMS\":"
-echo "       Example with 1 BMS: BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00"
-echo "       Example with 3 BMS: BLUETOOTH_BMS = Jkbms_Ble C8:47:8C:00:00:00, Jkbms_Ble C8:47:8C:00:00:11, Jkbms_Ble C8:47:8C:00:00:22"
+echo "    1. Add your Bluetooth BMS to the config file \"/data/etc/dbus-serialbattery/config.ini\"."
+echo "       Check the default config file \"/data/etc/dbus-serialbattery/config.default.ini\" for more informations."
 echo "       If your Bluetooth BMS are nearby you can show the MAC address with \"bluetoothctl devices\"."
 echo
-echo "    2. Make sure to disable Settings -> Bluetooth in the remote console/GUI to prevent reconnects every minute."
+echo "    2. Make sure to disable Bluetooth in \"Settings -> Bluetooth\" in the remote console/GUI to prevent reconnects every minute."
 echo
 echo "    3. Re-run \"/data/etc/dbus-serialbattery/reinstall-local.sh\", if the Bluetooth BMS were not added to the \"config.ini\" before."
 echo
@@ -247,6 +445,15 @@ echo "    ATTENTION!"
 echo "    If you changed the default connection PIN of your BMS, then you have to pair the BMS first using OS tools like the \"bluetoothctl\"."
 echo "    See https://wiki.debian.org/BluetoothUser#Using_bluetoothctl for more details."
 echo
+echo "CAN battery connection: There are a few more steps to complete installation."
+echo
+echo "    1. Add your CAN port to the config file \"/data/etc/dbus-serialbattery/config.ini\"."
+echo "       Check the default config file \"/data/etc/dbus-serialbattery/config.default.ini\" for more informations."
+echo
+echo "    2. Make sure to select a profile with 250 kbit/s in \"Settings -> Services -> VE.Can port -> CAN-bus profile\" in the remote console/GUI."
+echo
+echo "    3. Re-run \"/data/etc/dbus-serialbattery/reinstall-local.sh\", if the CAN port was not added to the \"config.ini\" before."
+echo
 echo "CUSTOM SETTINGS: If you want to add custom settings, then check the settings you want to change in \"/data/etc/dbus-serialbattery/config.default.ini\""
 echo "                 and add them to \"/data/etc/dbus-serialbattery/config.ini\" to persist future driver updates."
 echo
diff --git a/etc/dbus-serialbattery/uninstall.sh b/etc/dbus-serialbattery/uninstall.sh
index 94100a9d..9bec2518 100755
--- a/etc/dbus-serialbattery/uninstall.sh
+++ b/etc/dbus-serialbattery/uninstall.sh
@@ -19,12 +19,13 @@ rm -rf /opt/victronenergy/dbus-serialbattery
 
 
 # uninstall modules
-read -r -p "Do you want to uninstall bleak, python3-pip and python3-modules? If you don't know just press enter. [y/N] " response
+read -r -p "Do you want to uninstall bleak, python-can, python3-pip and python3-modules? If you don't know just press enter. [y/N] " response
 echo
 response=${response,,} # tolower
 if [[ $response =~ ^(y) ]]; then
     echo "Uninstalling modules..."
     pip3 uninstall bleak
+    pip3 uninstall python-can
     opkg remove python3-pip python3-modules
     echo "done."
     echo
diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py
index e5f22fa5..df4ce2ab 100644
--- a/etc/dbus-serialbattery/utils.py
+++ b/etc/dbus-serialbattery/utils.py
@@ -13,12 +13,14 @@
 # Logging
 logging.basicConfig()
 logger = logging.getLogger("SerialBattery")
-logger.setLevel(logging.INFO)
+
+PATH_CONFIG_DEFAULT = "config.default.ini"
+PATH_CONFIG_USER = "config.ini"
 
 config = configparser.ConfigParser()
 path = Path(__file__).parents[0]
-default_config_file_path = path.joinpath("config.default.ini").absolute().__str__()
-custom_config_file_path = path.joinpath("config.ini").absolute().__str__()
+default_config_file_path = path.joinpath(PATH_CONFIG_DEFAULT).absolute().__str__()
+custom_config_file_path = path.joinpath(PATH_CONFIG_USER).absolute().__str__()
 config.read([default_config_file_path, custom_config_file_path])
 
 
@@ -27,18 +29,30 @@ def _get_list_from_config(
 ) -> List[Any]:
     rawList = config[group][option].split(",")
     return list(
-        map(mapper, [item for item in rawList if item != "" and item is not None])
+        map(
+            mapper,
+            [item.strip() for item in rawList if item != "" and item is not None],
+        )
     )
 
 
-# battery types
-# if not specified: baud = 9600
-
-# Constants - Need to dynamically get them in future
-DRIVER_VERSION = "1.0.20230531"
+# Constants
+DRIVER_VERSION = "1.0.20231117dev"
 zero_char = chr(48)
 degree_sign = "\N{DEGREE SIGN}"
 
+# get logging level from config file
+if config["DEFAULT"]["LOGGING"].upper() == "ERROR":
+    logger.setLevel(logging.ERROR)
+elif config["DEFAULT"]["LOGGING"].upper() == "WARNING":
+    logger.setLevel(logging.WARNING)
+elif config["DEFAULT"]["LOGGING"].upper() == "DEBUG":
+    logger.setLevel(logging.DEBUG)
+else:
+    logger.setLevel(logging.INFO)
+
+# save config values to constants
+
 # --------- Battery Current limits ---------
 MAX_BATTERY_CHARGE_CURRENT = float(config["DEFAULT"]["MAX_BATTERY_CHARGE_CURRENT"])
 MAX_BATTERY_DISCHARGE_CURRENT = float(
@@ -46,100 +60,66 @@ def _get_list_from_config(
 )
 
 # --------- Cell Voltages ---------
-# Description: Cell min/max voltages which are used to calculate the min/max battery voltage
-# Example: 16 cells * 3.45V/cell = 55.2V max charge voltage. 16 cells * 2.90V = 46.4V min discharge voltage
 MIN_CELL_VOLTAGE = float(config["DEFAULT"]["MIN_CELL_VOLTAGE"])
 MAX_CELL_VOLTAGE = float(config["DEFAULT"]["MAX_CELL_VOLTAGE"])
-# Max voltage can seen as absorption voltage
+
 FLOAT_CELL_VOLTAGE = float(config["DEFAULT"]["FLOAT_CELL_VOLTAGE"])
+if FLOAT_CELL_VOLTAGE > MAX_CELL_VOLTAGE:
+    FLOAT_CELL_VOLTAGE = MAX_CELL_VOLTAGE
+    logger.error(
+        ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value greater than MAX_CELL_VOLTAGE. Please check the configuration."
+    )
+if FLOAT_CELL_VOLTAGE < MIN_CELL_VOLTAGE:
+    FLOAT_CELL_VOLTAGE = MIN_CELL_VOLTAGE
+    logger.error(
+        ">>> ERROR: FLOAT_CELL_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration."
+    )
+
+SOC_RESET_VOLTAGE = float(config["DEFAULT"]["SOC_RESET_VOLTAGE"])
+if SOC_RESET_VOLTAGE < MAX_CELL_VOLTAGE:
+    SOC_RESET_VOLTAGE = MAX_CELL_VOLTAGE
+    logger.error(
+        ">>> ERROR: SOC_RESET_VOLTAGE is set to a value less than MAX_CELL_VOLTAGE. Please check the configuration."
+    )
+SOC_RESET_AFTER_DAYS = (
+    int(config["DEFAULT"]["SOC_RESET_AFTER_DAYS"])
+    if config["DEFAULT"]["SOC_RESET_AFTER_DAYS"] != ""
+    else False
+)
 
 # --------- BMS disconnect behaviour ---------
-# Description: Block charge and discharge when the communication to the BMS is lost. If you are removing the
-#              BMS on purpose, then you have to restart the driver/system to reset the block.
-# False: Charge and discharge is not blocked on BMS communication loss
-# True: Charge and discharge is blocked on BMS communication loss, it's unblocked when connection is established
-#       again or the driver/system is restarted
 BLOCK_ON_DISCONNECT = "True" == config["DEFAULT"]["BLOCK_ON_DISCONNECT"]
 
 # --------- Charge mode ---------
-# Choose the mode for voltage / current limitations (True / False)
-# False is a step mode: This is the default with limitations on hard boundary steps
-# True is a linear mode:
-#     For CCL and DCL the values between the steps are calculated for smoother values (by WaldemarFech)
-#     For CVL max battery voltage is calculated dynamically in order that the max cell voltage is not exceeded
 LINEAR_LIMITATION_ENABLE = "True" == config["DEFAULT"]["LINEAR_LIMITATION_ENABLE"]
-
-# Specify in seconds how often the penalty should be recalculated
 LINEAR_RECALCULATION_EVERY = int(config["DEFAULT"]["LINEAR_RECALCULATION_EVERY"])
-# Specify in percent when the linear values should be recalculated immediately
-# Example: 5 for a immediate change, when the value changes by more than 5%
 LINEAR_RECALCULATION_ON_PERC_CHANGE = int(
     config["DEFAULT"]["LINEAR_RECALCULATION_ON_PERC_CHANGE"]
 )
 
-
 # --------- Charge Voltage limitation (affecting CVL) ---------
-# Description: Limit max charging voltage (MAX_CELL_VOLTAGE * cell count), switch from max voltage to float
-#              voltage (FLOAT_CELL_VOLTAGE * cell count) and back
-#     False: Max charging voltage is always kept
-#     True: Max charging voltage is reduced based on charge mode
-#         Step mode: After max voltage is reached for MAX_VOLTAGE_TIME_SEC it switches to float voltage. After
-#                    SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT it switches back to max voltage.
-#         Linear mode: After max voltage is reachend and cell voltage difference is smaller or equal to
-#                      CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL it switches to float voltage after 300 (fixed)
-#                      additional seconds.
-#                      After cell voltage difference is greater or equal to CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
-#                      OR
-#                      SoC is below SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT
-#                      it switches back to max voltage.
-# Example: The battery reached max voltage of 55.2V and hold it for 900 seconds, the the CVL is switched to
-#          float voltage of 53.6V to don't stress the batteries. Allow max voltage of 55.2V again, if SoC is
-#          once below 90%
-#          OR
-#          The battery reached max voltage of 55.2V and the max cell difference is 0.010V, then switch to float
-#          voltage of 53.6V after 300 additional seconds to don't stress the batteries. Allow max voltage of
-#          55.2V again if max cell difference is above 0.080V or SoC below 90%.
-# Charge voltage control management enable (True/False).
 CVCM_ENABLE = "True" == config["DEFAULT"]["CVCM_ENABLE"]
-
-# -- CVL reset based on cell voltage diff (linear mode)
-# Specify cell voltage diff where CVL limit is kept until diff is equal or lower
 CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL = float(
     config["DEFAULT"]["CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_UNTIL"]
 )
-# Specify cell voltage diff where CVL limit is reset to max voltage, if value get above
-# the cells are considered as imbalanced, if the cell diff exceeds 5% of the nominal cell voltage
-# e.g. 3.2 V * 5 / 100 = 0.160 V
 CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT = float(
     config["DEFAULT"]["CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT"]
 )
 
-# -- CVL Reset based on SoC option
-# Specify how long the max voltage should be kept, if reached then switch to float voltage
-MAX_VOLTAGE_TIME_SEC = float(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"])
-# Specify SoC where CVL limit is reset to max voltage, if value gets below
-SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = float(
+MAX_VOLTAGE_TIME_SEC = int(config["DEFAULT"]["MAX_VOLTAGE_TIME_SEC"])
+SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = int(
     config["DEFAULT"]["SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT"]
 )
-
-
-# --------- Cell Voltage Current limitation (affecting CCL/DCL) ---------
-# Description: Maximal charge / discharge current will be in-/decreased depending on min and max cell voltages
-# Example: 18 cells * 3.55V/cell = 63.9V max charge voltage
-#          18 cells * 2.70V/cell = 48.6V min discharge voltage
-#          But in reality not all cells reach the same voltage at the same time. The (dis)charge current
-#          will be (in-/)decreased, if even ONE SINGLE BATTERY CELL reaches the limits
-
-# Charge current control management referring to cell-voltage enable (True/False).
 CCCM_CV_ENABLE = "True" == config["DEFAULT"]["CCCM_CV_ENABLE"]
-# Discharge current control management referring to cell-voltage enable (True/False).
 DCCM_CV_ENABLE = "True" == config["DEFAULT"]["DCCM_CV_ENABLE"]
 
-# Set steps to reduce battery current
-# The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True
 CELL_VOLTAGES_WHILE_CHARGING = _get_list_from_config(
     "DEFAULT", "CELL_VOLTAGES_WHILE_CHARGING", lambda v: float(v)
 )
+if CELL_VOLTAGES_WHILE_CHARGING[0] < MAX_CELL_VOLTAGE:
+    logger.error(
+        ">>> ERROR: Maximum value of CELL_VOLTAGES_WHILE_CHARGING is set to a value lower than MAX_CELL_VOLTAGE. Please check the configuration."
+    )
 MAX_CHARGE_CURRENT_CV = _get_list_from_config(
     "DEFAULT",
     "MAX_CHARGE_CURRENT_CV_FRACTION",
@@ -149,24 +129,20 @@ def _get_list_from_config(
 CELL_VOLTAGES_WHILE_DISCHARGING = _get_list_from_config(
     "DEFAULT", "CELL_VOLTAGES_WHILE_DISCHARGING", lambda v: float(v)
 )
+if CELL_VOLTAGES_WHILE_DISCHARGING[0] > MIN_CELL_VOLTAGE:
+    logger.error(
+        ">>> ERROR: Minimum value of CELL_VOLTAGES_WHILE_DISCHARGING is set to a value greater than MIN_CELL_VOLTAGE. Please check the configuration."
+    )
 MAX_DISCHARGE_CURRENT_CV = _get_list_from_config(
     "DEFAULT",
     "MAX_DISCHARGE_CURRENT_CV_FRACTION",
     lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v),
 )
 
-
 # --------- Temperature limitation (affecting CCL/DCL) ---------
-# Description: Maximal charge / discharge current will be in-/decreased depending on temperature
-# Example: The temperature limit will be monitored to control the currents. If there are two temperature senors,
-#          then the worst case will be calculated and the more secure lower current will be set.
-# Charge current control management referring to temperature enable (True/False).
 CCCM_T_ENABLE = "True" == config["DEFAULT"]["CCCM_T_ENABLE"]
-# Charge current control management referring to temperature enable (True/False).
 DCCM_T_ENABLE = "True" == config["DEFAULT"]["DCCM_T_ENABLE"]
 
-# Set steps to reduce battery current
-# The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True
 TEMPERATURE_LIMITS_WHILE_CHARGING = _get_list_from_config(
     "DEFAULT", "TEMPERATURE_LIMITS_WHILE_CHARGING", lambda v: float(v)
 )
@@ -185,22 +161,14 @@ def _get_list_from_config(
     lambda v: MAX_BATTERY_DISCHARGE_CURRENT * float(v),
 )
 
-
 # --------- SOC limitation (affecting CCL/DCL) ---------
-# Description: Maximal charge / discharge current will be increased / decreased depending on State of Charge,
-#              see CC_SOC_LIMIT1 etc.
-# Example: The SoC limit will be monitored to control the currents.
-# Charge current control management enable (True/False).
 CCCM_SOC_ENABLE = "True" == config["DEFAULT"]["CCCM_SOC_ENABLE"]
-# Discharge current control management enable (True/False).
 DCCM_SOC_ENABLE = "True" == config["DEFAULT"]["DCCM_SOC_ENABLE"]
 
-# Charge current soc limits
 CC_SOC_LIMIT1 = float(config["DEFAULT"]["CC_SOC_LIMIT1"])
 CC_SOC_LIMIT2 = float(config["DEFAULT"]["CC_SOC_LIMIT2"])
 CC_SOC_LIMIT3 = float(config["DEFAULT"]["CC_SOC_LIMIT3"])
 
-# Charge current limits
 CC_CURRENT_LIMIT1 = MAX_BATTERY_CHARGE_CURRENT * float(
     config["DEFAULT"]["CC_CURRENT_LIMIT1_FRACTION"]
 )
@@ -211,12 +179,10 @@ def _get_list_from_config(
     config["DEFAULT"]["CC_CURRENT_LIMIT3_FRACTION"]
 )
 
-# Discharge current soc limits
 DC_SOC_LIMIT1 = float(config["DEFAULT"]["DC_SOC_LIMIT1"])
 DC_SOC_LIMIT2 = float(config["DEFAULT"]["DC_SOC_LIMIT2"])
 DC_SOC_LIMIT3 = float(config["DEFAULT"]["DC_SOC_LIMIT3"])
 
-# Discharge current limits
 DC_CURRENT_LIMIT1 = MAX_BATTERY_DISCHARGE_CURRENT * float(
     config["DEFAULT"]["DC_CURRENT_LIMIT1_FRACTION"]
 )
@@ -227,96 +193,56 @@ def _get_list_from_config(
     config["DEFAULT"]["DC_CURRENT_LIMIT3_FRACTION"]
 )
 
-
 # --------- Time-To-Go ---------
-# Description: Calculates the time to go shown in the GUI
 TIME_TO_GO_ENABLE = "True" == config["DEFAULT"]["TIME_TO_GO_ENABLE"]
 
 # --------- Time-To-Soc ---------
-# Description: Calculates the time to a specific SoC
-# Example: TIME_TO_SOC_POINTS = 50, 25, 15, 0
-#          6h 24m remaining until 50% SoC
-#          17h 36m remaining until 25% SoC
-#          22h 5m remaining until 15% SoC
-#          28h 48m remaining until 0% SoC
-# Set of SoC percentages to report on dbus and MQTT. The more you specify the more it will impact system performance.
-# [Valid values 0-100, comma separated list. More that 20 intervals are not recommended]
-# Example: TIME_TO_SOC_POINTS = 100, 95, 90, 85, 75, 50, 25, 20, 10, 0
-# Leave empty to disable
 TIME_TO_SOC_POINTS = _get_list_from_config(
     "DEFAULT", "TIME_TO_SOC_POINTS", lambda v: int(v)
 )
-# Specify TimeToSoc value type [Valid values 1, 2, 3]
-# 1 Seconds
-# 2 Time string <days>d <hours>h <minutes>m <seconds>s
-# 3 Both seconds and time string "<seconds> [<days>d <hours>h <minutes>m <seconds>s]"
 TIME_TO_SOC_VALUE_TYPE = int(config["DEFAULT"]["TIME_TO_SOC_VALUE_TYPE"])
-# Specify in seconds how often the TimeToSoc should be recalculated
-# Minimum are 5 seconds to prevent CPU overload
 TIME_TO_SOC_RECALCULATE_EVERY = (
     int(config["DEFAULT"]["TIME_TO_SOC_RECALCULATE_EVERY"])
     if int(config["DEFAULT"]["TIME_TO_SOC_RECALCULATE_EVERY"]) > 5
     else 5
 )
-# Include TimeToSoC points when moving away from the SoC point [Valid values True, False]
-# These will be as negative time. Disabling this improves performance slightly
 TIME_TO_SOC_INC_FROM = "True" == config["DEFAULT"]["TIME_TO_SOC_INC_FROM"]
 
-
 # --------- Additional settings ---------
-# Specify 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/"
+BMS_TYPE = _get_list_from_config("DEFAULT", "BMS_TYPE", lambda v: str(v))
+
+EXCLUDED_DEVICES = _get_list_from_config(
+    "DEFAULT", "EXCLUDED_DEVICES", lambda v: str(v)
+)
+
+CUSTOM_BATTERY_NAMES = _get_list_from_config(
+    "DEFAULT", "CUSTOM_BATTERY_NAMES", lambda v: str(v)
+)
+
+# Auto reset SoC
+# If on, then SoC is reset to 100%, if the value switches from absorption to float voltage
+# Currently only working for Daly BMS and JK BMS BLE
+AUTO_RESET_SOC = "True" == config["DEFAULT"]["AUTO_RESET_SOC"]
+
 PUBLISH_CONFIG_VALUES = int(config["DEFAULT"]["PUBLISH_CONFIG_VALUES"])
 
-# Select the format of cell data presented on dbus [Valid values 0,1,2,3]
-# 0 Do not publish all the cells (only the min/max cell data as used by the default GX)
-# 1 Format: /Voltages/Cell (also available for display on Remote Console)
-# 2 Format: /Cell/#/Volts
-# 3 Both formats 1 and 2
 BATTERY_CELL_DATA_FORMAT = int(config["DEFAULT"]["BATTERY_CELL_DATA_FORMAT"])
 
-# Simulate Midpoint graph (True/False).
 MIDPOINT_ENABLE = "True" == config["DEFAULT"]["MIDPOINT_ENABLE"]
 
-# Battery temperature
-# Specifiy how the battery temperature is assembled
-# 0 Get mean of temp sensor 1 and temp sensor 2
-# 1 Get only temp from temp sensor 1
-# 2 Get only temp from temp sensor 2
 TEMP_BATTERY = int(config["DEFAULT"]["TEMP_BATTERY"])
 
-# Temperature sensor 1 name
 TEMP_1_NAME = config["DEFAULT"]["TEMP_1_NAME"]
-
-# Temperature sensor 2 name
 TEMP_2_NAME = config["DEFAULT"]["TEMP_2_NAME"]
-
-# Temperature sensor 3 name
 TEMP_3_NAME = config["DEFAULT"]["TEMP_3_NAME"]
-
-# Temperature sensor 2 name
 TEMP_4_NAME = config["DEFAULT"]["TEMP_4_NAME"]
 
-
 # --------- BMS specific settings ---------
-
-# -- LltJbd settings
-# SoC low levels
-# NOTE: SOC_LOW_WARNING is also used to calculate the Time-To-Go even if you are not using a LltJbd BMS
 SOC_LOW_WARNING = float(config["DEFAULT"]["SOC_LOW_WARNING"])
 SOC_LOW_ALARM = float(config["DEFAULT"]["SOC_LOW_ALARM"])
 
 # -- Daly settings
-# Battery capacity (amps) if the BMS does not support reading it
 BATTERY_CAPACITY = float(config["DEFAULT"]["BATTERY_CAPACITY"])
-# Invert Battery Current. Default non-inverted. Set to -1 to invert
 INVERT_CURRENT_MEASUREMENT = int(config["DEFAULT"]["INVERT_CURRENT_MEASUREMENT"])
 
 # -- ESC GreenMeter and Lipro device settings
@@ -330,19 +256,7 @@ def _get_list_from_config(
     "DEFAULT", "HELTEC_MODBUS_ADDR", lambda v: int(v)
 )
 
-
 # --------- Battery monitor specific settings ---------
-# If you are using a SmartShunt or something else as a battery monitor, the battery voltage reported
-# from the BMS and SmartShunt could differ. This causes, that the driver never goapplies the float voltage,
-# since max voltage is never reached.
-# Example:
-#     cell count: 16
-#     MAX_CELL_VOLTAGE = 3.45
-#     max voltage calculated = 16 * 3.45 = 55.20
-#     CVL is set to 55.20 and the battery is now charged until the SmartShunt measures 55.20 V. The BMS
-#     now measures 55.05 V since there is a voltage drop of 0.15 V. Since the dbus-serialbattery measures
-#     55.05 V the max voltage is never reached for the driver and max voltage is kept forever.
-#     Set VOLTAGE_DROP to 0.15
 VOLTAGE_DROP = float(config["DEFAULT"]["VOLTAGE_DROP"])
 
 
@@ -404,6 +318,10 @@ def kelvin_to_celsius(kelvin_temp):
     return kelvin_temp - 273.1
 
 
+def bytearray_to_string(data):
+    return "".join("\\x" + format(byte, "02x") for byte in data)
+
+
 def format_value(value, prefix, suffix):
     return (
         None
@@ -519,6 +437,7 @@ def read_serialport_data(
 locals_copy = locals().copy()
 
 
+# Publish config variables to dbus
 def publish_config_variables(dbusservice):
     for variable, value in locals_copy.items():
         if variable.startswith("__"):
diff --git a/requirements.txt b/requirements.txt
index 6de24eea..7a984a6f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 pyserial==3.5
 minimalmodbus==2.0.1
-bleak==0.20.0
\ No newline at end of file
+bleak==0.21.0
+dbus-fast==1.94.1