Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signal strength, unique identifier, persist custom name, many improvements #701

Merged
merged 8 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
# Changelog

## v1.0.x
* Added: Bluetooth: Show signal strenght of BMS in log by @mr-manuel
* Added: Create unique identifier, if not provided from BMS by @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 - Show last five characters from the MAC address in the custom name (which is displayed in the device list) by @mr-manuel
* Added: Save custom name and make it restart persistant by @mr-manuel
* Changed: Fixed error in `reinstall-local.sh` script for Bluetooth installation by @mr-manuel
* Changed: Fixed typo in `config.ini` sample by @hoschult
* Changed: Improved driver reinstall when multiple Bluetooth BMS are enabled by @mr-manuel
* Changed: Improved Jkbms_Ble driver by @seidler2547 & @mr-manuel


## v1.0.20230531

Expand Down
177 changes: 169 additions & 8 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import math
from time import time
from abc import ABC, abstractmethod
import re
import sys


class Protection(object):
Expand Down Expand Up @@ -69,15 +71,12 @@ 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):
self.voltage = None
self.current = None
Expand Down Expand Up @@ -129,11 +128,34 @@ 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
"""
return (
"".join(filter(str.isalnum, self.hardware_version))
+ "_"
+ str(self.capacity)
+ "Ah"
)

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 + ")"
Expand Down Expand Up @@ -994,11 +1016,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
Expand Down
14 changes: 9 additions & 5 deletions etc/dbus-serialbattery/bms/battery_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ def test_connection(self):

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
Expand All @@ -53,11 +62,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):
Expand Down
14 changes: 9 additions & 5 deletions etc/dbus-serialbattery/bms/daly.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,13 +522,17 @@ def read_battery_code(self, ser):
" ",
(battery_code.strip()),
)
self.unique_identifier = self.custom_field.replace(" ", "_")
else:
self.unique_identifier = (
str(self.production) + "_" + str(int(self.capacity))
)
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
Expand Down
11 changes: 9 additions & 2 deletions etc/dbus-serialbattery/bms/heltecmodbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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]

Expand Down
12 changes: 10 additions & 2 deletions etc/dbus-serialbattery/bms/jkbms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -126,6 +127,7 @@ 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 3.500 V
offset = cellbyte_count + 18
self.soc = unpack_from(">B", self.get_data(status_data, b"\x85", offset, 1))[0]

Expand Down Expand Up @@ -183,9 +185,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()
Expand All @@ -208,6 +210,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])
Expand Down
Loading