Errorcodes, SoC, 9 slaves

This commit is contained in:
jazzpi 2022-08-08 01:23:20 +02:00
parent cf4b1763fe
commit 61f652ee8f
1 changed files with 136 additions and 31 deletions

View File

@ -8,6 +8,8 @@ import random
import serial import serial
import sys import sys
import numpy as np
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QApplication, QApplication,
@ -25,14 +27,32 @@ from PyQt5.QtWidgets import (
BITRATE = 115200 # baud/s BITRATE = 115200 # baud/s
TIMEOUT = 1 # seconds TIMEOUT = 1 # seconds
N_SLAVES = 7 N_SLAVES = 9
LOG_FRAME_LENGTH = 8 # bytes LOG_FRAME_LENGTH = 8 # bytes
PARALLEL_CELLS = 9
CELLS_PER_SLAVE = 10 CELLS_PER_SLAVE = 10
TEMP_SENSORS_PER_SLAVE = 32 TEMP_SENSORS_PER_SLAVE = 32
VOLTAGE_CONV = 5.0 / 255 # volts/quantum VOLTAGE_CONV = 5.0 / 255 # volts/quantum
TEMP_CONV = 0.0625 * 16 # °C/quantum TEMP_CONV = 0.0625 * 16 # °C/quantum
ERRORCODE_TIMEOUT_SLAVE = 1
ERRORCODE_SLAVE_PANIC = 2
ERRORCODE_TIMEOUT_SLAVE_FRAMES = 3
ERRORCODE_TOO_FEW_TEMPS = 4
ERRORCODE_TIMEOUT_SHUNT = 6
ERRORCODE_MASTER_THRESH = 7
SLAVE_ERROR_UV = 0
SLAVE_ERROR_OV = 1
SLAVE_ERROR_UT = 2
SLAVE_ERROR_OT = 3
SLAVE_ERROR_BQ = 4
SLAVE_ERROR_TMP144 = 5
MASTER_THRESH_UT = 0
MASTER_THRESH_OT = 1
MASTER_THRESH_UV = 2
MASTER_THRESH_OV = 3
class SlaveData: class SlaveData:
cell_voltages: list[float] cell_voltages: list[float]
@ -47,6 +67,8 @@ class AccumulatorData:
slaves: list[SlaveData] slaves: list[SlaveData]
min_voltage: float min_voltage: float
max_voltage: float max_voltage: float
min_soc: float
max_soc: float
min_temp: float min_temp: float
max_temp: float max_temp: float
last_frame: float last_frame: float
@ -60,6 +82,10 @@ class AccumulatorData:
self.slaves = [SlaveData() for _ in range(N_SLAVES)] self.slaves = [SlaveData() for _ in range(N_SLAVES)]
self.min_voltage = ( self.min_voltage = (
self.max_voltage self.max_voltage
) = (
self.min_soc
) = (
self.max_soc
) = self.min_temp = self.max_temp = self.last_frame = self.current = 0 ) = self.min_temp = self.max_temp = self.last_frame = self.current = 0
self.panic = False self.panic = False
self.panic_errorcode = self.panic_errorarg = 0 self.panic_errorcode = self.panic_errorarg = 0
@ -257,25 +283,37 @@ class Window(QWidget):
self.l_max_voltage.setNum(data.max_voltage) self.l_max_voltage.setNum(data.max_voltage)
self.l_max_voltage.setAlignment(Qt.AlignLeft) self.l_max_voltage.setAlignment(Qt.AlignLeft)
self.l3 = QLabel("Min Temperature [°C]") self.l3 = QLabel("Min SoC [%]")
self.l3.setAlignment(Qt.AlignLeft) self.l3.setAlignment(Qt.AlignLeft)
self.l_min_soc = QLabel()
self.l_min_soc.setNum(data.min_soc)
self.l_min_soc.setAlignment(Qt.AlignLeft)
self.l4 = QLabel("Max SoC [%]")
self.l4.setAlignment(Qt.AlignLeft)
self.l_max_soc = QLabel()
self.l_max_soc.setNum(data.max_soc)
self.l_max_soc.setAlignment(Qt.AlignLeft)
self.l5 = QLabel("Min Temperature [°C]")
self.l5.setAlignment(Qt.AlignLeft)
self.l_min_temp = QLabel() self.l_min_temp = QLabel()
self.l_min_temp.setNum(data.min_temp) self.l_min_temp.setNum(data.min_temp)
self.l_min_temp.setAlignment(Qt.AlignLeft) self.l_min_temp.setAlignment(Qt.AlignLeft)
self.l4 = QLabel("Max Temperature [°C]") self.l6 = QLabel("Max Temperature [°C]")
self.l4.setAlignment(Qt.AlignLeft) self.l6.setAlignment(Qt.AlignLeft)
self.l_max_temp = QLabel() self.l_max_temp = QLabel()
self.l_max_temp.setNum(data.max_temp) self.l_max_temp.setNum(data.max_temp)
self.l_max_temp.setAlignment(Qt.AlignLeft) self.l_max_temp.setAlignment(Qt.AlignLeft)
self.l5 = QLabel("Current [A]") self.l7 = QLabel("Current [A]")
self.l5.setAlignment(Qt.AlignLeft) self.l7.setAlignment(Qt.AlignLeft)
self.l_current = QLabel() self.l_current = QLabel()
self.l_current.setNum(data.current) self.l_current.setNum(data.current)
self.l_current.setAlignment(Qt.AlignLeft) self.l_current.setAlignment(Qt.AlignLeft)
self.l6 = QLabel("Error") self.l8 = QLabel("Error")
self.l_error = QLabel() self.l_error = QLabel()
self.l_error.setText(str(data.panic)) self.l_error.setText(str(data.panic))
self.l_error.setAlignment(Qt.AlignLeft) self.l_error.setAlignment(Qt.AlignLeft)
@ -286,7 +324,7 @@ class Window(QWidget):
self.l_errorarg.setText(str(data.panic_errorarg)) self.l_errorarg.setText(str(data.panic_errorarg))
self.l_errorcode.setAlignment(Qt.AlignLeft) self.l_errorcode.setAlignment(Qt.AlignLeft)
self.l7 = QLabel("Time Since Last Dataframe") self.l9 = QLabel("Time Since Last Dataframe")
self.l_time_since_last_frame = QLabel() self.l_time_since_last_frame = QLabel()
self.l_time_since_last_frame.setText(str(data.time_since_last_frame)) self.l_time_since_last_frame.setText(str(data.time_since_last_frame))
self.l_time_since_last_frame.setAlignment(Qt.AlignLeft) self.l_time_since_last_frame.setAlignment(Qt.AlignLeft)
@ -295,21 +333,25 @@ class Window(QWidget):
grid_accumulator = QGridLayout() grid_accumulator = QGridLayout()
grid_accumulator.addWidget(self.l1, 0, 0) grid_accumulator.addWidget(self.l1, 0, 0)
grid_accumulator.addWidget(self.l2, 1, 0) grid_accumulator.addWidget(self.l2, 1, 0)
grid_accumulator.addWidget(self.l3, 0, 2) grid_accumulator.addWidget(self.l3, 2, 0)
grid_accumulator.addWidget(self.l4, 1, 2) grid_accumulator.addWidget(self.l4, 3, 0)
grid_accumulator.addWidget(self.l5, 2, 0) grid_accumulator.addWidget(self.l5, 0, 2)
grid_accumulator.addWidget(self.l6, 3, 0) grid_accumulator.addWidget(self.l6, 1, 2)
grid_accumulator.addWidget(self.l7, 4, 0) grid_accumulator.addWidget(self.l7, 2, 2)
grid_accumulator.addWidget(self.l8, 5, 0)
grid_accumulator.addWidget(self.l9, 3, 2)
grid_accumulator.addWidget(self.l_min_voltage, 0, 1) grid_accumulator.addWidget(self.l_min_voltage, 0, 1)
grid_accumulator.addWidget(self.l_max_voltage, 1, 1) grid_accumulator.addWidget(self.l_max_voltage, 1, 1)
grid_accumulator.addWidget(self.l_min_soc, 2, 1)
grid_accumulator.addWidget(self.l_max_soc, 3, 1)
grid_accumulator.addWidget(self.l_min_temp, 0, 3) grid_accumulator.addWidget(self.l_min_temp, 0, 3)
grid_accumulator.addWidget(self.l_max_temp, 1, 3) grid_accumulator.addWidget(self.l_max_temp, 1, 3)
grid_accumulator.addWidget(self.l_current, 2, 1) grid_accumulator.addWidget(self.l_current, 2, 3)
grid_accumulator.addWidget(self.l_error, 3, 1) grid_accumulator.addWidget(self.l_error, 5, 1)
grid_accumulator.addWidget(self.l_errorcode, 3, 2) grid_accumulator.addWidget(self.l_errorcode, 5, 2)
grid_accumulator.addWidget(self.l_errorarg, 3, 3) grid_accumulator.addWidget(self.l_errorarg, 5, 3)
grid_accumulator.addWidget(self.l_time_since_last_frame, 4, 1) grid_accumulator.addWidget(self.l_time_since_last_frame, 3, 3)
groupBox_accumulator = QGroupBox("Accumulator General") groupBox_accumulator = QGroupBox("Accumulator General")
groupBox_accumulator.setLayout(grid_accumulator) groupBox_accumulator.setLayout(grid_accumulator)
@ -400,15 +442,17 @@ class Window(QWidget):
def update(self): def update(self):
# Accumulator # Accumulator
self.l_min_voltage.setNum(data.min_voltage) self.l_min_voltage.setText(f"{data.min_voltage:.02f}")
self.l_max_voltage.setNum(data.max_voltage) self.l_max_voltage.setText(f"{data.max_voltage:.02f}")
self.l_min_temp.setNum(data.min_temp) self.l_min_soc.setText(f"{data.min_soc:.01f}")
self.l_max_temp.setNum(data.max_temp) self.l_max_soc.setText(f"{data.max_soc:.01f}")
self.l_current.setNum(data.current) self.l_min_temp.setText(f"{data.min_temp:.00f}")
self.l_max_temp.setText(f"{data.max_temp:.00f}")
self.l_current.setText(f"{data.current:.03f}")
self.l_error.setText(str(data.panic)) self.l_error.setText(str(data.panic))
self.l_errorcode.setText(str(data.panic_errorcode)) self.l_errorcode.setText(str(data.panic_errorcode))
self.l_errorarg.setText(str(data.panic_errorarg)) self.l_errorarg.setText(str(data.panic_errorarg))
self.l_time_since_last_frame.setText(str(data.time_since_last_frame)) self.l_time_since_last_frame.setText(str(time.time() - data.last_frame))
# Stacks # Stacks
for i, sge in enumerate(self.stack_gui_elements): for i, sge in enumerate(self.stack_gui_elements):
@ -434,6 +478,7 @@ class Worker(QObject):
def charger_communication(self): def charger_communication(self):
rx_data = ser.read(256) rx_data = ser.read(256)
# print(len(rx_data), rx_data)
while len(rx_data) > 0: while len(rx_data) > 0:
if (frame_start := self.check_log_start(rx_data)) != -1: if (frame_start := self.check_log_start(rx_data)) != -1:
self.decode_log_frame(rx_data[frame_start + 3 : frame_start + 11]) self.decode_log_frame(rx_data[frame_start + 3 : frame_start + 11])
@ -503,6 +548,8 @@ class Worker(QObject):
] ]
data.min_voltage = min(voltages) data.min_voltage = min(voltages)
data.max_voltage = max(voltages) data.max_voltage = max(voltages)
data.min_soc = self.calculate_soc(data.min_voltage)
data.max_soc = self.calculate_soc(data.max_voltage)
data.min_temp = min(temps) data.min_temp = min(temps)
data.max_temp = max(temps) data.max_temp = max(temps)
data.time_since_last_frame = time.time() - data.last_frame data.time_since_last_frame = time.time() - data.last_frame
@ -516,11 +563,72 @@ class Worker(QObject):
def decode_panic_frame(self, buf: bytes): def decode_panic_frame(self, buf: bytes):
data.panic = True data.panic = True
data.panic_errorcode = buf[0] errorcode = int(buf[0])
data.panic_errorarg = buf[1] errorargs = [int(buf[1]), int(buf[2])]
if errorcode == ERRORCODE_TIMEOUT_SLAVE:
data.panic_errorcode = "Slave timeout"
data.panic_errorarg = f"Slave ID {errorargs[0]}"
elif errorcode == ERRORCODE_SLAVE_PANIC:
data.panic_errorcode = "Slave panic"
if errorargs[1] == SLAVE_ERROR_UV:
slave_error = "Undervoltage"
elif errorargs[1] == SLAVE_ERROR_OV:
slave_error = "Overvoltage"
elif errorargs[1] == SLAVE_ERROR_UT:
slave_error = "Undertemperature"
elif errorargs[1] == SLAVE_ERROR_OT:
slave_error = "Overtemperature"
elif errorargs[1] == SLAVE_ERROR_BQ:
slave_error = "BQ Communication error"
elif errorargs[1] == SLAVE_ERROR_TMP144:
slave_error = "TMP144 communication error"
else:
slave_error = f"Unknown error ({errorargs[1]})"
data.panic_errorarg = f"Slave ID {errorargs[0]}: {slave_error}"
elif errorcode == ERRORCODE_TIMEOUT_SLAVE_FRAMES:
data.panic_errorcode = "Slave frame timeout"
data.panic_errorarg = f"Slave ID {errorargs[0]}, frame {errorargs[1]}"
elif errorcode == ERRORCODE_TOO_FEW_TEMPS:
data.panic_errorcode = "Too few temperature sensors"
data.panic_errorarg = f"Slave ID {errorargs[0]}"
elif errorcode == ERRORCODE_TIMEOUT_SHUNT:
data.panic_errorcode = "Shunt timeout"
elif errorcode == ERRORCODE_MASTER_THRESH:
data.panic_errorcode = "Master detected threshold"
if errorargs[0] == MASTER_THRESH_UT:
data.panic_errorarg = "Undertemperature"
elif errorargs[0] == MASTER_THRESH_OT:
data.panic_errorarg = "Overtemperature"
elif errorargs[0] == MASTER_THRESH_UV:
data.panic_errorarg = "Undervoltage"
elif errorargs[0] == MASTER_THRESH_OV:
data.panic_errorarg = "Overvoltage"
else:
data.panic_errorarg = f"Unknown threshold ({errorargs[0]})"
else:
data.panic_errorcode = buf[0]
data.panic_errorarg = buf[1]
INTERNAL_RESISTANCE_CURVE_X = [2.0, 4.12]
INTERNAL_RESISTANCE_CURVE_Y = [0.0528, 0.0294]
SOC_OCV_X = [2.1, 2.9, 3.2, 3.3, 3.4, 3.5, 3.68, 4.0, 4.15, 4.2]
SOC_OCV_Y = [0, 0.023, 0.06, 0.08, 0.119, 0.227, 0.541, 0.856, 0.985, 1.0]
def calculate_soc(self, voltage: float):
r_i = np.interp(
[voltage],
self.INTERNAL_RESISTANCE_CURVE_X,
self.INTERNAL_RESISTANCE_CURVE_Y,
)[0]
# i = data.current / PARALLEL_CELLS
i = 3 / PARALLEL_CELLS
ocv = voltage - i * r_i
return np.interp([ocv], self.SOC_OCV_X, self.SOC_OCV_Y)[0] * 100
def parse_cell_temps(self, slave: int): def parse_cell_temps(self, slave: int):
temps = list(filter(lambda t: t > 0, data.slaves[slave].cell_temps[:16])) temps = list(
filter(lambda t: t > 0 and t < 60, data.slaves[slave].cell_temps[:16])
)
if len(temps) == 0: if len(temps) == 0:
temps = [-1] temps = [-1]
min_t = min(temps) min_t = min(temps)
@ -542,10 +650,7 @@ if __name__ == "__main__":
SERIAL_PORT = sys.argv[1] SERIAL_PORT = sys.argv[1]
print(SERIAL_PORT) print(SERIAL_PORT)
try: ser = serial.Serial(SERIAL_PORT, BITRATE, timeout=TIMEOUT)
ser = serial.Serial(SERIAL_PORT, BITRATE, timeout=TIMEOUT)
except serial.serialutil.SerialException:
pass
app = QApplication(sys.argv) app = QApplication(sys.argv)
gui = Window() gui = Window()