#!/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_())