302 lines
9.2 KiB
Python
302 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import struct
|
|
import time
|
|
|
|
import random
|
|
|
|
# import serial
|
|
import sys
|
|
|
|
from PyQt5.QtCore import Qt, QTimer
|
|
from PyQt5.QtWidgets import (
|
|
QApplication,
|
|
QWidget,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QGridLayout,
|
|
QSlider,
|
|
QLabel,
|
|
QRadioButton,
|
|
QGroupBox,
|
|
)
|
|
|
|
|
|
BITRATE = 115200 # baud/s
|
|
TIMEOUT = 1 # seconds
|
|
N_SLAVES = 6
|
|
LOG_FRAME_LENGTH = 8 # bytes
|
|
|
|
CELLS_PER_SLAVE = 10
|
|
TEMP_SENSORS_PER_SLAVE = 32
|
|
VOLTAGE_CONV = 5.0 / 255 # volts/quantum
|
|
TEMP_CONV = 0.0625 * 16 # °C/quantum
|
|
|
|
|
|
class SlaveData:
|
|
cell_voltages: list[float]
|
|
cell_temps: list[float]
|
|
|
|
def __init__(self) -> None:
|
|
self.cell_voltages = [-1] * CELLS_PER_SLAVE
|
|
self.cell_temps = [-1] * TEMP_SENSORS_PER_SLAVE
|
|
|
|
|
|
class AccumulatorData:
|
|
slaves: list[SlaveData]
|
|
min_voltage: float
|
|
max_voltage: float
|
|
min_temp: float
|
|
max_temp: float
|
|
last_frame: float
|
|
current: float
|
|
panic: bool
|
|
panic_errorcode: int
|
|
panic_errorarg: int
|
|
|
|
def __init__(self) -> None:
|
|
self.slaves = [SlaveData() for _ in range(N_SLAVES)]
|
|
self.min_voltage = (
|
|
self.max_voltage
|
|
) = self.min_temp = self.max_temp = self.last_frame = self.current = 0
|
|
self.panic = False
|
|
self.panic_errorcode = self.panic_errorarg = 0
|
|
|
|
|
|
class StackGuiElement:
|
|
title: str
|
|
min_voltage_label: QLabel
|
|
max_voltage_label: QLabel
|
|
min_temp_label: QLabel
|
|
max_temp_label: QLabel
|
|
groupBox: QGroupBox
|
|
|
|
def __init__(self, title: str):
|
|
self.title = title
|
|
self.min_voltage_label = QLabel()
|
|
self.max_voltage_label = QLabel()
|
|
self.min_temp_label = QLabel()
|
|
self.max_temp_label = QLabel()
|
|
self.groupBox = QGroupBox()
|
|
self.__post__init__()
|
|
|
|
def __post__init__(self):
|
|
l1 = QLabel("Min Voltage [V]")
|
|
l2 = QLabel("Max Voltage [V]")
|
|
l3 = QLabel("Min Temperature [°C]")
|
|
l4 = QLabel("Max Temperature [°C]")
|
|
|
|
l1.setAlignment(Qt.AlignLeft)
|
|
l2.setAlignment(Qt.AlignLeft)
|
|
l3.setAlignment(Qt.AlignLeft)
|
|
l4.setAlignment(Qt.AlignLeft)
|
|
|
|
self.min_voltage_label.setAlignment(Qt.AlignLeft)
|
|
self.max_voltage_label.setAlignment(Qt.AlignLeft)
|
|
self.min_temp_label.setAlignment(Qt.AlignLeft)
|
|
self.max_temp_label.setAlignment(Qt.AlignLeft)
|
|
|
|
grid = QGridLayout()
|
|
grid.addWidget(l1, 0, 0)
|
|
grid.addWidget(l2, 1, 0)
|
|
grid.addWidget(l3, 0, 2)
|
|
grid.addWidget(l4, 1, 2)
|
|
|
|
grid.addWidget(self.min_voltage_label, 0, 1)
|
|
grid.addWidget(self.max_voltage_label, 1, 1)
|
|
grid.addWidget(self.min_temp_label, 0, 3)
|
|
grid.addWidget(self.max_temp_label, 1, 3)
|
|
|
|
self.groupBox.setTitle(self.title)
|
|
self.groupBox.setLayout(grid)
|
|
|
|
def update_data_from_slave(self, slave: SlaveData):
|
|
self.min_voltage_label.setNum(min(slave.cell_voltages))
|
|
self.max_voltage_label.setNum(max(slave.cell_voltages))
|
|
self.min_temp_label.setNum(min(slave.cell_temps))
|
|
self.max_temp_label.setNum(max(slave.cell_temps))
|
|
|
|
|
|
def fill_dummy_data():
|
|
data.min_voltage = random.uniform(1, 3)
|
|
data.max_voltage = random.uniform(3, 5)
|
|
data.min_temp = random.uniform(0, 25)
|
|
data.max_temp = random.uniform(25, 60)
|
|
data.current = random.uniform(25, 60)
|
|
data.last_frame = time.time() - random.random()
|
|
data.panic = random.choice([True, False])
|
|
if data.panic:
|
|
data.panic_errorcode = random.randint(1, 10000)
|
|
data.panic_errorarg = "ABCDERFG"
|
|
else:
|
|
data.panic_errorcode = 0
|
|
data.panic_errorarg = ""
|
|
|
|
|
|
def update_display():
|
|
voltages = [
|
|
slave.cell_voltages[i] for i in range(CELLS_PER_SLAVE) for slave in data.slaves
|
|
]
|
|
temps = [
|
|
slave.cell_temps[i]
|
|
for i in range(TEMP_SENSORS_PER_SLAVE)
|
|
for slave in data.slaves
|
|
]
|
|
data.min_voltage = min(voltages)
|
|
data.max_voltage = max(voltages)
|
|
data.min_temp = min(temps)
|
|
data.max_temp = max(temps)
|
|
time_since_last_frame = time.time() - data.last_frame
|
|
|
|
print("\033[2J\033[H", end="")
|
|
print("-" * 20)
|
|
if data.panic:
|
|
print("!!!!! PANIC !!!!!")
|
|
print(f"Error code: {data.panic_errorcode}")
|
|
print(f"Error arg: {data.panic_errorarg}")
|
|
time.sleep(0.5)
|
|
return
|
|
|
|
print(f"Time since last frame: {time_since_last_frame} s")
|
|
print(f"Current: {data.current:.2f} A")
|
|
print(f"Min voltage: {data.min_voltage:.2f} V")
|
|
print(f"Max voltage: {data.max_voltage:.2f} V")
|
|
print(f"Min temp: {data.min_temp:.1f} °C")
|
|
print(f"Max temp: {data.max_temp:.1f} °C")
|
|
for i in range(N_SLAVES):
|
|
min_v = min(data.slaves[i].cell_voltages)
|
|
max_v = max(data.slaves[i].cell_voltages)
|
|
min_t = min(data.slaves[i].cell_temps)
|
|
max_t = max(data.slaves[i].cell_temps)
|
|
print(
|
|
f"Stack {i}: V ∈ [{min_v:.2f}, {max_v:.2f}]\tT ∈ [{min_t:.1f}, {max_t:.1f}]"
|
|
)
|
|
|
|
|
|
class Window(QWidget):
|
|
def __init__(self, parent=None):
|
|
super(Window, self).__init__(parent)
|
|
|
|
win = QVBoxLayout()
|
|
|
|
### ACCUMULATOR GENERAL ###
|
|
self.l1 = QLabel("Min Voltage [V]")
|
|
self.l1.setAlignment(Qt.AlignLeft)
|
|
self.l_min_voltage = QLabel()
|
|
self.l_min_voltage.setNum(data.min_voltage)
|
|
self.l_min_voltage.setAlignment(Qt.AlignLeft)
|
|
|
|
self.l2 = QLabel("Max Voltage [V]")
|
|
self.l2.setAlignment(Qt.AlignLeft)
|
|
self.l_max_voltage = QLabel()
|
|
self.l_max_voltage.setNum(data.max_voltage)
|
|
self.l_max_voltage.setAlignment(Qt.AlignLeft)
|
|
|
|
self.l3 = QLabel("Min Temperature [°C]")
|
|
self.l3.setAlignment(Qt.AlignLeft)
|
|
self.l_min_temp = QLabel()
|
|
self.l_min_temp.setNum(data.min_temp)
|
|
self.l_min_temp.setAlignment(Qt.AlignLeft)
|
|
|
|
self.l4 = QLabel("Max Temperature [°C]")
|
|
self.l4.setAlignment(Qt.AlignLeft)
|
|
self.l_max_temp = QLabel()
|
|
self.l_max_temp.setNum(data.max_temp)
|
|
self.l_max_temp.setAlignment(Qt.AlignLeft)
|
|
|
|
self.l5 = QLabel("Current [A]")
|
|
self.l5.setAlignment(Qt.AlignLeft)
|
|
self.l_current = QLabel()
|
|
self.l_current.setNum(data.current)
|
|
self.l_current.setAlignment(Qt.AlignLeft)
|
|
|
|
self.l6 = QLabel("Error")
|
|
self.l_error = QLabel()
|
|
self.l_error.setText(str(data.panic))
|
|
self.l_error.setAlignment(Qt.AlignLeft)
|
|
self.l_errorcode = QLabel()
|
|
self.l_errorcode.setText(str(data.panic_errorcode))
|
|
self.l_errorcode.setAlignment(Qt.AlignLeft)
|
|
self.l_errorarg = QLabel()
|
|
self.l_errorarg.setText(str(data.panic_errorarg))
|
|
self.l_errorcode.setAlignment(Qt.AlignLeft)
|
|
|
|
grid_accumulator = QGridLayout()
|
|
grid_accumulator.addWidget(self.l1, 0, 0)
|
|
grid_accumulator.addWidget(self.l2, 1, 0)
|
|
grid_accumulator.addWidget(self.l3, 0, 2)
|
|
grid_accumulator.addWidget(self.l4, 1, 2)
|
|
grid_accumulator.addWidget(self.l5, 2, 0)
|
|
grid_accumulator.addWidget(self.l6, 3, 0)
|
|
|
|
grid_accumulator.addWidget(self.l_min_voltage, 0, 1)
|
|
grid_accumulator.addWidget(self.l_max_voltage, 1, 1)
|
|
grid_accumulator.addWidget(self.l_min_temp, 0, 3)
|
|
grid_accumulator.addWidget(self.l_max_temp, 1, 3)
|
|
grid_accumulator.addWidget(self.l_current, 2, 1)
|
|
grid_accumulator.addWidget(self.l_error, 3, 1)
|
|
grid_accumulator.addWidget(self.l_errorcode, 3, 2)
|
|
grid_accumulator.addWidget(self.l_errorarg, 3, 3)
|
|
|
|
groupBox_accumulator = QGroupBox("Accumulator General")
|
|
groupBox_accumulator.setLayout(grid_accumulator)
|
|
|
|
win.addWidget(groupBox_accumulator)
|
|
|
|
### STACKS ###
|
|
|
|
self.stack_gui_elements = []
|
|
for i in range(N_SLAVES):
|
|
sge = StackGuiElement(f"Stack {i}")
|
|
sge.update_data_from_slave(data.slaves[i])
|
|
self.stack_gui_elements.append(sge)
|
|
|
|
### Layout Stacks ###
|
|
n_slaves_half = N_SLAVES // 2
|
|
grid_stacks = QGridLayout()
|
|
for i, sge in enumerate(self.stack_gui_elements):
|
|
grid_stacks.addWidget(
|
|
sge.groupBox, 0 if i < n_slaves_half else 1, i % n_slaves_half
|
|
)
|
|
|
|
groupBox_stacks = QGroupBox("Individual Stacks")
|
|
groupBox_stacks.setLayout(grid_stacks)
|
|
|
|
win.addWidget(groupBox_stacks)
|
|
|
|
self.setLayout(win)
|
|
self.setWindowTitle("FT22 Charger Display")
|
|
|
|
def update(self):
|
|
# Accumulator
|
|
self.l_min_voltage.setNum(data.min_voltage)
|
|
self.l_max_voltage.setNum(data.max_voltage)
|
|
self.l_min_temp.setNum(data.min_temp)
|
|
self.l_max_temp.setNum(data.max_temp)
|
|
self.l_current.setNum(data.current)
|
|
self.l_error.setText(str(data.panic))
|
|
self.l_errorcode.setText(str(data.panic_errorcode))
|
|
self.l_errorarg.setText(str(data.panic_errorarg))
|
|
|
|
# Cells
|
|
for i, sge in enumerate(self.stack_gui_elements):
|
|
sge.update_data_from_slave(data.slaves[i])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
data = AccumulatorData()
|
|
app = QApplication(sys.argv)
|
|
gui = Window()
|
|
gui.show()
|
|
|
|
timer = QTimer()
|
|
timer.timeout.connect(gui.update)
|
|
timer.timeout.connect(fill_dummy_data)
|
|
timer.start(1000) # every 1,000 milliseconds
|
|
|
|
# update_display()
|
|
|
|
sys.exit(app.exec_())
|