Skip to content

Commit

Permalink
Merge pull request #876 from mr-manuel/dev
Browse files Browse the repository at this point in the history
Changes 2023.11.26
  • Loading branch information
mr-manuel committed Nov 26, 2023
2 parents 28a5174 + 5f5cb6d commit da73f05
Show file tree
Hide file tree
Showing 9 changed files with 500 additions and 240 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# Changelog

## Notes

* The Bluetooth and CAN connections are still not stable on some systems. If you want to have a stable connection use the serial connection.

## Breaking changes

* Driver version greater or equal to `v1.0.20231126beta`

The custom name is not saved to the config file anymore, but to the dbus service com.victronenergy.settings. You have to re-enter it once.

* 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:
Expand All @@ -24,8 +32,10 @@
* 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 current charge state for driver restart or device reboot. Fixes https://github.com/Louisvdw/dbus-serialbattery/issues/840 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: The device instance does not change anymore when you plug the BMS into another USB port. Fixed https://github.com/Louisvdw/dbus-serialbattery/issues/718 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
Expand Down
288 changes: 74 additions & 214 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import math
from time import time
from abc import ABC, abstractmethod
import re
import sys


class Protection(object):
Expand Down Expand Up @@ -57,78 +55,78 @@ class Battery(ABC):
use the individual implementations as type Battery and work with it.
"""

def __init__(self, port, baud, address):
self.port = port
self.baud_rate = baud
self.role = "battery"
self.type = "Generic"
self.poll_interval = 1000
self.online = True
self.hardware_version = None
self.cell_count = None
def __init__(self, port: str, baud: int, address: str):
self.port: str = port
self.baud_rate: int = baud
self.role: str = "battery"
self.type: str = "Generic"
self.poll_interval: int = 1000
self.online: bool = True
self.hardware_version: str = None
self.cell_count: int = None
# max battery charge/discharge current
self.max_battery_charge_current = None
self.max_battery_discharge_current = None
self.has_settings = 0
self.max_battery_charge_current: float = None
self.max_battery_discharge_current: float = None
self.has_settings: bool = False

# fetched from the BMS from a field where the user can input a custom string
# only if available
self.custom_field = None
self.custom_field: str = 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
self.total_ah_drawn = None
self.voltage: float = None
self.current: float = None
self.current_avg: float = None
self.current_avg_lst: list = []
self.capacity_remain: float = None
self.capacity: float = None
self.cycles: float = None
self.total_ah_drawn: float = None
self.production = None
self.protection = Protection()
self.version = None
self.soc = None
self.time_to_soc_update = 0
self.charge_fet = None
self.discharge_fet = None
self.balance_fet = None
self.temp_sensors = None
self.temp1 = None
self.temp2 = None
self.temp3 = None
self.temp4 = None
self.temp_mos = None
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
self.temp3: float = None
self.temp4: float = None
self.temp_mos: float = None
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.control_current = None
self.control_previous_total = None
self.control_previous_max = None
self.control_discharge_current = None
self.control_charge_current = None
self.control_allow_charge = None
self.control_allow_discharge = None
# self.control_charging = None # seems unused
self.control_voltage: float = None
self.soc_reset_requested: bool = False
self.soc_reset_last_reached: int = 0 # save state to preserve on restart
self.soc_reset_battery_voltage: int = None
self.max_battery_voltage: float = None
self.min_battery_voltage: float = None
self.allow_max_voltage: bool = True # save state to preserve on restart
self.max_voltage_start_time: int = None # save state to preserve on restart
self.transition_start_time: int = None
# self.control_voltage_at_transition_start = None # seems unused
self.charge_mode: str = None
self.charge_mode_debug: str = ""
self.charge_limitation: str = None
self.discharge_limitation: str = None
self.linear_cvl_last_set: int = 0
self.linear_ccl_last_set: int = 0
self.linear_dcl_last_set: int = 0
# self.control_current = None # seems unused
# self.control_previous_total = None # seems unused
# self.control_previous_max = None # seems unused
self.control_discharge_current: int = None
self.control_charge_current: int = None
self.control_allow_charge: bool = None
self.control_allow_discharge: bool = None

@abstractmethod
def test_connection(self) -> bool:
Expand Down Expand Up @@ -487,7 +485,7 @@ def manage_charge_voltage_linear(self) -> None:
self.control_voltage = None
self.charge_mode = "--"

def set_cvl_linear(self, control_voltage) -> bool:
def set_cvl_linear(self, control_voltage: float) -> bool:
"""
set CVL only once every LINEAR_RECALCULATION_EVERY seconds
:return: bool
Expand Down Expand Up @@ -899,12 +897,12 @@ def get_max_cell_desc(self) -> Union[str, None]:
cell_no = self.get_max_cell()
return cell_no if cell_no is None else "C" + str(cell_no + 1)

def get_cell_voltage(self, idx) -> Union[float, None]:
def get_cell_voltage(self, idx: int) -> Union[float, None]:
if idx >= min(len(self.cells), self.cell_count):
return None
return self.cells[idx].voltage

def get_cell_balancing(self, idx) -> Union[int, None]:
def get_cell_balancing(self, idx: int) -> Union[int, None]:
if idx >= min(len(self.cells), self.cell_count):
return None
if self.cells[idx].balance is not None and self.cells[idx].balance:
Expand All @@ -918,7 +916,9 @@ def get_capacity_remain(self) -> Union[float, None]:
return self.capacity * self.soc / 100
return None

def get_timeToSoc(self, socnum, crntPrctPerSec, onlyNumber=False) -> str:
def get_timeToSoc(
self, socnum: float, crntPrctPerSec: float, onlyNumber: bool = False
) -> str:
if self.current > 0:
diffSoc = socnum - self.soc
else:
Expand Down Expand Up @@ -949,7 +949,7 @@ def get_timeToSoc(self, socnum, crntPrctPerSec, onlyNumber=False) -> str:

return ttgStr

def get_secondsToString(self, timespan, precision=3) -> str:
def get_secondsToString(self, timespan: int, precision: int = 3) -> str:
"""
Transforms seconds to a string in the format: 1d 1h 1m 1s (Victron Style)
:param precision:
Expand Down Expand Up @@ -1253,161 +1253,21 @@ def log_settings(self) -> None:

return

# save custom name to config file
def custom_name_callback(self, path, value):
try:
if path == "/CustomName":
file = open(
"/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "r"
)
lines = file.readlines()
last = len(lines)

# remove not allowed characters
value = value.replace(":", "").replace("=", "").replace(",", "").strip()

# empty string to save new config file
config_file_new = ""

# make sure we are in the [DEFAULT] section
current_line_in_default_section = False
default_section_checked = False

# check if already exists
exists = False

# count lines
i = 0
# looping through the file
for line in lines:
# increment by one
i += 1

# stripping line break
line = line.strip()

# check, if current line is after the [DEFAULT] section
if line == "[DEFAULT]":
current_line_in_default_section = True

# check, if current line starts a new section
if line != "[DEFAULT]" and re.match(r"^\[.*\]", line):
# set default_section_checked to true, if it was already checked and a new section comes on
if current_line_in_default_section and not exists:
default_section_checked = True
current_line_in_default_section = False

# check, if the current line is the last line
if i == last:
default_section_checked = True

# insert or replace only in [DEFAULT] section
if current_line_in_default_section and re.match(
r"^CUSTOM_BATTERY_NAMES.*", line
):
# set that the setting was found, else a new one is created
exists = True

# remove setting name
line = re.sub(
"^CUSTOM_BATTERY_NAMES\s*=\s*", "", line # noqa: W605
)

# change only the name of the current BMS
result = []
bms_name_list = line.split(",")
for bms_name_pair in bms_name_list:
tmp = bms_name_pair.split(":")
if tmp[0] == self.port:
result.append(tmp[0] + ":" + value)
else:
result.append(bms_name_pair)

new_line = "CUSTOM_BATTERY_NAMES = " + ",".join(result)

else:
if default_section_checked and not exists:
exists = True

# add before current line
if i != last:
new_line = (
"CUSTOM_BATTERY_NAMES = "
+ self.port
+ ":"
+ value
+ "\n\n"
+ line
)

# add at the end if last line
else:
new_line = (
line
+ "\n\n"
+ "CUSTOM_BATTERY_NAMES = "
+ self.port
+ ":"
+ value
)
else:
new_line = line
# concatenate the new string and add an end-line break
config_file_new = config_file_new + new_line + "\n"

# close the file
file.close()
# Open file in write mode
write_file = open(
"/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "w"
)
# overwriting the old file contents with the new/replaced content
write_file.write(config_file_new)
# close the file
write_file.close()

# logger.error("value (saved): " + str(value))

"""
# this removes all comments and tranfsorm the values to lowercase
utils.config.set(
"DEFAULT",
"CUSTOM_BATTERY_NAMES",
self.port + ":" + value,
)
# Writing our configuration file to 'example.ini'
with open(
"/data/etc/dbus-serialbattery/" + utils.PATH_CONFIG_USER, "w"
) as configfile:
type(utils.config.write(configfile))
"""

except Exception:
exception_type, exception_object, exception_traceback = sys.exc_info()
file = exception_traceback.tb_frame.f_code.co_filename
line = exception_traceback.tb_lineno
logger.error(
f"Exception occurred: {repr(exception_object)} of type {exception_type} in {file} line #{line}"
)

return value

def reset_soc_callback(self, path, value):
def reset_soc_callback(self, path: str, value: int) -> bool:
# callback for handling reset soc request
return
return True

def force_charging_off_callback(self, path, value):
return
def force_charging_off_callback(self, path: str, value: int) -> bool:
return True

def force_discharging_off_callback(self, path, value):
return
def force_discharging_off_callback(self, path: str, value: int) -> bool:
return True

def turn_balancing_off_callback(self, path, value):
return
def turn_balancing_off_callback(self, path: str, value: int) -> bool:
return True

def trigger_soc_reset(self):
def trigger_soc_reset(self) -> bool:
"""
This method can be used to implement SOC reset when the battery is assumed to be full
"""
return
return True
2 changes: 1 addition & 1 deletion etc/dbus-serialbattery/bms/daly.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, port, baud, address):
self.cell_max_no = None
self.poll_interval = 1000
self.type = self.BATTERYTYPE
self.has_settings = 1
self.has_settings = True
self.reset_soc = 0
self.soc_to_set = None
self.runtime = 0 # TROUBLESHOOTING for no reply errors
Expand Down

0 comments on commit da73f05

Please sign in to comment.