-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'jkbms_ble' into jkbms_ble
- Loading branch information
Showing
5 changed files
with
1,876 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
[DEFAULT] | ||
LINEAR_LIMITATION_ENABLE = False | ||
|
||
; battery Current limits | ||
MAX_BATTERY_CHARGE_CURRENT = 70.0 | ||
MAX_BATTERY_DISCHARGE_CURRENT = 90.0 | ||
|
||
; -------- Cell Voltage limitation --------- | ||
; Description: | ||
; Maximal charge / discharge current will be in-/decreased depending on min- and max-cell-voltages | ||
; Example: 18cells * 3.55V/cell = 63.9V max charge voltage. 18 * 2.7V = 48,6V min discharge voltage | ||
; ... but 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 | ||
; Discharge current control management referring to cell-voltage enable (True/False). | ||
DCCM_CV_ENABLE = True | ||
|
||
; Set Steps to reduce battery current. The current will be changed linear between those steps | ||
CELL_VOLTAGES_WHILE_CHARGING = 3.55,3.50,3.45,3.30 | ||
MAX_CHARGE_CURRENT_CV_FRACTION = 0,0.05,0.5,1 | ||
|
||
CELL_VOLTAGES_WHILE_DISCHARGING = 2.70,2.80,2.90,3.10 | ||
MAX_DISCHARGE_CURRENT_CV_FRACTION = 0,0.1,0.5,1 | ||
|
||
; -------- Temperature limitation --------- | ||
; 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 | ||
; Charge current control management referring to temperature enable (True/False). | ||
DCCM_T_ENABLE = True | ||
|
||
; Set Steps to reduce battery current. The current will be changed linear between those steps | ||
TEMPERATURE_LIMITS_WHILE_CHARGING = 0,2,5,10,15,20,35,40,55 | ||
MAX_CHARGE_CURRENT_T_FRACTION = 0,0.1,0.2,0.4,0.8,1,1,0.4,0 | ||
|
||
TEMPERATURE_LIMITS_WHILE_DISCHARGING = -20,0,5,10,15,45,55 | ||
MAX_DISCHARGE_CURRENT_T_FRACTION = 0,.2,.3,.4,1,1,0 | ||
|
||
; if the cell voltage reaches 3.55V, then reduce current battery-voltage by 0.01V | ||
; if the cell voltage goes over 3.6V, then the maximum penalty will not be exceeded | ||
; there will be a sum of all penalties for each cell, which exceeds the limits | ||
PENALTY_AT_CELL_VOLTAGE = 3.45,3.55,3.6 | ||
; this voltage will be subtracted | ||
PENALTY_BATTERY_VOLTAGE = 0.01,1.0,2.0 | ||
|
||
|
||
; -------- SOC limitation --------- | ||
; Description: | ||
; Maximal charge / discharge current will be increased / decreased depending on State of Charge, see CC_SOC_LIMIT1 etc. | ||
; The State of Charge (SoC) charge / discharge current will be in-/decreased depending on SOC. | ||
; Example: 16cells * 3.45V/cell = 55,2V max charge voltage. 16*2.9V = 46,4V min discharge voltage | ||
; Cell min/max voltages - used with the cell count to get the min/max battery voltage | ||
MIN_CELL_VOLTAGE = 2.9 | ||
MAX_CELL_VOLTAGE = 3.45 | ||
FLOAT_CELL_VOLTAGE = 3.35 | ||
MAX_VOLTAGE_TIME_SEC = 900 | ||
SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90 | ||
|
||
; Charge current control management enable (True/False). | ||
CCCM_SOC_ENABLE = True | ||
; Discharge current control management enable (True/False). | ||
DCCM_SOC_ENABLE = True | ||
|
||
; charge current soc limits | ||
CC_SOC_LIMIT1 = 98 | ||
CC_SOC_LIMIT2 = 95 | ||
CC_SOC_LIMIT3 = 91 | ||
|
||
; charge current limits | ||
CC_CURRENT_LIMIT1_FRACTION = 0.1 | ||
CC_CURRENT_LIMIT2_FRACTION = 0.3 | ||
CC_CURRENT_LIMIT3_FRACTION = 0.5 | ||
|
||
; discharge current soc limits | ||
DC_SOC_LIMIT1 = 10 | ||
DC_SOC_LIMIT2 = 20 | ||
DC_SOC_LIMIT3 = 30 | ||
|
||
; discharge current limits | ||
DC_CURRENT_LIMIT1_FRACTION = 0.1 | ||
DC_CURRENT_LIMIT2_FRACTION = 0.3 | ||
DC_CURRENT_LIMIT3_FRACTION = 0.5 | ||
|
||
; Charge voltage control management enable (True/False). | ||
CVCM_ENABLE = False | ||
|
||
; Simulate Midpoint graph (True/False). | ||
MIDPOINT_ENABLE = False | ||
|
||
; soc low levels | ||
SOC_LOW_WARNING = 20 | ||
SOC_LOW_ALARM = 10 | ||
|
||
; Daly settings | ||
; Battery capacity (amps) if the BMS does not support reading it | ||
BATTERY_CAPACITY = 50 | ||
; Invert Battery Current. Default non-inverted. Set to -1 to invert | ||
INVERT_CURRENT_MEASUREMENT = 1 | ||
|
||
; TIME TO SOC settings [Valid values 0-100, but I don't recommend more that 20 intervals] | ||
; Set of SoC percentages to report on dbus. The more you specify the more it will impact system performance. | ||
; TIME_TO_SOC_POINTS = [100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0] | ||
; Every 5% SoC | ||
; TIME_TO_SOC_POINTS = [100, 95, 90, 85, 75, 50, 25, 20, 10, 0] | ||
; No data set to disable | ||
TIME_TO_SOC_POINTS = | ||
; Specify TimeToSoc value type: [Valid values 1,2,3] | ||
; TIME_TO_SOC_VALUE_TYPE = 1 ; Seconds | ||
; TIME_TO_SOC_VALUE_TYPE = 2 ; Time string HH:MN:SC | ||
; Both Seconds and time str "<seconds> [days, HR:MN:SC]" | ||
TIME_TO_SOC_VALUE_TYPE = 3 | ||
; Specify how many loop cycles between each TimeToSoc updates | ||
TIME_TO_SOC_LOOP_CYCLES = 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 = False | ||
|
||
|
||
; 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 = 1 | ||
|
||
; Settings for ESC GreenMeter and Lipro devices | ||
GREENMETER_ADDRESS = 1 | ||
LIPRO_START_ADDRESS = 2 | ||
LIPRO_END_ADDRESS = 4 | ||
LIPRO_CELL_COUNT = 15 | ||
|
||
PUBLISH_CONFIG_VALUES = 1 | ||
|
||
BMS_TYPE = |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
# -*- coding: utf-8 -*- | ||
from battery import Battery, Cell | ||
from utils import logger | ||
import utils | ||
import serial | ||
from time import sleep | ||
|
||
|
||
class HLPdataBMS4S(Battery): | ||
def __init__(self, port, baud, address): | ||
super(HLPdataBMS4S, self).__init__(port, baud, address) | ||
self.type = self.BATTERYTYPE | ||
|
||
BATTERYTYPE = "HLPdataBMS4S" | ||
|
||
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 | ||
result = False | ||
try: | ||
result = self.read_test_data() | ||
except Exception as err: | ||
logger.error(f"Unexpected {err=}, {type(err)=}") | ||
result = False | ||
|
||
return result | ||
|
||
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 | ||
result = False | ||
try: | ||
result = self.read_settings_data() | ||
except Exception as e: | ||
logger.error(e, exc_info=True) | ||
pass | ||
return result | ||
|
||
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 = False | ||
try: | ||
result = self.read_status_data() | ||
except Exception as e: | ||
logger.error(e, exc_info=True) | ||
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) | ||
if test_data is False: | ||
return False | ||
s1 = str(test_data) | ||
ix = s1.find("BMS4S") | ||
if ix > 0: | ||
self.hardware_version = s1[ix : len(s1) - 1] | ||
self.version = self.hardware_version | ||
self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT | ||
self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT | ||
self.poll_interval = 10000 | ||
self.control_discharge_current = 1000 | ||
self.control_charge_current = 1000 | ||
self.soc = 50 | ||
self.voltage = 13.2 | ||
self.current = 0 | ||
self.min_battery_voltage = 12.0 | ||
self.max_battery_voltage = 14.4 | ||
|
||
if self.cell_count is None: | ||
self.cell_count = 4 | ||
for c in range(self.cell_count): | ||
self.cells.append(Cell(False)) | ||
return True | ||
return False | ||
|
||
def read_settings_data(self): | ||
test_data = self.read_serial_data_HLPdataBMS4S(b"ps\n", 3, 700) | ||
if test_data is False: | ||
return False | ||
s = str(test_data) | ||
s = s.replace(",", ".") | ||
par = get_par("BatterySize= ", s) | ||
if par is False: | ||
return False | ||
self.capacity = int(par) | ||
v = get_par("VoltHigh= ", s) | ||
if v is False: | ||
return False | ||
self.max_battery_voltage = float(v) * float(4) | ||
v = get_par("VoltLow= ", s) | ||
if v is False: | ||
return False | ||
self.min_battery_voltage = float(v) * float(4) | ||
|
||
return True | ||
|
||
def read_status_data(self): | ||
status_data = self.read_serial_data_HLPdataBMS4S(b"m1\n", 0.2, 40) | ||
if status_data is False: | ||
return False | ||
par1 = str(status_data) | ||
par = par1.split(",") | ||
if len(par) < 8: | ||
return False | ||
if len(par[0]) < 7: | ||
return False | ||
p0 = str(par[0]) | ||
ix = p0.find(".") | ||
par0 = p0[ix - 1 : len(p0)] | ||
|
||
# v1,v2,v3,v4,current,soc,chargeoff,loadoff,vbat2,socnow,adj,beep,led,temp1,temp2... | ||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14... | ||
|
||
self.voltage = float(par0) + float(par[1]) + float(par[2]) + float(par[3]) | ||
self.cells[0].voltage = float(par0) | ||
self.cells[1].voltage = float(par[1]) | ||
self.cells[2].voltage = float(par[2]) | ||
self.cells[3].voltage = float(par[3]) | ||
self.current = float(par[4]) | ||
self.soc = int(par[5]) | ||
self.control_allow_charge = par[6] | ||
self.charge_fet = par[6] | ||
self.control_allow_discharge = par[7] | ||
self.discharge_fet = par[7] | ||
|
||
beep = int(par[11]) | ||
if beep == 2: | ||
self.protection.temp_low_charge = 1 | ||
else: | ||
self.protection.temp_low_charge = 0 | ||
if beep == 3: | ||
self.protection.temp_high_charge = 1 | ||
else: | ||
self.protection.temp_high_charge = 0 | ||
if beep == 4: | ||
self.protection.voltage_low = 2 | ||
else: | ||
self.protection.voltage_low = 0 | ||
if beep == 5: | ||
self.protection.voltage_high = 2 | ||
else: | ||
self.protection.voltage_high = 0 | ||
|
||
if len(par) > 13: | ||
nb = 0 | ||
min = int(1000) | ||
max = int(-1000) | ||
ix = 13 | ||
while ix < len(par): | ||
tmp = par[ix].split(" ") | ||
ix += 1 | ||
if len(tmp) == 2: | ||
name = tmp[0] | ||
temp = int("".join(filter(str.isdigit, tmp[1]))) | ||
if name[0] == "b": | ||
nb += 1 | ||
if temp > max: | ||
max = temp | ||
if temp < min: | ||
min = temp | ||
if nb == 1: | ||
self.temp1 = max | ||
if nb > 1: | ||
self.temp1 = max | ||
self.temp2 = min | ||
|
||
return True | ||
|
||
def manage_charge_voltage(self): | ||
self.allow_max_voltage = True | ||
self.control_voltage = self.max_battery_voltage | ||
|
||
def manage_charge_current(self): | ||
self.control_charge_current = 1000 | ||
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 | ||
return data | ||
|
||
|
||
def read_serial_data2(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 not ret is False: | ||
return ret | ||
return False | ||
|
||
except serial.SerialException as e: | ||
logger.error(e) | ||
return False | ||
|
||
|
||
def read_serialport_data2(ser, command, time, min_len): | ||
try: | ||
cnt = 0 | ||
while cnt < 3: | ||
cnt += 1 | ||
ser.flushOutput() | ||
ser.flushInput() | ||
ser.write(command) | ||
sleep(time) | ||
res = ser.read(1000) | ||
if len(res) >= min_len: | ||
return res | ||
return False | ||
|
||
except serial.SerialException as e: | ||
logger.error(e) | ||
return False | ||
|
||
|
||
def get_par(p, s): | ||
ix = s.find(p) | ||
if ix > 0: | ||
ix += len(p) | ||
for i in range(ix, len(s)): | ||
if s[i] == " " or s[i] == 10 or s[i] == 13: | ||
ret = s[ix:i] | ||
return ret | ||
return False |
Oops, something went wrong.