Compare commits
3 Commits
61f652ee8f
...
857ca68d83
Author | SHA1 | Date |
---|---|---|
jazzpi | 857ca68d83 | |
jazzpi | 18d9517fcc | |
jazzpi | a3f3b2c23e |
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
@ -33,8 +34,8 @@ LOG_FRAME_LENGTH = 8 # bytes
|
||||||
PARALLEL_CELLS = 9
|
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 / 0xFFFF # volts/quantum
|
||||||
TEMP_CONV = 0.0625 * 16 # °C/quantum
|
TEMP_CONV = 0.0625 # °C/quantum
|
||||||
|
|
||||||
ERRORCODE_TIMEOUT_SLAVE = 1
|
ERRORCODE_TIMEOUT_SLAVE = 1
|
||||||
ERRORCODE_SLAVE_PANIC = 2
|
ERRORCODE_SLAVE_PANIC = 2
|
||||||
|
@ -60,7 +61,20 @@ class SlaveData:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.cell_voltages = [-1] * CELLS_PER_SLAVE
|
self.cell_voltages = [-1] * CELLS_PER_SLAVE
|
||||||
self.cell_temps = [-1] * TEMP_SENSORS_PER_SLAVE
|
self.cell_temps = ([-1] * (TEMP_SENSORS_PER_SLAVE // 2)) + (
|
||||||
|
[0] * (TEMP_SENSORS_PER_SLAVE // 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TSState(Enum):
|
||||||
|
TS_INACTIVE = 0
|
||||||
|
TS_ACTIVE = 1
|
||||||
|
TS_PRECHARGE = 2
|
||||||
|
TS_DISCHARGE = 3
|
||||||
|
TS_ERROR = 4
|
||||||
|
TS_CHARGING_CHECK = 5
|
||||||
|
TS_CHARGING = 6
|
||||||
|
TS_BALANCING = 7
|
||||||
|
|
||||||
|
|
||||||
class AccumulatorData:
|
class AccumulatorData:
|
||||||
|
@ -77,6 +91,7 @@ class AccumulatorData:
|
||||||
panic: bool
|
panic: bool
|
||||||
panic_errorcode: int
|
panic_errorcode: int
|
||||||
panic_errorarg: int
|
panic_errorarg: int
|
||||||
|
ts_state: TSState
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.slaves = [SlaveData() for _ in range(N_SLAVES)]
|
self.slaves = [SlaveData() for _ in range(N_SLAVES)]
|
||||||
|
@ -90,6 +105,7 @@ class AccumulatorData:
|
||||||
self.panic = False
|
self.panic = False
|
||||||
self.panic_errorcode = self.panic_errorarg = 0
|
self.panic_errorcode = self.panic_errorarg = 0
|
||||||
self.time_since_last_frame = 0
|
self.time_since_last_frame = 0
|
||||||
|
self.ts_state = TSState.TS_INACTIVE
|
||||||
|
|
||||||
def fill_dummy_data(self):
|
def fill_dummy_data(self):
|
||||||
self.min_voltage = random.uniform(1, 3)
|
self.min_voltage = random.uniform(1, 3)
|
||||||
|
@ -142,7 +158,7 @@ class StackGuiElement:
|
||||||
l2 = QLabel("Max Voltage [V]")
|
l2 = QLabel("Max Voltage [V]")
|
||||||
l3 = QLabel("Min Temperature [°C]")
|
l3 = QLabel("Min Temperature [°C]")
|
||||||
l4 = QLabel("Max Temperature [°C]")
|
l4 = QLabel("Max Temperature [°C]")
|
||||||
popup_btn = QPushButton(self.title + " details")
|
popup_btn = QPushButton("Details")
|
||||||
popup_btn.clicked.connect(self.show_popup)
|
popup_btn.clicked.connect(self.show_popup)
|
||||||
|
|
||||||
l1.setAlignment(Qt.AlignLeft)
|
l1.setAlignment(Qt.AlignLeft)
|
||||||
|
@ -172,10 +188,10 @@ class StackGuiElement:
|
||||||
self.groupBox.setLayout(grid)
|
self.groupBox.setLayout(grid)
|
||||||
|
|
||||||
def update_data_from_slave(self, slave: SlaveData):
|
def update_data_from_slave(self, slave: SlaveData):
|
||||||
self.min_voltage_label.setNum(min(slave.cell_voltages))
|
self.min_voltage_label.setText(f"{min(slave.cell_voltages):.02f}")
|
||||||
self.max_voltage_label.setNum(max(slave.cell_voltages))
|
self.max_voltage_label.setText(f"{max(slave.cell_voltages):.02f}")
|
||||||
self.min_temp_label.setNum(min(slave.cell_temps))
|
self.min_temp_label.setText(f"{min(slave.cell_temps):.02f}")
|
||||||
self.max_temp_label.setNum(max(slave.cell_temps))
|
self.max_temp_label.setText(f"{max(slave.cell_temps):.02f}")
|
||||||
|
|
||||||
def show_popup(self):
|
def show_popup(self):
|
||||||
if self.detail_popup is None:
|
if self.detail_popup is None:
|
||||||
|
@ -313,15 +329,15 @@ class Window(QWidget):
|
||||||
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.l8 = QLabel("Error")
|
self.l8 = QLabel("State")
|
||||||
self.l_error = QLabel()
|
self.l_state = QLabel()
|
||||||
self.l_error.setText(str(data.panic))
|
self.l_state.setText(data.ts_state.name)
|
||||||
self.l_error.setAlignment(Qt.AlignLeft)
|
self.l_state.setAlignment(Qt.AlignLeft)
|
||||||
self.l_errorcode = QLabel()
|
self.l_errorcode = QLabel()
|
||||||
self.l_errorcode.setText(str(data.panic_errorcode))
|
self.l_errorcode.setText("")
|
||||||
self.l_errorcode.setAlignment(Qt.AlignLeft)
|
self.l_errorcode.setAlignment(Qt.AlignLeft)
|
||||||
self.l_errorarg = QLabel()
|
self.l_errorarg = QLabel()
|
||||||
self.l_errorarg.setText(str(data.panic_errorarg))
|
self.l_errorarg.setText("")
|
||||||
self.l_errorcode.setAlignment(Qt.AlignLeft)
|
self.l_errorcode.setAlignment(Qt.AlignLeft)
|
||||||
|
|
||||||
self.l9 = QLabel("Time Since Last Dataframe")
|
self.l9 = QLabel("Time Since Last Dataframe")
|
||||||
|
@ -348,7 +364,7 @@ class Window(QWidget):
|
||||||
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, 3)
|
grid_accumulator.addWidget(self.l_current, 2, 3)
|
||||||
grid_accumulator.addWidget(self.l_error, 5, 1)
|
grid_accumulator.addWidget(self.l_state, 5, 1)
|
||||||
grid_accumulator.addWidget(self.l_errorcode, 5, 2)
|
grid_accumulator.addWidget(self.l_errorcode, 5, 2)
|
||||||
grid_accumulator.addWidget(self.l_errorarg, 5, 3)
|
grid_accumulator.addWidget(self.l_errorarg, 5, 3)
|
||||||
grid_accumulator.addWidget(self.l_time_since_last_frame, 3, 3)
|
grid_accumulator.addWidget(self.l_time_since_last_frame, 3, 3)
|
||||||
|
@ -386,14 +402,18 @@ class Window(QWidget):
|
||||||
self.btn_stop.resize(self.btn_stop.sizeHint())
|
self.btn_stop.resize(self.btn_stop.sizeHint())
|
||||||
self.btn_stop.move(150, 50)
|
self.btn_stop.move(150, 50)
|
||||||
self.btn_charge = QPushButton("Start Charging")
|
self.btn_charge = QPushButton("Start Charging")
|
||||||
self.btn_charge.resize(self.btn_stop.sizeHint())
|
self.btn_charge.resize(self.btn_charge.sizeHint())
|
||||||
self.btn_charge.move(150, 50)
|
self.btn_charge.move(250, 50)
|
||||||
|
self.btn_balance = QPushButton("Start Balancing")
|
||||||
|
self.btn_balance.resize(self.btn_balance.sizeHint())
|
||||||
|
self.btn_balance.move(350, 50)
|
||||||
|
|
||||||
### LAYOUT BUTTONS ###
|
### LAYOUT BUTTONS ###
|
||||||
vbox_controls = QVBoxLayout()
|
vbox_controls = QVBoxLayout()
|
||||||
vbox_controls.addWidget(self.btn_start)
|
vbox_controls.addWidget(self.btn_start)
|
||||||
vbox_controls.addWidget(self.btn_stop)
|
vbox_controls.addWidget(self.btn_stop)
|
||||||
vbox_controls.addWidget(self.btn_charge)
|
vbox_controls.addWidget(self.btn_charge)
|
||||||
|
vbox_controls.addWidget(self.btn_balance)
|
||||||
groupBox_controls = QGroupBox("Controls")
|
groupBox_controls = QGroupBox("Controls")
|
||||||
groupBox_controls.setLayout(vbox_controls)
|
groupBox_controls.setLayout(vbox_controls)
|
||||||
win.addWidget(groupBox_controls)
|
win.addWidget(groupBox_controls)
|
||||||
|
@ -407,6 +427,9 @@ class Window(QWidget):
|
||||||
# Charge Button action
|
# Charge Button action
|
||||||
self.btn_charge.clicked.connect(self.start_charge)
|
self.btn_charge.clicked.connect(self.start_charge)
|
||||||
|
|
||||||
|
# Balance Button action
|
||||||
|
self.btn_balance.clicked.connect(self.start_balance)
|
||||||
|
|
||||||
self.setLayout(win)
|
self.setLayout(win)
|
||||||
self.setWindowTitle("FT22 Charger Display")
|
self.setWindowTitle("FT22 Charger Display")
|
||||||
|
|
||||||
|
@ -440,19 +463,34 @@ class Window(QWidget):
|
||||||
def start_charge(self):
|
def start_charge(self):
|
||||||
ser.write(b"C")
|
ser.write(b"C")
|
||||||
|
|
||||||
|
def start_balance(self):
|
||||||
|
ser.write(b"B")
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
# Accumulator
|
# Accumulator
|
||||||
self.l_min_voltage.setText(f"{data.min_voltage:.02f}")
|
self.l_min_voltage.setText(f"{data.min_voltage:.02f}")
|
||||||
self.l_max_voltage.setText(f"{data.max_voltage:.02f}")
|
self.l_max_voltage.setText(f"{data.max_voltage:.02f}")
|
||||||
self.l_min_soc.setText(f"{data.min_soc:.01f}")
|
self.l_min_soc.setText(f"{data.min_soc:.01f}")
|
||||||
self.l_max_soc.setText(f"{data.max_soc:.01f}")
|
self.l_max_soc.setText(f"{data.max_soc:.01f}")
|
||||||
self.l_min_temp.setText(f"{data.min_temp:.00f}")
|
self.l_min_temp.setText(f"{data.min_temp:.02f}")
|
||||||
self.l_max_temp.setText(f"{data.max_temp:.00f}")
|
self.l_max_temp.setText(f"{data.max_temp:.02f}")
|
||||||
self.l_current.setText(f"{data.current:.03f}")
|
self.l_current.setText(f"{data.current:.02f}")
|
||||||
self.l_error.setText(str(data.panic))
|
self.l_state.setText(data.ts_state.name)
|
||||||
|
if 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(time.time() - data.last_frame))
|
for l in (self.l_state, self.l_errorcode, self.l_errorarg):
|
||||||
|
l.setStyleSheet("color: red; font-weight: bold;")
|
||||||
|
elif data.ts_state == TSState.TS_CHARGING:
|
||||||
|
for l in (self.l_state, self.l_errorcode, self.l_errorarg):
|
||||||
|
l.setStyleSheet("color: green; font-weight: bold;")
|
||||||
|
else:
|
||||||
|
self.l_errorcode.setText("")
|
||||||
|
self.l_errorarg.setText("")
|
||||||
|
for l in (self.l_state, self.l_errorcode, self.l_errorarg):
|
||||||
|
l.setStyleSheet("color: black; font-weight: normal;")
|
||||||
|
last_time = time.time() - data.last_frame
|
||||||
|
self.l_time_since_last_frame.setText(f"{last_time:.03f}")
|
||||||
|
|
||||||
# Stacks
|
# Stacks
|
||||||
for i, sge in enumerate(self.stack_gui_elements):
|
for i, sge in enumerate(self.stack_gui_elements):
|
||||||
|
@ -492,6 +530,10 @@ class Worker(QObject):
|
||||||
self.decode_panic_frame(rx_data[frame_start + 3 : frame_start + 11])
|
self.decode_panic_frame(rx_data[frame_start + 3 : frame_start + 11])
|
||||||
rx_data = rx_data[frame_start + 11 :]
|
rx_data = rx_data[frame_start + 11 :]
|
||||||
continue
|
continue
|
||||||
|
elif (frame_start := self.check_status_start(rx_data)) != -1:
|
||||||
|
self.decode_status_frame(rx_data[frame_start + 3 : frame_start + 11])
|
||||||
|
rx_data = rx_data[frame_start + 11 :]
|
||||||
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
def check_log_start(self, buf: bytes):
|
def check_log_start(self, buf: bytes):
|
||||||
|
@ -503,6 +545,9 @@ class Worker(QObject):
|
||||||
def check_panic_start(self, buf: bytes):
|
def check_panic_start(self, buf: bytes):
|
||||||
return buf[:-12].find(b"PAN")
|
return buf[:-12].find(b"PAN")
|
||||||
|
|
||||||
|
def check_status_start(self, buf: bytes):
|
||||||
|
return buf[:-12].find(b"STA")
|
||||||
|
|
||||||
def decode_log_frame(self, buf: bytes):
|
def decode_log_frame(self, buf: bytes):
|
||||||
msg_id = buf[0]
|
msg_id = buf[0]
|
||||||
slave = msg_id >> 4
|
slave = msg_id >> 4
|
||||||
|
@ -512,25 +557,44 @@ class Worker(QObject):
|
||||||
return
|
return
|
||||||
|
|
||||||
if frame_id == 0:
|
if frame_id == 0:
|
||||||
for i in range(7):
|
for i in range(3):
|
||||||
data.slaves[slave].cell_voltages[i] = buf[i + 1] * VOLTAGE_CONV
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_voltages[i] = raw * VOLTAGE_CONV
|
||||||
elif frame_id == 1:
|
elif frame_id == 1:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
data.slaves[slave].cell_voltages[i + 7] = buf[i + 1] * VOLTAGE_CONV
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
for i in range(4):
|
data.slaves[slave].cell_voltages[i + 3] = raw * VOLTAGE_CONV
|
||||||
data.slaves[slave].cell_temps[i] = buf[i + 4] * TEMP_CONV
|
|
||||||
elif frame_id == 2:
|
elif frame_id == 2:
|
||||||
for i in range(7):
|
for i in range(3):
|
||||||
data.slaves[slave].cell_temps[i + 4] = buf[i + 1] * TEMP_CONV
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_voltages[i + 6] = raw * VOLTAGE_CONV
|
||||||
elif frame_id == 3:
|
elif frame_id == 3:
|
||||||
for i in range(7):
|
for i in range(1):
|
||||||
data.slaves[slave].cell_temps[i + 11] = buf[i + 1] * TEMP_CONV
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_voltages[i + 9] = raw * VOLTAGE_CONV
|
||||||
|
for i in range(2):
|
||||||
|
raw = (buf[i * 2 + 3] << 8) | buf[i * 2 + 4]
|
||||||
|
data.slaves[slave].cell_temps[i] = raw * TEMP_CONV
|
||||||
elif frame_id == 4:
|
elif frame_id == 4:
|
||||||
for i in range(7):
|
for i in range(3):
|
||||||
data.slaves[slave].cell_temps[i + 18] = buf[i + 1] * TEMP_CONV
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_temps[i + 2] = raw * TEMP_CONV
|
||||||
elif frame_id == 5:
|
elif frame_id == 5:
|
||||||
for i in range(7):
|
for i in range(3):
|
||||||
data.slaves[slave].cell_temps[i + 25] = buf[i + 1] * TEMP_CONV
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_temps[i + 5] = raw * TEMP_CONV
|
||||||
|
elif frame_id == 6:
|
||||||
|
for i in range(3):
|
||||||
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_temps[i + 8] = raw * TEMP_CONV
|
||||||
|
elif frame_id == 7:
|
||||||
|
for i in range(3):
|
||||||
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_temps[i + 11] = raw * TEMP_CONV
|
||||||
|
elif frame_id == 8:
|
||||||
|
for i in range(3):
|
||||||
|
raw = (buf[i * 2 + 1] << 8) | buf[i * 2 + 2]
|
||||||
|
data.slaves[slave].cell_temps[i + 14] = raw * TEMP_CONV
|
||||||
else:
|
else:
|
||||||
print(f"Unknown frame ID: {frame_id} (buf: {repr(buf)})", file=sys.stderr)
|
print(f"Unknown frame ID: {frame_id} (buf: {repr(buf)})", file=sys.stderr)
|
||||||
# time.sleep(1)
|
# time.sleep(1)
|
||||||
|
@ -609,6 +673,11 @@ class Worker(QObject):
|
||||||
data.panic_errorcode = buf[0]
|
data.panic_errorcode = buf[0]
|
||||||
data.panic_errorarg = buf[1]
|
data.panic_errorarg = buf[1]
|
||||||
|
|
||||||
|
def decode_status_frame(self, buf: bytes):
|
||||||
|
state = buf[0] & 0x7F
|
||||||
|
data.ts_state = TSState(state)
|
||||||
|
data.panic = data.ts_state == TSState.TS_ERROR
|
||||||
|
|
||||||
INTERNAL_RESISTANCE_CURVE_X = [2.0, 4.12]
|
INTERNAL_RESISTANCE_CURVE_X = [2.0, 4.12]
|
||||||
INTERNAL_RESISTANCE_CURVE_Y = [0.0528, 0.0294]
|
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_X = [2.1, 2.9, 3.2, 3.3, 3.4, 3.5, 3.68, 4.0, 4.15, 4.2]
|
||||||
|
@ -633,8 +702,8 @@ class Worker(QObject):
|
||||||
temps = [-1]
|
temps = [-1]
|
||||||
min_t = min(temps)
|
min_t = min(temps)
|
||||||
max_t = max(temps)
|
max_t = max(temps)
|
||||||
for i in range(16, 32):
|
# for i in range(16, 32):
|
||||||
data.slaves[slave].cell_temps[i] = random.randint(min_t, max_t)
|
# data.slaves[slave].cell_temps[i] = random.randint(min_t, max_t)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.continue_run = False # set the run condition to false on stop
|
self.continue_run = False # set the run condition to false on stop
|
||||||
|
|
Loading…
Reference in New Issue