Skip to content

Commit

Permalink
Merge branch 'jkbms_ble' into jkbms_ble
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-manuel committed May 3, 2023
2 parents ad2855d + a1972c8 commit 2c9d3dd
Show file tree
Hide file tree
Showing 5 changed files with 1,876 additions and 0 deletions.
138 changes: 138 additions & 0 deletions etc/dbus-serialbattery/default_config.ini
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 =
241 changes: 241 additions & 0 deletions etc/dbus-serialbattery/hlpdatabms4s.py
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
Loading

0 comments on commit 2c9d3dd

Please sign in to comment.