Louisvdw / dbus-serialbattery

Battery Monitor driver for serial battery in VenusOS GX systems
MIT License
543 stars 166 forks source link

random non existing BMS keeps popping up. #815

Closed robbesutr closed 1 year ago

robbesutr commented 1 year ago

Describe the problem

Hi All,

I've been using my dbus-serialbattery for a while now with my Daly BMS. works great till about two weeks ago. Almost every night a random ANT BMS shows up and sets off the low voltage alarm in the Cerbo.. When I restart my Cerbo the ANT disappears again for about a day... Any idea whats going on? just to be clear. I have no ANT BMS hooked up to my cerbo or even in my system. All I have (connected) is a daly bms, multi, mppt and a gps mouse. Daly works fine.

many thanks for taking a look at this.

regards, Rob

Schermafbeelding 2023-09-20 om 16 30 17

Driver version

0.14.3

Venus OS device type

Cerbo GX

Venus OS version

3.10

BMS type

Daly Smart BMS

Cell count

4

Connection type

Serial USB adapter to RS485

Config file

# -*- coding: utf-8 -*-
import logging
import serial
from time import sleep
from struct import *
import bisect

# Logging
logging.basicConfig()
logger = logging.getLogger("SerialBattery")
logger.setLevel(logging.INFO)

# battery types
# if not specified: baud = 9600
battery_types = [
    {'bms' : "LltJbd"},
    {'bms' : "Ant", "baud" : 19200},
    {"bms" : "Daly", "address" : b"\x40"},
    {"bms" : "Daly", "address" : b"\x80"},
    {"bms" : "Jkbms", "baud" : 115200},
#    {"bms" : "Sinowealth"},
    {"bms" : "Lifepower"},
    {"bms" : "Renogy", "address": b"\x30"},
    {"bms" : "Renogy", "address": b"\xF7"},
    {"bms" : "Ecs", "baud" : 19200},
#    {"bms" : "MNB"},
]

# Constants - Need to dynamically get them in future
DRIVER_VERSION = 0.14
DRIVER_SUBVERSION = '.3' 
zero_char = chr(48)
degree_sign = u'\N{DEGREE SIGN}'

# Choose the mode for voltage / current limitations (True / False)
# False is a Step mode. This is the default with limitations on hard boundary steps
# True "Linear"    # New linear limitations by WaldemarFech for smoother values
LINEAR_LIMITATION_ENABLE = False

######### 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                = [   0,    2,  30,  100]

CELL_VOLTAGES_WHILE_DISCHARGING      = [2.70, 2.80, 2.90, 3.10]
MAX_DISCHARGE_CURRENT_CV             = [   0,    5,  30,  60]

######### 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    = [55, 40,  35,   5,  2, 0]
MAX_CHARGE_CURRENT_T                 = [ 0, 28, 60, 60, 28, 0]

TEMPERATURE_LIMITS_WHILE_DISCHARGING = [55, 40,  35,   5,  0, -20]
MAX_DISCHARGE_CURRENT_T              = [ 0, 28, 60, 60, 28,   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]
PENALTY_BATTERY_VOLTAGE  = [0.01, 1.0, 2.0]  # this voltage will be subtracted

######### 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.75
MAX_CELL_VOLTAGE = 3.45
FLOAT_CELL_VOLTAGE = 3.40
MAX_VOLTAGE_TIME_SEC = 15*60
SOC_LEVEL_TO_RESET_VOLTAGE_LIMIT = 90

# battery Current limits
MAX_BATTERY_CHARGE_CURRENT = 100.0
MAX_BATTERY_DISCHARGE_CURRENT = 250.0

# 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 = 100
CC_CURRENT_LIMIT2 = MAX_BATTERY_CHARGE_CURRENT/4
CC_CURRENT_LIMIT3 = MAX_BATTERY_CHARGE_CURRENT/2

#discharge current soc limits
DC_SOC_LIMIT1 = 10
DC_SOC_LIMIT2 = 20
DC_SOC_LIMIT3 = 30

#discharge current limits
DC_CURRENT_LIMIT1 = 150
DC_CURRENT_LIMIT2 = MAX_BATTERY_DISCHARGE_CURRENT/4
DC_CURRENT_LIMIT3 = MAX_BATTERY_DISCHARGE_CURRENT/2

# 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 = 304
# 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]
TIME_TO_SOC_POINTS = [] # No data set to disable
# 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
TIME_TO_SOC_VALUE_TYPE = 3        # Both Seconds and time str "<seconds> [days, HR:MN:SC]"
# 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

def constrain(val, min_val, max_val):
    if min_val > max_val:
        min_val, max_val = max_val, min_val
    return min(max_val, max(min_val, val))

def mapRange(inValue, inMin, inMax, outMin, outMax):
    return outMin + (((inValue - inMin) / (inMax - inMin)) * (outMax - outMin))

def mapRangeConstrain(inValue, inMin, inMax, outMin, outMax):
    return constrain(mapRange(inValue, inMin, inMax, outMin, outMax), outMin, outMax)

def calcLinearRelationship(inValue, inArray, outArray):
    if inArray[0] > inArray[-1]:    # change compare-direction in array
        return calcLinearRelationship(inValue, inArray[::-1], outArray[::-1])
    else:

        # Handle out of bounds
        if inValue <= inArray[0]:
            return outArray[0]
        if inValue >= inArray[-1]:
            return outArray[-1]

        # else calculate linear current between the setpoints
        idx = bisect.bisect(inArray, inValue)
        upperIN  = inArray[idx - 1]  # begin with idx 0 as max value
        upperOUT = outArray[idx - 1]
        lowerIN  = inArray[idx]
        lowerOUT = outArray[idx]            
        return  mapRangeConstrain(inValue, lowerIN, upperIN, lowerOUT, upperOUT)

def calcStepRelationship(inValue, inArray, outArray, returnLower):
    if inArray[0] > inArray[-1]:    # change compare-direction in array
        return calcStepRelationship(inValue, inArray[::-1], outArray[::-1], returnLower)

    # Handle out of bounds
    if inValue <= inArray[0]:
        return outArray[0]
    if inValue >= inArray[-1]:
        return outArray[-1]

    # else get index between the setpoints
    idx = bisect.bisect(inArray, inValue)

    return outArray[idx] if returnLower else outArray[idx-1]

def is_bit_set(tmp):
    return False if tmp == zero_char else True

def kelvin_to_celsius(kelvin_temp):
    return kelvin_temp - 273.1

def format_value(value, prefix, suffix):
    return None if value is None else ('' if prefix is None else prefix) + \
                                      str(value) + \
                                      ('' if suffix is None else suffix)

def read_serial_data(command, port, baud, length_pos, length_check, length_fixed=None, length_size=None):
    try:
        with serial.Serial(port, baudrate=baud, timeout=0.1) as ser:
            return read_serialport_data(ser, command, length_pos, length_check, length_fixed, length_size)

    except serial.SerialException as e:
        logger.error(e)
        return False

# Open the serial port
# Return variable for the openned port 
def open_serial_port(port, baud):
    ser = None
    tries = 3
    while tries > 0:
        try:
            ser = serial.Serial(port, baudrate=baud, timeout=0.1)
            tries = 0
        except serial.SerialException as e:
            logger.error(e)
            tries -= 1

    return ser

# Read data from previously openned serial port
def read_serialport_data(ser, command, length_pos, length_check, length_fixed=None, length_size=None):
    try:
        ser.flushOutput()
        ser.flushInput()
        ser.write(command)

        length_byte_size = 1
        if length_size is not None: 
            if length_size.upper() == 'H':
                length_byte_size = 2
            elif length_size.upper() == 'I' or length_size.upper() == 'L':
                length_byte_size = 4

        count = 0
        toread = ser.inWaiting()

        while toread < (length_pos+length_byte_size):
            sleep(0.005)
            toread = ser.inWaiting()
            count += 1
            if count > 50:
                logger.error(">>> ERROR: No reply - returning")
                return False

        #logger.info('serial data toread ' + str(toread))
        res = ser.read(toread)
        if length_fixed is not None:
            length = length_fixed
        else:
            if len(res) < (length_pos+length_byte_size):
                logger.error(">>> ERROR: No reply - returning [len:" + str(len(res)) + "]")
                return False
            length_size = length_size if length_size is not None else 'B'
            length = unpack_from('>'+length_size, res,length_pos)[0]

        #logger.info('serial data length ' + str(length))

        count = 0
        data = bytearray(res)
        while len(data) <= length + length_check:
            res = ser.read(length + length_check)
            data.extend(res)
            #logger.info('serial data length ' + str(len(data)))
            sleep(0.005)
            count += 1
            if count > 150:
                logger.error(">>> ERROR: No reply - returning [len:" + str(len(data)) + "/" + str(length + length_check) + "]")
                return False

        return data

    except serial.SerialException as e:
        logger.error(e)
        return False

Relevant log output

bit lost here but according to my cerbo the ANT should be on ttyUSB2.. 

root@cerbosgx:~# tail -F -n 100 /data/log/dbus-serialbattery.ttyUSB2/current | tai64nlocal
2023-09-21 19:31:42.098501500 ERROR:SerialBattery:>>> ERROR: No reply - returning
2023-09-21 19:31:42.105399500 ERROR:SerialBattery:>>> ERROR: Incorrect Data

Any other information that may be helpful

No response

mr-manuel commented 1 year ago

This issue is fixed in the latest beta. Anyway I recommend you to install the latest nightly from the dev branch.