diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e1217079..1b90dc16 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,7 +15,9 @@ body: * Please carefully read all the instructions below including the descriptions! Very often we have to request the data again, because it was not provided in the first place. - * Please try the `nightly` build from the `dev` first, if you are not already using it. It's very likely that the bug is already fixed. See [here](https://louisvdw.github.io/dbus-serialbattery/general/install#nightly-build) how to install it. + * Please try the `nightly` build first, if you are not already using it. It's very likely that the bug is already fixed. See [here](https://louisvdw.github.io/dbus-serialbattery/general/install#nightly-build) how to install it. + + * Please read the complete [changelog](https://github.com/Louisvdw/dbus-serialbattery/blob/master/CHANGELOG.md). - type: textarea id: description diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index a53b5847..262f8869 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -13,7 +13,9 @@ body: ## 🚨 IMPORTANT 🚨 - * Please try the `nightly` build from the `dev` first, if you are not already using it. Maybe the feature was already developed. See [here](https://louisvdw.github.io/dbus-serialbattery/general/install#nightly-build) how to install it. + * Please try the `nightly` build first, if you are not already using it. Maybe the feature was already developed. See [here](https://louisvdw.github.io/dbus-serialbattery/general/install#nightly-build) how to install it. + + * Please read the complete [changelog](https://github.com/Louisvdw/dbus-serialbattery/blob/master/CHANGELOG.md). - type: textarea id: idea diff --git a/.github/ISSUE_TEMPLATE/support_request.yml b/.github/ISSUE_TEMPLATE/support_request.yml index c62a296a..ef428008 100644 --- a/.github/ISSUE_TEMPLATE/support_request.yml +++ b/.github/ISSUE_TEMPLATE/support_request.yml @@ -15,7 +15,9 @@ body: * Please carefully read all the instructions below including the descriptions! Very often we have to request the data again, because it was not provided in the first place. - * Please try the `nightly` build from the `dev` first, if you are not already using it. It's very likely that the bug is already fixed. See [here](https://louisvdw.github.io/dbus-serialbattery/general/install#nightly-build) how to install it. + * Please try the `nightly` build first, if you are not already using it. It's very likely that the bug is already fixed. See [here](https://louisvdw.github.io/dbus-serialbattery/general/install#nightly-build) how to install it. + + * Please read the complete [changelog](https://github.com/Louisvdw/dbus-serialbattery/blob/master/CHANGELOG.md). - type: textarea id: description diff --git a/CHANGELOG.md b/CHANGELOG.md index 537f4a1e..98947fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,23 +54,30 @@ * `BULK_AFTER_DAYS` -> `SOC_RESET_AFTER_DAYS` -## v1.2.x + +## v1.2.20240408 * Added: Check if the device instance is already used by @mr-manuel * Added: Check if there is enough space on system and data partitions before installation by @mr-manuel * Added: LLT/JBD BLE BMS - Added MAC address as unique identifier. Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/970 by @mr-manuel * Added: Reset calculated SoC to 0%, if battery is empty by @mr-manuel * Added: Venus OS version to logfile by @mr-manuel +* Changed: Config: SoC limitation is now disabled by default, since in most use cases it's very inaccurate by @mr-manuel * Changed: Config: SoC limitation variables where changed to match other setting variables by @mr-manuel * Changed: Config: Temperature limitation variables where changed to match other setting variables by @mr-manuel +* Changed: Daly BMS - Fixed some smaller errory with https://github.com/mr-manuel/venus-os_dbus-serialbattery/pull/22 and https://github.com/mr-manuel/venus-os_dbus-serialbattery/pull/23 by @transistorgit +* Changed: Fixed CAN installation with https://github.com/Louisvdw/dbus-serialbattery/pull/1007 by @p0l0us +* Changed: Fixed non-working can-bus dependency with https://github.com/Louisvdw/dbus-serialbattery/pull/1007 by @p0l0us * Changed: Fixed showing None SoC in log in driver start by @mr-manuel * Changed: Fixed some other errors when restoring values from dbus settings by @mr-manuel * Changed: Fixed some SOC calculation issues by @mr-manuel * Changed: Fixed Time-to-SoC and Time-to-Go calculation by @mr-manuel +* Changed: Set CCL/DCL to 0, if allow to charge/discharge is no, fixes https://github.com/Louisvdw/dbus-serialbattery/issues/1024 by @mr-manuel * Changed: Install script now shows repositories and version numbers by @mr-manuel * Changed: JKBMS BLE - Fixed driver gets unresponsive, if connection is lost https://github.com/Louisvdw/dbus-serialbattery/issues/720 with https://github.com/Louisvdw/dbus-serialbattery/pull/941 by @cupertinomiranda * Changed: JKBMS BLE - Fixed driver not starting for some BMS models that are not sending BLE data correctly https://github.com/Louisvdw/dbus-serialbattery/issues/819 by @mr-manuel * Changed: JKBMS BLE - Fixed temperature issue https://github.com/Louisvdw/dbus-serialbattery/issues/916 by @mr-manuel +* Changed: JKBMS CAN - Fixed different BMS versions with https://github.com/mr-manuel/venus-os_dbus-serialbattery/pull/24 by @p0l0us * Changed: LLT/JBD BMS & BLE - If only one temperature is available use it as battery temp. Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/971 by @mr-manuel * Changed: Optimized reinstall-local.sh. Show installed version and restart GUI only on changes by @mr-manuel * Changed: Reinstallation of the driver now checks, if packages are already installed for Bluetooth and CAN by @mr-manuel diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 87e959fc..764d86e1 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -7,6 +7,7 @@ import math from time import time from abc import ABC, abstractmethod +import sys class Protection(object): @@ -69,6 +70,22 @@ def __init__(self, port: str, baud: int, address: str): self.max_battery_discharge_current: float = None self.has_settings: bool = False + # this values should only be initialized once, + # else the BMS turns off the inverter on disconnect + self.soc_calc_capacity_remain: float = None + self.soc_calc_capacity_remain_lasttime: float = None + self.soc_calc_reset_starttime: int = None + self.soc_calc: float = None # save soc_calc to preserve on restart + self.soc: float = None + self.charge_fet: bool = None + self.discharge_fet: bool = None + self.balance_fet: bool = None + self.block_because_disconnect: bool = False + self.control_charge_current: int = None + self.control_discharge_current: int = None + self.control_allow_charge: bool = None + self.control_allow_discharge: bool = None + # fetched from the BMS from a field where the user can input a custom string # only if available self.custom_field: str = None @@ -83,6 +100,7 @@ def init_values(self): self.current: float = None self.current_avg: float = None self.current_avg_lst: list = [] + self.current_corrected: float = None self.capacity_remain: float = None self.capacity: float = None self.cycles: float = None @@ -90,15 +108,7 @@ def init_values(self): self.production = None self.protection = Protection() self.version = None - self.soc_calc_capacity_remain: float = None - self.soc_calc_capacity_remain_lasttime: float = None - self.soc_calc_reset_starttime: int = None - self.soc_calc: float = None # save soc_calc to preserve on restart - self.soc: float = None self.time_to_soc_update: int = 0 - self.charge_fet: bool = None - self.discharge_fet: bool = None - self.balance_fet: bool = None self.temp_sensors: int = None self.temp1: float = None self.temp2: float = None @@ -122,10 +132,6 @@ def init_values(self): self.linear_cvl_last_set: int = 0 self.linear_ccl_last_set: int = 0 self.linear_dcl_last_set: int = 0 - self.control_discharge_current: int = None - self.control_charge_current: int = None - self.control_allow_charge: bool = None - self.control_allow_discharge: bool = None # list of available callbacks, in order to display the buttons in the GUI self.available_callbacks: List[str] = [] @@ -241,7 +247,7 @@ def manage_charge_voltage(self) -> None: def soc_calculation(self) -> None: current_time = time() voltage_sum = 0 - current_corrected = 0 + self.current_corrected = 0 current_min_cell_voltage = self.get_min_cell_voltage() # calculate battery voltage from cell voltages @@ -252,15 +258,18 @@ def soc_calculation(self) -> None: if self.soc_calc_capacity_remain is not None: # calculate real current - current_corrected = utils.calcLinearRelationship( - self.current, - utils.SOC_CALC_CURRENT_REPORTED_BY_BMS, - utils.SOC_CALC_CURRENT_MEASURED_BY_USER, + self.current_corrected = round( + utils.calcLinearRelationship( + self.current, + utils.SOC_CALC_CURRENT_REPORTED_BY_BMS, + utils.SOC_CALC_CURRENT_MEASURED_BY_USER, + ), + 2, ) self.soc_calc_capacity_remain = ( self.soc_calc_capacity_remain - + current_corrected + + self.current_corrected * (current_time - self.soc_calc_capacity_remain_lasttime) / 3600 ) @@ -601,8 +610,9 @@ def manage_charge_voltage_linear(self) -> None: ) self.charge_mode_debug += f" • Reset voltage limit SoC: {utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT}%" self.charge_mode_debug += ( - f"\nSoC: {self.soc}% • SoC_Calc {self.soc_calc}%" + f"\nSoC: {self.soc}% • SoC_Calc: {self.soc_calc}%" ) + self.charge_mode_debug += f"\ncurrent: {self.current}A • current_corrected: {self.current_corrected}A" self.charge_mode_debug += ( f"\nallow_max_voltage: {self.allow_max_voltage}" ) @@ -613,6 +623,11 @@ def manage_charge_voltage_linear(self) -> None: self.charge_mode_debug += ( f"\nlinear_cvl_last_set: {self.linear_cvl_last_set}" ) + self.charge_mode_debug += ( + f"\ncharge_fet: {self.charge_fet} • control_allow_charge: {self.control_allow_charge}" + + f"\ndischarge_fet: {self.discharge_fet} • control_allow_discharge: {self.control_allow_discharge}" + + f"\nblock_because_disconnect: {self.block_because_disconnect}" + ) soc_reset_days_ago = round( (current_time - self.soc_reset_last_reached) / 60 / 60 / 24, 2 ) @@ -783,11 +798,18 @@ def manage_charge_current(self) -> None: else: charge_limits.update({tmp: "SoC"}) + # set CCL to 0, if BMS does not allow to charge + if self.charge_fet is False or self.block_because_disconnect: + if 0 in charge_limits: + charge_limits.update({0: charge_limits[0] + ", BMS"}) + else: + charge_limits.update({0: "BMS"}) + # do not set CCL immediately, but only # - after LINEAR_RECALCULATION_EVERY passed # - if CCL changes to 0 # - if CCL changes more than LINEAR_RECALCULATION_ON_PERC_CHANGE - ccl = round(min(charge_limits), 3) # gets changed after finished testing + ccl = round(min(charge_limits), 3) diff = ( abs(self.control_charge_current - ccl) if self.control_charge_current is not None @@ -809,6 +831,7 @@ def manage_charge_current(self) -> None: self.charge_limitation = charge_limits[min(charge_limits)] + # set allow to charge to no, if CCL is 0 if self.control_charge_current == 0: self.control_allow_charge = False else: @@ -869,11 +892,18 @@ def manage_charge_current(self) -> None: else: discharge_limits.update({tmp: "SoC"}) + # set DCL to 0, if BMS does not allow to discharge + if self.discharge_fet is False or self.block_because_disconnect: + if 0 in discharge_limits: + discharge_limits.update({0: discharge_limits[0] + ", BMS"}) + else: + discharge_limits.update({0: "BMS"}) + # do not set DCL immediately, but only # - after LINEAR_RECALCULATION_EVERY passed # - if DCL changes to 0 # - if DCL changes more than LINEAR_RECALCULATION_ON_PERC_CHANGE - dcl = round(min(discharge_limits), 3) # gets changed after finished testing + dcl = round(min(discharge_limits), 3) diff = ( abs(self.control_discharge_current - dcl) if self.control_discharge_current is not None @@ -895,6 +925,7 @@ def manage_charge_current(self) -> None: self.discharge_limitation = discharge_limits[min(discharge_limits)] + # set allow to discharge to no, if DCL is 0 if self.control_discharge_current == 0: self.control_allow_discharge = False else: @@ -918,6 +949,17 @@ def calcMaxChargeCurrentReferringToCellVoltage(self) -> float: logger.warning( "Error while executing calcMaxChargeCurrentReferringToCellVoltage(). Using default value instead." ) + logger.warning( + f"CELL_VOLTAGES_WHILE_CHARGING: {utils.CELL_VOLTAGES_WHILE_CHARGING}" + + f" • MAX_CHARGE_CURRENT_CV: {utils.MAX_CHARGE_CURRENT_CV}" + ) + + 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 self.max_battery_charge_current def calcMaxDischargeCurrentReferringToCellVoltage(self) -> float: @@ -938,6 +980,17 @@ def calcMaxDischargeCurrentReferringToCellVoltage(self) -> float: logger.warning( "Error while executing calcMaxDischargeCurrentReferringToCellVoltage(). Using default value instead." ) + logger.warning( + f"CELL_VOLTAGES_WHILE_DISCHARGING: {utils.CELL_VOLTAGES_WHILE_DISCHARGING}" + + f" • MAX_DISCHARGE_CURRENT_CV: {utils.MAX_DISCHARGE_CURRENT_CV}" + ) + + 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 self.max_battery_charge_current def calcMaxChargeCurrentReferringToTemperature(self) -> float: @@ -1004,6 +1057,17 @@ def calcMaxChargeCurrentReferringToSoc(self) -> float: logger.warning( "Error while executing calcMaxChargeCurrentReferringToSoc(). Using default value instead." ) + logger.warning( + f"SOC_WHILE_CHARGING: {utils.SOC_WHILE_CHARGING}" + + f" • MAX_CHARGE_CURRENT_SOC: {utils.MAX_CHARGE_CURRENT_SOC}" + ) + + 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 self.max_battery_charge_current def calcMaxDischargeCurrentReferringToSoc(self) -> float: @@ -1024,6 +1088,17 @@ def calcMaxDischargeCurrentReferringToSoc(self) -> float: logger.warning( "Error while executing calcMaxDischargeCurrentReferringToSoc(). Using default value instead." ) + logger.warning( + f"SOC_WHILE_DISCHARGING: {utils.SOC_WHILE_DISCHARGING}" + + f" • MAX_DISCHARGE_CURRENT_SOC: {utils.MAX_DISCHARGE_CURRENT_SOC}" + ) + + 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 self.max_battery_discharge_current def get_min_cell(self) -> int: @@ -1333,6 +1408,27 @@ def get_mos_temp(self) -> Union[float, None]: else: return None + def get_allow_to_charge(self) -> bool: + return ( + True + if self.charge_fet + and self.control_allow_charge + and self.block_because_disconnect is False + else False + ) + + def get_allow_to_discharge(self) -> bool: + return ( + True + if self.discharge_fet + and self.control_allow_discharge + and self.block_because_disconnect is False + else False + ) + + def get_allow_to_balance(self) -> bool: + return True if self.balance_fet else False + def validate_data(self) -> bool: """ Used to validate the data received from the BMS. diff --git a/etc/dbus-serialbattery/bms/daly.py b/etc/dbus-serialbattery/bms/daly.py index 38697022..4126e106 100644 --- a/etc/dbus-serialbattery/bms/daly.py +++ b/etc/dbus-serialbattery/bms/daly.py @@ -35,8 +35,10 @@ def __init__(self, port, baud, address): "force_discharging_off_callback", ] - # 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" + # command bytes [StartFlag=A5][Address=40][Command=94][DataLength=8][8x fill bytes][checksum] + # use 0xAA (or 0x55) as fill bytes to allow the daly's "weak" uart to sync better + # this reduces read errors dramatically + command_base = b"\xA5\x40\x94\x08\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\x00" command_set_soc = b"\x21" command_rated_params = b"\x50" command_batt_details = b"\x53" @@ -71,6 +73,8 @@ def test_connection(self): if result: self.read_soc_data(ser) self.read_battery_code(ser) + self.read_capacity(ser) + self.read_production_date(ser) except Exception: ( @@ -92,7 +96,7 @@ def test_connection(self): return result def get_settings(self): - self.capacity = utils.BATTERY_CAPACITY + self.capacity = utils.BATTERY_CAPACITY if not None else 0.0 with open_serial_port(self.port, self.baud_rate) as ser: self.read_capacity(ser) self.read_production_date(ser) @@ -198,6 +202,7 @@ def refresh_data(self): if not result: # TROUBLESHOOTING for no reply errors logger.info("refresh_data: result: " + str(result)) + return result def update_soc(self, ser): @@ -217,14 +222,17 @@ def read_status_data(self, ser): logger.debug("No data received in 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) + try: + ( + self.cell_count, + self.temp_sensors, + self.charger_connected, + self.load_connected, + state, + self.cycles, + ) = unpack_from(">bb??bhx", status_data) + except Exception: + return False self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count @@ -505,7 +513,6 @@ def read_fed_data(self, ser): self.capacity_remain = capacity_remain / 1000 return True - # new def read_capacity(self, ser): capa_data = self.request_data(ser, self.command_rated_params) # check if connection success @@ -514,13 +521,12 @@ def read_capacity(self, ser): return False (capacity, cell_volt) = unpack_from(">LL", capa_data) - if capacity and capacity > 0: + if capacity is not None and capacity > 0: self.capacity = capacity / 1000 return True else: return False - # new def read_production_date(self, ser): production = self.request_data(ser, self.command_batt_details) # check if connection success @@ -532,7 +538,6 @@ def read_production_date(self, ser): self.production = f"{year + 2000}{month:02d}{day:02d}" return True - # new def read_battery_code(self, ser): data = self.request_data(ser, self.command_batt_code, sentences_to_receive=5) @@ -560,7 +565,7 @@ def unique_identifier(self) -> str: """ Used to identify a BMS when multiple BMS are connected """ - if self.custom_field != "": + if self.custom_field is not None and self.custom_field != "": return self.custom_field.replace(" ", "_") else: return str(self.production) + "_" + str(int(self.capacity)) @@ -753,7 +758,10 @@ def read_sentence(self, ser, expected_reply, timeout=0.5): return False reply += ser.read(12) - _, id, cmd, length = unpack_from(">BBBB", reply) + try: + _, id, cmd, length = unpack_from(">BBBB", reply) + except Exception: + return False # logger.info(f"reply: {utils.bytearray_to_string(reply)}") # debug diff --git a/etc/dbus-serialbattery/bms/jkbms_can.py b/etc/dbus-serialbattery/bms/jkbms_can.py index 7a9f7da7..e5407dc4 100644 --- a/etc/dbus-serialbattery/bms/jkbms_can.py +++ b/etc/dbus-serialbattery/bms/jkbms_can.py @@ -8,6 +8,7 @@ MAX_BATTERY_DISCHARGE_CURRENT, MAX_CELL_VOLTAGE, MIN_CELL_VOLTAGE, + JKBMS_CAN_CELL_COUNT, zero_char, ) from struct import unpack_from @@ -49,12 +50,14 @@ def __del__(self): MESSAGES_TO_READ = 100 - # Changed from 0x0XF4 to 0x0XF5. See https://github.com/Louisvdw/dbus-serialbattery/issues/950 + # B2A... Black is using 0x0XF4 + # B2A... Silver is using 0x0XF5 + # See https://github.com/Louisvdw/dbus-serialbattery/issues/950 CAN_FRAMES = { - BATT_STAT: 0x02F5, - CELL_VOLT: 0x04F5, - CELL_TEMP: 0x05F5, - ALM_INFO: 0x07F5, + BATT_STAT: [0x02F4, 0x02F5], + CELL_VOLT: [0x04F4, 0x04F5], + CELL_TEMP: [0x05F4, 0x05F5], + ALM_INFO: [0x07F4, 0x07F5], } def test_connection(self): @@ -67,6 +70,7 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure + self.cell_count = JKBMS_CAN_CELL_COUNT self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count @@ -204,7 +208,7 @@ def read_serial_data_jkbms_CAN(self): # print("message received") messages_to_read -= 1 # print(messages_to_read) - if msg.arbitration_id == self.CAN_FRAMES[self.BATT_STAT]: + if msg.arbitration_id in self.CAN_FRAMES[self.BATT_STAT]: voltage = unpack_from("BB", 0, charge_disabled | (discharge_disabled << 1)) reply = self.read_serial_data_llt(writeCmd(REG_CTRL_MOSFET, mosdata)) diff --git a/etc/dbus-serialbattery/bms/renogy.py b/etc/dbus-serialbattery/bms/renogy.py index f4ee3aab..0b205d1c 100644 --- a/etc/dbus-serialbattery/bms/renogy.py +++ b/etc/dbus-serialbattery/bms/renogy.py @@ -41,7 +41,6 @@ def __init__(self, port, baud, address): ) # BMS warning and protection config - def unique_identifier(self) -> str: return self.serial_number diff --git a/etc/dbus-serialbattery/bms/sinowealth.py b/etc/dbus-serialbattery/bms/sinowealth.py old mode 100755 new mode 100644 diff --git a/etc/dbus-serialbattery/config.default.ini b/etc/dbus-serialbattery/config.default.ini index fb669289..bd11740f 100644 --- a/etc/dbus-serialbattery/config.default.ini +++ b/etc/dbus-serialbattery/config.default.ini @@ -40,6 +40,9 @@ FLOAT_CELL_VOLTAGE = 3.375 ; 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 +; You also have to set CELL_VOLTAGES_WHILE_CHARGING accordingly, if you set CCCM_CV_ENABLE to true +; else the charging current will be reduced to 0 before the target voltage is reached and the +; battery will never switch to float 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 @@ -263,22 +266,22 @@ MAX_DISCHARGE_CURRENT_T_FRACTION = 0.00, 0.20, 0.30, 0.40, 1.00, 1.00, 0.00 ; --------- SOC limitation (affecting CCL/DCL) --------- ; Description: -; Maximal charge / discharge current will be increased / decreased depending on State of Charge, -; see CC_SOC_LIMIT1 etc. +; Maximal charge / discharge current will be increased / decreased depending on State of Charge +; Since the SoC is not as accurate as the cell voltage, this option is disabled by default ; Example: ; The SoC limit will be monitored to control the currents. ; Charge current control management enable (True/False). -CCCM_SOC_ENABLE = True +CCCM_SOC_ENABLE = False ; Discharge current control management enable (True/False). -DCCM_SOC_ENABLE = True +DCCM_SOC_ENABLE = False ; Set steps to reduce battery current ; The current will be changed linear between those steps if LINEAR_LIMITATION_ENABLE is set to True -SOC_WHILE_CHARGING = 100, 95, 90, 85 -MAX_CHARGE_CURRENT_SOC_FRACTION = 0.00, 0.15, 0.50, 1.00 +SOC_WHILE_CHARGING = 98, 95, 90, 85 +MAX_CHARGE_CURRENT_SOC_FRACTION = 0.10, 0.20, 0.50, 1.00 -SOC_WHILE_DISCHARGING = 0, 5, 10, 15, 20 -MAX_DISCHARGE_CURRENT_SOC_FRACTION = 0.00, 0.10, 0.20, 0.50, 1.00 +SOC_WHILE_DISCHARGING = 5, 10, 15, 20 +MAX_DISCHARGE_CURRENT_SOC_FRACTION = 0.10, 0.20, 0.50, 1.00 ; --------- Time-To-Go --------- @@ -383,6 +386,13 @@ BATTERY_CAPACITY = 50 ; Invert Battery Current. Default non-inverted. Set to -1 to invert INVERT_CURRENT_MEASUREMENT = 1 +; -- JKBMS settings +; Predefines cell count for Jkbms_can +; The cell count should be auto-detected by identifying the highest cell number, +; but this process may be sometimes slow what could cause that cells voltage is not not +; updated in VenusOS. Try this workaround if you experience problems with cell voltage. +JKBMS_CAN_CELL_COUNT = 1 + ; -- ESC GreenMeter and Lipro device settings GREENMETER_ADDRESS = 1 LIPRO_START_ADDRESS = 2 diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 0d3fd030..ad0389d5 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -128,7 +128,8 @@ def get_battery(_port) -> Union[Battery, None]: 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}" + "Non blocking exception occurred: " + + f"{repr(exception_object)} of type {exception_type} in {file} line #{line}" ) # Ignore any malfunction test_function() pass @@ -222,14 +223,6 @@ def get_port() -> str: logger.error("ERROR >>> No battery connection at " + port) sys.exit(1) - # get SoC from battery, else None is displayed - if utils.SOC_CALCULATION: - battery.soc_calculation() - else: - battery.soc_calc = battery.soc - - battery.log_settings() - # Have a mainloop, so we can send/receive asynchronous calls to and from dbus DBusGMainLoop(set_as_default=True) if sys.version_info.major == 2: @@ -248,6 +241,9 @@ def get_port() -> str: # if not possible, poll the battery every poll_interval milliseconds gobject.timeout_add(battery.poll_interval, lambda: poll_battery(mainloop)) + # print log at this point, else not all data is correctly populated + battery.log_settings() + # Run the main loop try: mainloop.run() diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 2d1609f6..5601c67b 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -39,7 +39,6 @@ def __init__(self, battery): self.instance = 1 self.settings = None self.error = {"count": 0, "timestamp_first": None, "timestamp_last": None} - self.block_because_disconnect = False self.cell_voltages_good = False self._dbusname = ( "com.victronenergy.battery." @@ -228,15 +227,20 @@ def setup_instance(self): # set found_bms to true found_bms = True - # get the instance from the object name - device_instance = int( - value["ClassAndVrmInstance"][ - value["ClassAndVrmInstance"].rfind(":") + 1 : - ] - ) - logger.info( - f"Found existing battery with DeviceInstance = {device_instance}" - ) + # check if the battery has ClassAndVrmInstance set + if ( + "ClassAndVrmInstance" in value + and value["ClassAndVrmInstance"] != "" + ): + # get the instance from the object name + device_instance = int( + value["ClassAndVrmInstance"][ + value["ClassAndVrmInstance"].rfind(":") + 1 : + ] + ) + logger.info( + f"Found existing battery with DeviceInstance = {device_instance}" + ) # check if the battery has AllowMaxVoltage set if ( @@ -480,7 +484,9 @@ def setup_vedbus(self): # Create the mandatory objects self._dbusservice.add_path("/DeviceInstance", self.instance) - self._dbusservice.add_path("/ProductId", 0x0) + self._dbusservice.add_path( + "/ProductId", 0xBA77 + ) # set to "BATT", little gimmick self._dbusservice.add_path("/ProductName", self.battery.product_name()) self._dbusservice.add_path("/FirmwareVersion", str(utils.DRIVER_VERSION)) self._dbusservice.add_path("/HardwareVersion", self.battery.hardware_version) @@ -759,7 +765,7 @@ def publish_battery(self, loop): # unblock charge/discharge, if it was blocked when battery went offline if utils.BLOCK_ON_DISCONNECT: - self.block_because_disconnect = False + self.battery.block_because_disconnect = False else: # update error variables @@ -789,7 +795,7 @@ def publish_battery(self, loop): # block charge/discharge if utils.BLOCK_ON_DISCONNECT: - self.block_because_disconnect = True + self.battery.block_because_disconnect = True # if the battery did not update in 60 second, it's assumed to be completely failed if time_since_first_error >= 60 and ( @@ -860,34 +866,19 @@ def publish_dbus(self): self._dbusservice["/History/ChargeCycles"] = self.battery.cycles self._dbusservice["/History/TotalAhDrawn"] = self.battery.total_ah_drawn self._dbusservice["/Io/AllowToCharge"] = ( - 1 - if self.battery.charge_fet - and self.battery.control_allow_charge - and self.block_because_disconnect is False - else 0 + 1 if self.battery.get_allow_to_charge() else 0 ) self._dbusservice["/Io/AllowToDischarge"] = ( - 1 - if self.battery.discharge_fet - and self.battery.control_allow_discharge - and self.block_because_disconnect is False - else 0 + 1 if self.battery.get_allow_to_discharge() else 0 + ) + self._dbusservice["/Io/AllowToBalance"] = ( + 1 if self.battery.get_allow_to_balance() else 0 ) - self._dbusservice["/Io/AllowToBalance"] = 1 if self.battery.balance_fet else 0 self._dbusservice["/System/NrOfModulesBlockingCharge"] = ( - 0 - if ( - self.battery.charge_fet is None - or (self.battery.charge_fet and self.battery.control_allow_charge) - ) - and self.block_because_disconnect is False - else 1 + 0 if self.battery.get_allow_to_charge() else 1 ) self._dbusservice["/System/NrOfModulesBlockingDischarge"] = ( - 0 - if (self.battery.discharge_fet is None or self.battery.discharge_fet) - and self.block_because_disconnect is False - else 1 + 0 if self.battery.get_allow_to_discharge() else 1 ) self._dbusservice["/System/NrOfModulesOnline"] = 1 if self.battery.online else 0 self._dbusservice["/System/NrOfModulesOffline"] = ( @@ -926,7 +917,7 @@ def publish_dbus(self): self.battery.control_discharge_current ) - # Voltage and charge control info + # Voltage and charge control info (custom dbus paths) 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 @@ -985,7 +976,7 @@ def publish_dbus(self): self.battery.protection.temp_low_discharge ) self._dbusservice["/Alarms/BmsCable"] = ( - 2 if self.block_because_disconnect else 0 + 2 if self.battery.block_because_disconnect else 0 ) self._dbusservice["/Alarms/HighInternalTemperature"] = ( self.battery.protection.temp_high_internal @@ -1019,6 +1010,13 @@ def publish_dbus(self): 3, ) 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( + "Non blocking exception occurred: " + + f"{repr(exception_object)} of type {exception_type} in {file} line #{line}" + ) pass # Update TimeToGo and/or TimeToSoC @@ -1091,7 +1089,8 @@ def publish_dbus(self): file = exception_traceback.tb_frame.f_code.co_filename line = exception_traceback.tb_lineno logger.error( - f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}" + "Non blocking exception occurred: " + + f"{repr(exception_object)} of type {exception_type} in {file} line #{line}" ) pass @@ -1148,6 +1147,10 @@ def getSettingsWithValues( def setSetting( self, bus, service: str, object_path: str, setting_name: str, value ) -> bool: + # check if value is None + if value is None: + return False + obj = bus.get_object(service, object_path + "/" + setting_name) # iface = dbus.Interface(obj, "org.freedesktop.DBus.Introspectable") # xml_string = iface.Introspect() diff --git a/etc/dbus-serialbattery/install.sh b/etc/dbus-serialbattery/install.sh old mode 100644 new mode 100755 diff --git a/etc/dbus-serialbattery/reinstall-local.sh b/etc/dbus-serialbattery/reinstall-local.sh index c9697cdf..0851db4c 100755 --- a/etc/dbus-serialbattery/reinstall-local.sh +++ b/etc/dbus-serialbattery/reinstall-local.sh @@ -199,7 +199,8 @@ bluetooth_length=${#bms_array[@]} # echo $bluetooth_length # stop all dbus-blebattery services, if at least one exists -if [ -d "/service/dbus-blebattery.0" ]; then +if ls /service/dbus-blebattery.* 1> /dev/null 2>&1; then + echo "Killing old BLE battery services..." svc -t /service/dbus-blebattery.* # always remove existing blebattery services to cleanup @@ -386,7 +387,8 @@ can_lenght=${#can_array[@]} # echo $can_lenght # stop all dbus-canbattery services, if at least one exists -if [ -d "/service/dbus-canbattery.0" ]; then +if ls /service/dbus-canbattery.* 1> /dev/null 2>&1; then + echo "Killing old CAN battery services..." svc -t /service/dbus-canbattery.* # always remove existing canbattery services to cleanup @@ -395,10 +397,7 @@ if [ -d "/service/dbus-canbattery.0" ]; then # 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 " + pkill -f "python .*/dbus-serialbattery.py can.*" fi @@ -434,7 +433,7 @@ if [ "$can_lenght" -gt 0 ]; then if [ ! -f "/usr/lib/python3.8/site-packages/can/__init__.py" ]; then echo "Install can..." # Note: 4.x version of python-can causes pip dependency issue on VenusOS v3.22, so using python-can 3.x - # "ERROR: Could not build wheels for msgpack which use PEP 517 and cannot be installed directly" + # "ERROR: Could not build wheels for msgpack which use PEP 517 and cannot be installed directly" pip3 install python-can==3.3.4 echo fi @@ -550,7 +549,7 @@ echo " and add them to \"/data/etc/dbus-serialbattery/config.ini echo echo echo "GUIv2: If you want to try the new GUIv2 follow this link:" -echo " https://github.com/mr-manuel/venus-os_dbus-serialbattery/tree/dev/gui-v2" +echo " https://github.com/mr-manuel/venus-os_dbus-serialbattery/tree/master/gui-v2" echo echo # print which version was installed diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 7bff655f..76df3d02 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -37,7 +37,7 @@ def _get_list_from_config( # Constants -DRIVER_VERSION = "1.2.20240227dev" +DRIVER_VERSION = "1.2.20240404dev" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" @@ -53,6 +53,7 @@ def _get_list_from_config( # save config values to constants + # --------- Battery Current limits --------- MAX_BATTERY_CHARGE_CURRENT = float(config["DEFAULT"]["MAX_BATTERY_CHARGE_CURRENT"]) MAX_BATTERY_DISCHARGE_CURRENT = float( @@ -250,6 +251,9 @@ def _get_list_from_config( BATTERY_CAPACITY = float(config["DEFAULT"]["BATTERY_CAPACITY"]) INVERT_CURRENT_MEASUREMENT = int(config["DEFAULT"]["INVERT_CURRENT_MEASUREMENT"]) +# -- JK BMS settings +JKBMS_CAN_CELL_COUNT = int(config["DEFAULT"]["JKBMS_CAN_CELL_COUNT"]) + # -- ESC GreenMeter and Lipro device settings GREENMETER_ADDRESS = int(config["DEFAULT"]["GREENMETER_ADDRESS"]) LIPRO_START_ADDRESS = int(config["DEFAULT"]["LIPRO_START_ADDRESS"]) diff --git a/gui-v2/README.md b/gui-v2/README.md index 2f139fbc..73eba3a0 100644 --- a/gui-v2/README.md +++ b/gui-v2/README.md @@ -1,18 +1,85 @@ -# Venus OS GUIv2 - Alpha +# dbus-serialbattery +This is a driver for Venus OS devices (any GX device sold by Victron or a Raspberry Pi running the Venus OS image). -## 🚨 NOTE 🚨 +The driver will communicate with a Battery Management System (BMS) that support serial (RS232, RS485 or TTL UART) and Bluetooth communication (see [BMS feature comparison](https://louisvdw.github.io/dbus-serialbattery/general/features#bms-feature-comparison) for details). The data is then published to the Venus OS system (dbus). The main purpose is to act as a Battery Monitor in your GX and supply State of Charge (SoC) and other values to the inverter/charger. -* Currently you can only access through a browser to this GUI -* Many functions are not yet visible in the new GUI, also it is not sure that this is the way it will be implemented in future -* Do not open an issue in the GitHub repository for any problem or feedback. It will be directly closed -* Check [Venus OS: modifying gui-v2](https://community.victronenergy.com/questions/245056/venus-os-modifying-gui-v2.html) for more informations +## Documentation -## Install +* [Introduction](https://louisvdw.github.io/dbus-serialbattery/) +* [Features](https://louisvdw.github.io/dbus-serialbattery/general/features) +* [Supported BMS](https://louisvdw.github.io/dbus-serialbattery/general/supported-bms) +* [How to install, update, disable, enable and uninstall](https://louisvdw.github.io/dbus-serialbattery/general/install) +* [How to troubleshoot](https://louisvdw.github.io/dbus-serialbattery/troubleshoot/) +* [FAQ](https://louisvdw.github.io/dbus-serialbattery/faq/) -```bash -wget -O - https://raw.githubusercontent.com/mr-manuel/venus-os_dbus-serialbattery/dev/gui-v2/install-new-webgui.sh | bash -``` +### Supporting this project +If you find this driver helpful please consider supporting this project. You can buy me a Ko-Fi or get in contact, if you would like to donate hardware for development. -## How to open +### Support [Louisvdw](https://github.com/Louisvdw) +* Main developer +* Added most of the BMS drivers -Open your browser and navigate to [http://venusos/gui-battery](http://venusos/gui-battery). +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Z8Z73LCW1) or using [Paypal.me](https://paypal.me/innernet) + +### Support [mr-manuel](https://github.com/mr-manuel) +* Added a lot of features, optimizations and improvements with `v1.0.x` +* Assistance with the issues and discussions of forum +* Added a lot of documentation to the config file and notes that are displayed after installation for better understanding +* Introduced the new documentation page of the driver and reworked a great part of it for easier understanding + +[](https://www.paypal.com/donate/?hosted_button_id=3NEVZBDM5KABW) + +### Developer Remarks +To develop this project, install the requirements. This project makes use of velib_python which is pre-installed on +Venus-OS Devices under `/opt/victronenergy/dbus-systemcalc-py/ext/velib_python`. To use the python files locally, +`git clone` the [velib_python](https://github.com/victronenergy/velib_python) project to velib_python and add +velib_python to the `PYTHONPATH` environment variable. + +Make sure the GitHub Actions run fine in your repository. In order to make the GitHub Actions run please select in your repository settings under `Actions` -> `General` -> `Actions permissions` the option `Allow all actions and reusable workflows`. Check also in your repository settings under `Actions` -> `General` -> `Workflow permissions` if `Read and write permissions` are selected. This will check your code for Flake8 and Black Lint errors. [Here](https://py-vscode.readthedocs.io/en/latest/files/linting.html) is a short instruction on how to set up Flake8 and Black Lint checks in VS Code. This will save you a lot of time. + +See this checklist, if you want to [add a new BMS](https://louisvdw.github.io/dbus-serialbattery/general/supported-bms#add-by-opening-a-pull-request) + +#### How it works +* Each supported BMS needs to implement the abstract base class `Battery` from `battery.py`. +* `dbus-serialbattery.py` tries to figure out the correct connected BMS by looping through all known implementations of +`Battery` and executing its `test_connection()`. If this returns true, `dbus-serialbattery.py` sticks with this battery +and then periodically executes `dbushelpert.publish_battery()`. `publish_battery()` executes `Battery.refresh_data()` which +updates the fields of Battery. It then publishes those fields to dbus using `dbushelper.publish_dbus()` +* The Victron Device will be "controlled" by the values published on `/Info/` - namely: + * `/Info/MaxChargeCurrent ` + * `/Info/MaxDischargeCurrent` + * `/Info/MaxChargeVoltage` + * `/Info/BatteryLowVoltage` + * `/Info/ChargeRequest` (not implemented in dbus-serialbattery) + +For more details on the victron dbus interface see [the official victron dbus documentation](https://github.com/victronenergy/venus/wiki/dbus) + +## Screenshots + +### Venus OS + +![VenusOS](docs/screenshots/venus-os_001.png) +![VenusOS](docs/screenshots/venus-os_002.png) +![VenusOS](docs/screenshots/venus-os_003.png) +![VenusOS](docs/screenshots/venus-os_004.png) +![VenusOS](docs/screenshots/venus-os_005.png) +![VenusOS](docs/screenshots/venus-os_006.png) +![VenusOS](docs/screenshots/venus-os_007.png) +![VenusOS](docs/screenshots/venus-os_008.png) +![VenusOS](docs/screenshots/venus-os_009.png) +![VenusOS](docs/screenshots/venus-os_010.png) +![VenusOS](docs/screenshots/venus-os_011.png) +![VenusOS](docs/screenshots/venus-os_012.png) +![VenusOS](docs/screenshots/venus-os_013.png) + +### VRM Portal + +![VenusOS](docs/screenshots/vrm-portal_001.png) +![VenusOS](docs/screenshots/vrm-portal_002.png) +![VenusOS](docs/screenshots/vrm-portal_003.png) +![VenusOS](docs/screenshots/vrm-portal_004.png) +![VenusOS](docs/screenshots/vrm-portal_005.png) +![VenusOS](docs/screenshots/vrm-portal_006.png) +![VenusOS](docs/screenshots/vrm-portal_007.png) +![VenusOS](docs/screenshots/vrm-portal_008.png) +![VenusOS](docs/screenshots/vrm-portal_009.png)