Skip to content

Commit

Permalink
Merge branch 'dev' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-manuel committed Dec 23, 2023
2 parents 9e6d1d0 + f67f363 commit 6a5ed30
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 63 deletions.
94 changes: 80 additions & 14 deletions etc/dbus-serialbattery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ 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
Expand Down Expand Up @@ -230,6 +234,11 @@ def manage_charge_voltage(self) -> None:
manages the charge voltage by setting self.control_voltage
:return: None
"""
if utils.SOC_CALCULATION:
self.soc_calculation()
else:
self.soc_calc = self.soc

self.prepare_voltage_management()
if utils.CVCM_ENABLE:
if utils.LINEAR_LIMITATION_ENABLE:
Expand All @@ -241,6 +250,60 @@ def manage_charge_voltage(self) -> None:
self.control_voltage = round(self.max_battery_voltage, 3)
self.charge_mode = "Keep always max voltage"

def soc_calculation(self) -> None:
current_time = time()
voltageSum = 0
current_corr = 0

for i in range(self.cell_count):
voltage = self.get_cell_voltage(i)
if voltage:
voltageSum += voltage

if self.soc_calc_capacity_remain:
current_corr = utils.calcLinearRelationship(
self.current,
utils.SOC_CALC_CURRENT_MEASURED,
utils.SOC_CALC_CURRENT_REAL,
)

self.soc_calc_capacity_remain = (
self.soc_calc_capacity_remain
+ current_corr
* (current_time - self.soc_calc_capacity_remain_lasttime)
/ 3600
)
self.soc_calc_capacity_remain_lasttime = current_time
# Reset-Condition
if (
self.current < utils.SOC_RESET_CURRENT
and (self.max_battery_voltage - utils.VOLTAGE_DROP <= voltageSum)
and self.soc_calc_reset_starttime
):
if (
int(current_time) - self.soc_calc_reset_starttime
) > utils.SOC_RESET_TIME:
self.soc_calc_capacity_remain = self.capacity
else:
self.soc_calc_reset_starttime = int(current_time)
else:
if self.soc_calc is None:
# if soc_calc was not stored in dbus then initialize with the soc reported by the bms
if self.soc is not None:
self.soc_calc_capacity_remain = (
self.capacity * self.soc / 100)
else:
# if there is no soc from bms then set to 100%
self.soc_calc_capacity_remain = self.capacity
else:
self.soc_calc_capacity_remain = self.capacity * self.soc_calc / 100
self.soc_calc_capacity_remain_lasttime = current_time

# Calculate the SOC based on remaining capacity
self.soc_calc = max(
min((self.soc_calc_capacity_remain / self.capacity) * 100, 100), 0
)

def prepare_voltage_management(self) -> None:
soc_reset_last_reached_days_ago = (
0
Expand Down Expand Up @@ -330,23 +393,26 @@ def manage_charge_voltage_linear(self) -> None:

# allow max voltage again, if cells are unbalanced or SoC threshold is reached
elif (
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc_calc
or voltageDiff >= utils.CELL_VOLTAGE_DIFF_TO_RESET_VOLTAGE_LIMIT
) and not self.allow_max_voltage:
self.allow_max_voltage = True
else:
pass

else:
if voltageDiff > utils.CELL_VOLTAGE_DIFF_KEEP_MAX_VOLTAGE_TIME_RESTART:
self.max_voltage_start_time = current_time

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:
if self.soc_calc <= 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"Could not change to float voltage. Battery SoC ({self.soc_calc}%) 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".'
Expand Down Expand Up @@ -485,7 +551,7 @@ def manage_charge_voltage_linear(self) -> None:
)
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" • SoC: {self.soc}% SoC_Calc {self.soc_calc}%"
self.charge_mode_debug += (
f" • Reset SoC: {utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT}%"
)
Expand Down Expand Up @@ -552,7 +618,7 @@ def manage_charge_voltage_step(self) -> None:
# 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
utils.SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT > self.soc_calc
and not self.allow_max_voltage
):
self.allow_max_voltage = True
Expand Down Expand Up @@ -857,10 +923,10 @@ def calcMaxChargeCurrentReferringToSoc(self) -> float:
]
if utils.LINEAR_LIMITATION_ENABLE:
return utils.calcLinearRelationship(
self.soc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC
self.soc_calc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC
)
return utils.calcStepRelationship(
self.soc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC, True
self.soc_calc, SOC_WHILE_CHARGING, MAX_CHARGE_CURRENT_SOC, True
)
except Exception:
return self.max_battery_charge_current
Expand All @@ -881,10 +947,10 @@ def calcMaxDischargeCurrentReferringToSoc(self) -> float:
]
if utils.LINEAR_LIMITATION_ENABLE:
return utils.calcLinearRelationship(
self.soc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC
self.soc_calc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC
)
return utils.calcStepRelationship(
self.soc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC, True
self.soc_calc, SOC_WHILE_DISCHARGING, MAX_DISCHARGE_CURRENT_SOC, True
)
except Exception:
return self.max_battery_charge_current
Expand Down Expand Up @@ -942,15 +1008,15 @@ def get_cell_balancing(self, idx: int) -> Union[int, None]:
def get_capacity_remain(self) -> Union[float, None]:
if self.capacity_remain is not None:
return self.capacity_remain
if self.capacity is not None and self.soc is not None:
return self.capacity * self.soc / 100
if self.capacity is not None and self.soc_calc is not None:
return self.capacity * self.soc_calc / 100
return None

def get_timeToSoc(
self, soc_target: float, percent_per_second: float, only_number: bool = False
) -> str:
if self.current > 0:
soc_diff = soc_target - self.soc
soc_diff = soc_target - self.soc_calc
else:
soc_diff = self.soc - soc_target

Expand All @@ -964,7 +1030,7 @@ def get_timeToSoc(

time_to_go_str = None
if (
self.soc != soc_target
self.soc_calc != soc_target
and percent_per_second != 0
and (soc_diff > 0 or utils.TIME_TO_SOC_INC_FROM is True)
):
Expand Down Expand Up @@ -1247,7 +1313,7 @@ def log_settings(self) -> None:
logger.info(f"Battery {self.type} connected to dbus from {self.port}")
logger.info("========== Settings ==========")
logger.info(
f"> Connection voltage: {self.voltage}V | Current: {self.current}A | SoC: {self.soc}%"
f"> Connection voltage: {self.voltage}V | Current: {self.current}A | SoC: {self.soc_calc}%"
)
logger.info(
f"> Cell count: {self.cell_count} | Cells populated: {cell_counter}"
Expand Down
130 changes: 85 additions & 45 deletions etc/dbus-serialbattery/bms/lltjbd_ble.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import threading
import sys
import re
from asyncio import CancelledError
from time import sleep
from typing import Union, Optional
Expand Down Expand Up @@ -43,6 +44,21 @@ def __init__(self, port: Optional[str], baud: Optional[int], address: str):
self.response_queue: Optional[asyncio.Queue] = None
self.ready_event: Optional[asyncio.Event] = None

self.hci_uart_ok = True
if not os.path.isfile("/tmp/dbus-blebattery-hciattach"):
execfile = open("/tmp/dbus-blebattery-hciattach", "w")
execpath = os.popen('ps -ww | grep hciattach | grep -v grep').read()
execpath = re.search("/usr/bin/hciattach.+", execpath)
execfile.write(execpath.group())
execfile.close()
else:
execpath = os.popen('ps -ww | grep hciattach | grep -v grep').read()
if not execpath:
execfile = open("/tmp/dbus-blebattery-hciattach", "r")
os.system(execfile.readline())
execfile.close()


logger.info("Init of LltJbd_Ble at " + address)

def connection_name(self) -> str:
Expand All @@ -64,10 +80,14 @@ async def bt_main_loop(self):
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}"
)
if "Bluetooth adapters" in repr(exception_object):
self.reset_hci_uart()
else:
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
Expand Down Expand Up @@ -131,20 +151,21 @@ def background_loop(self):
asyncio.run(self.bt_main_loop())

async def async_test_connection(self):
self.ready_event = asyncio.Event()
if not self.bt_thread.is_alive():
self.bt_thread.start()

def shutdown_ble_atexit(thread):
self.run = False
thread.join()

atexit.register(shutdown_ble_atexit, self.bt_thread)
try:
return await asyncio.wait_for(self.ready_event.wait(), timeout=5)
except asyncio.TimeoutError:
logger.error(">>> ERROR: Unable to connect with BLE device")
return False
if self.hci_uart_ok:
self.ready_event = asyncio.Event()
if not self.bt_thread.is_alive():
self.bt_thread.start()

def shutdown_ble_atexit(thread):
self.run = False
thread.join()

atexit.register(shutdown_ble_atexit, self.bt_thread)
try:
return await asyncio.wait_for(self.ready_event.wait(), timeout=5)
except asyncio.TimeoutError:
logger.error(">>> ERROR: Unable to connect with BLE device")
return False

def test_connection(self):
# call a function that will connect to the battery, send a command and retrieve the result.
Expand Down Expand Up @@ -201,33 +222,34 @@ def rx_callback(future: asyncio.Future, data: bytearray, sender, rx: bytearray):
return result

async def async_read_serial_data_llt(self, command):
try:
bt_task = asyncio.run_coroutine_threadsafe(
self.send_command(command), self.bt_loop
)
result = await asyncio.wait_for(asyncio.wrap_future(bt_task), 20)
return result
except asyncio.TimeoutError:
logger.error(">>> ERROR: No reply - returning")
return False
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
if self.hci_uart_ok:
try:
bt_task = asyncio.run_coroutine_threadsafe(
self.send_command(command), self.bt_loop
)
result = await asyncio.wait_for(asyncio.wrap_future(bt_task), 20)
return result
except asyncio.TimeoutError:
logger.error(">>> ERROR: No reply - returning")
return False
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):
if not self.bt_loop:
Expand Down Expand Up @@ -268,6 +290,24 @@ def reset_bluetooth(self):
sleep(5)
sys.exit(1)

def reset_hci_uart(self):
logger.error("Reset of hci_uart stack... Reconnecting to: " + self.address)
self.run = False
os.system("pkill -f 'hciattach'")
sleep(0.5)
os.system("rmmod hci_uart")
os.system("rmmod btbcm")
os.system("modprobe hci_uart")
os.system("modprobe btbcm")
sys.exit(1)
# execfile = open("/tmp/dbus-blebattery-hciattach", "r")
# sleep(5)
# os.system(execfile.readline())
# os.system(execfile.readline())
# execfile.close()
# sleep(0.5)
# os.system("bluetoothctl connect " + self.address)
# self.run = True

if __name__ == "__main__":
bat = LltJbd_Ble("Foo", -1, sys.argv[1])
Expand Down
Loading

0 comments on commit 6a5ed30

Please sign in to comment.