You've already forked CMB18_CellMonitoringApplication
Added automatic COM port discovery and selection during startup to remove hardcoded COM9 dependency. Updated GUI status and title bar to display the active port.
427 lines
19 KiB
Python
427 lines
19 KiB
Python
import serial
|
|
import serial.tools.list_ports # 🌟 Added for automatic port scanning
|
|
import time
|
|
import threading
|
|
import queue
|
|
import sys
|
|
import math
|
|
from PyQt5 import QtWidgets, QtCore, QtGui
|
|
import pyqtgraph as pg
|
|
|
|
# --- IMPORT OFFICIAL INFINEON MODULES ---
|
|
try:
|
|
from tle9018dqk.control import TLE9018DQK
|
|
from tle9018dqk.registers import REG
|
|
except ImportError:
|
|
print("\n[!] ERROR: 'tle9018dqk' folder was not found in the same directory!")
|
|
sys.exit(1)
|
|
|
|
# --- GLOBAL INITIALIZATION CACHE ---
|
|
RUNTIME_PROFILE = {
|
|
"ch_1_3_byte": 0xCC,
|
|
"ch_0_1_byte": 0xC9,
|
|
"ch_4_byte": 0x0C,
|
|
"ntc_1_4_r25": 10000.0,
|
|
"ntc_1_4_beta": 3435.0
|
|
}
|
|
|
|
# --- CLI HARDWARE INTERACTIVE SETUP (BEFORE STARTUP) ---
|
|
print("=" * 60)
|
|
print(" TLE9018 MULTI-NODE BMS HARDWARE FOOTPRINT SETUP")
|
|
print("=" * 60)
|
|
|
|
# 🌟 STEP 1: COM PORT SELECTION
|
|
ports = [p.device for p in serial.tools.list_ports.comports()]
|
|
|
|
if not ports:
|
|
print("[!] WARNING: No active COM Ports detected on the system!")
|
|
selected_port = input("[?] Enter the target port manually (e.g. COM9): ").strip().upper()
|
|
if not selected_port:
|
|
selected_port = "COM9"
|
|
else:
|
|
print("\nActive COM Ports detected on the system:")
|
|
for idx, p in enumerate(ports, start=1):
|
|
print(f" [{idx}] {p}")
|
|
|
|
port_choice = input(f"[?] Select a port option (1-{len(ports)}) or enter manually: ").strip()
|
|
|
|
if port_choice.isdigit() and 1 <= int(port_choice) <= len(ports):
|
|
selected_port = ports[int(port_choice) - 1]
|
|
else:
|
|
selected_port = port_choice.upper() if port_choice else ports[0]
|
|
|
|
print(f"-> Selected Port: {selected_port}")
|
|
|
|
# 🌟 STEP 2: NODE COUNT SELECTION
|
|
try:
|
|
TOTAL_NODES_INPUT = int(input("\n[?] Enter total number of TLE9018 nodes in the daisy chain (1-8): "))
|
|
if not (1 <= TOTAL_NODES_INPUT <= 8):
|
|
raise ValueError
|
|
except ValueError:
|
|
print("[!] Invalid input. Defaulting to 1 node.")
|
|
TOTAL_NODES_INPUT = 1
|
|
|
|
# 🌟 STEP 3: NTC PROFILE SELECTION
|
|
print("\nSelect the sensor type physically populated on channels 1 to 4:")
|
|
print(" [1] 10 kOhm NTCs (Standard Baseline Array)")
|
|
print(" [2] 100 kOhm NTCs (High Impedance Footprint)")
|
|
ntc_choice = input("[?] Choice (1 or 2): ").strip()
|
|
|
|
if ntc_choice == "2":
|
|
print("-> Configuring Channels 1-4 for 100k NTC profiles (10uA Bias Mode).")
|
|
RUNTIME_PROFILE["ntc_1_4_r25"] = 100000.0
|
|
RUNTIME_PROFILE["ntc_1_4_beta"] = 4250.0
|
|
RUNTIME_PROFILE["ch_1_3_byte"] = 0x99
|
|
RUNTIME_PROFILE["ch_0_1_byte"] = 0x99
|
|
RUNTIME_PROFILE["ch_4_byte"] = 0x09
|
|
else:
|
|
print("-> Configuring Channels 1-4 for 10k NTC profiles (40uA Bias Mode).")
|
|
RUNTIME_PROFILE["ntc_1_4_r25"] = 10000.0
|
|
RUNTIME_PROFILE["ntc_1_4_beta"] = 3435.0
|
|
RUNTIME_PROFILE["ch_1_3_byte"] = 0xCC
|
|
RUNTIME_PROFILE["ch_0_1_byte"] = 0xC9
|
|
RUNTIME_PROFILE["ch_4_byte"] = 0x0C
|
|
|
|
print("=" * 60)
|
|
print("DECLARATION: Hardware initialization parameters verified.")
|
|
print(f" -> Active Communication Port : {selected_port}")
|
|
print(f" -> Deploying total chain nodes: {TOTAL_NODES_INPUT}")
|
|
print(f" -> Channel 0 Target Profile : 100k Ohm NTC")
|
|
print(f" -> Channels 1-4 Target Profile: {int(RUNTIME_PROFILE['ntc_1_4_r25']/1000)}k Ohm NTC")
|
|
print("PROCEEDING TO MONITORING INTERFACE...")
|
|
print("=" * 60)
|
|
|
|
# --- GLOBAL HARDWARE CONFIGURATION CONSTANTS ---
|
|
CONFIG = {
|
|
"PORT": selected_port, # Dynamically assigned based on CLI selection
|
|
"BAUDRATE": 2000000,
|
|
"SAMPLE_INTERVAL_S": 0.1,
|
|
"MAX_PLOT_POINTS": 50,
|
|
}
|
|
|
|
R_TMP_OHM = 100.0
|
|
FSR_TMP_V = 2.0
|
|
NTC_T25_K = 298.15
|
|
|
|
NTC_R25_MAP = {0: 100000.0, 1: RUNTIME_PROFILE["ntc_1_4_r25"], 2: RUNTIME_PROFILE["ntc_1_4_r25"], 3: RUNTIME_PROFILE["ntc_1_4_r25"], 4: RUNTIME_PROFILE["ntc_1_4_r25"]}
|
|
NTC_BETA_MAP = {0: 4250.0, 1: RUNTIME_PROFILE["ntc_1_4_beta"], 2: RUNTIME_PROFILE["ntc_1_4_beta"], 3: RUNTIME_PROFILE["ntc_1_4_beta"], 4: RUNTIME_PROFILE["ntc_1_4_beta"]}
|
|
|
|
_PLOT_QUEUE = queue.Queue(maxsize=100)
|
|
_COMMAND_QUEUE = queue.Queue()
|
|
_STOP_REQUESTED = False
|
|
_ACTIVE_NODES = TOTAL_NODES_INPUT
|
|
|
|
class MultiCMBDashboard(QtWidgets.QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle(f"TLE9018 Master BMS - [{CONFIG['PORT']}] Dual Scope Control")
|
|
self.current_view_node = 1
|
|
self.data_history = {}
|
|
self.node_states = {i: {"e6": 0, "e7": 0} for i in range(1, _ACTIVE_NODES + 1)}
|
|
|
|
self.setup_ui()
|
|
|
|
self.timer = QtCore.QTimer()
|
|
self.timer.timeout.connect(self.update_ui)
|
|
self.timer.start(40)
|
|
|
|
def setup_ui(self):
|
|
self.setStyleSheet("""
|
|
QMainWindow { background-color: #0A0A0B; }
|
|
QLabel { color: #FFFFFF; font-family: 'Segoe UI'; }
|
|
QComboBox, QSpinBox { background-color: #2A2A2A; color: #FFFFFF; border: 1px solid #444; padding: 8px; border-radius: 6px; font-size: 18px; }
|
|
QComboBox QAbstractItemView { background-color: #2A2A2A; color: #FFFFFF; selection-background-color: #4A4A4A; selection-color: #FFFFFF; }
|
|
QTabBar::tab { background: #222; color: #FFFFFF; padding: 15px; min-width: 180px; font-size: 14px; }
|
|
QTabBar::tab:selected { background: #333; border-bottom: 4px solid #90CAF9; }
|
|
QScrollArea { border: none; background: transparent; }
|
|
""")
|
|
central = QtWidgets.QWidget(); self.setCentralWidget(central)
|
|
main_layout = QtWidgets.QVBoxLayout(central)
|
|
|
|
ctrl_panel = QtWidgets.QFrame()
|
|
ctrl_panel.setMinimumHeight(100)
|
|
ctrl_panel.setStyleSheet("background-color: #1A1A1A; border-radius: 12px; border: 1px solid #333;")
|
|
ctrl_layout = QtWidgets.QHBoxLayout(ctrl_panel)
|
|
ctrl_layout.setContentsMargins(25, 15, 25, 15)
|
|
ctrl_layout.setSpacing(20)
|
|
|
|
lbl_nodes = QtWidgets.QLabel("Nodes In Chain:"); lbl_nodes.setStyleSheet("font-weight: bold; font-size: 18px; color: #90CAF9;")
|
|
|
|
self.spin_nodes = QtWidgets.QSpinBox(); self.spin_nodes.setRange(1, 8); self.spin_nodes.setValue(_ACTIVE_NODES); self.spin_nodes.setMinimumHeight(45)
|
|
|
|
self.btn_init = QtWidgets.QPushButton("RE-ENUMERATE CHAIN")
|
|
self.btn_init.setStyleSheet("background-color: #FFAB91; color: #1A1A1A; font-weight: bold; font-size: 16px; height: 50px; padding: 0 20px; border-radius: 8px;")
|
|
self.btn_init.clicked.connect(self.run_enumeration)
|
|
|
|
self.status_lbl = QtWidgets.QLabel(f"PORT: {CONFIG['PORT']} | LIVE STREAMING"); self.status_lbl.setStyleSheet("font-weight: bold; font-size: 16px; color: #A5D6A7;")
|
|
|
|
lbl_view = QtWidgets.QLabel("Viewing Board:"); lbl_view.setStyleSheet("font-weight: bold; font-size: 18px; color: #FFCC80;")
|
|
self.node_selector = QtWidgets.QComboBox(); self.node_selector.setMinimumHeight(45); self.node_selector.setMinimumWidth(200)
|
|
for i in range(_ACTIVE_NODES):
|
|
self.node_selector.addItem(f"Board ID {i+1}")
|
|
self.node_selector.currentIndexChanged.connect(self.change_node_view)
|
|
|
|
ctrl_layout.addWidget(lbl_nodes); ctrl_layout.addWidget(self.spin_nodes)
|
|
ctrl_layout.addWidget(self.btn_init); ctrl_layout.addWidget(self.status_lbl); ctrl_layout.addStretch()
|
|
ctrl_layout.addWidget(lbl_view); ctrl_layout.addWidget(self.node_selector)
|
|
main_layout.addWidget(ctrl_panel)
|
|
|
|
self.tabs = QtWidgets.QTabWidget(); main_layout.addWidget(self.tabs)
|
|
|
|
self.v_cards = []
|
|
v_tab = QtWidgets.QWidget(); v_lay = QtWidgets.QVBoxLayout(v_tab); v_scroll = QtWidgets.QScrollArea(); v_scroll.setWidgetResizable(True)
|
|
v_cont = QtWidgets.QWidget(); self.v_grid = QtWidgets.QVBoxLayout(v_cont)
|
|
self.v_grid.setSpacing(10)
|
|
|
|
for i in range(18):
|
|
card = self.create_card(f"CELL {i+1:02d}", "#90CAF9", is_voltage=True)
|
|
self.v_cards.append(card); self.v_grid.addWidget(card['frame'])
|
|
v_scroll.setWidget(v_cont); v_lay.addWidget(v_scroll); self.tabs.addTab(v_tab, "VOLTAGES")
|
|
|
|
self.t_cards = []
|
|
t_tab = QtWidgets.QWidget(); t_lay = QtWidgets.QVBoxLayout(t_tab); t_scroll = QtWidgets.QScrollArea(); t_scroll.setWidgetResizable(True)
|
|
t_cont = QtWidgets.QWidget(); self.t_grid = QtWidgets.QVBoxLayout(t_cont)
|
|
self.t_grid.setSpacing(10)
|
|
|
|
for i in range(5):
|
|
title = f"NTC TEMPERATURE {i+1:02d} (100k)" if i == 0 else f"NTC TEMPERATURE {i+1:02d} ({int(RUNTIME_PROFILE['ntc_1_4_r25']/1000)}k)"
|
|
card = self.create_card(title, "#80CBC4", is_voltage=False)
|
|
self.t_cards.append(card); self.t_grid.addWidget(card['frame'])
|
|
t_scroll.setWidget(t_cont); t_lay.addWidget(t_scroll); self.tabs.addTab(t_tab, "TEMPERATURES (5 CH ACTIVE)")
|
|
|
|
def create_card(self, title, color, is_voltage):
|
|
frame = QtWidgets.QFrame(); frame.setMinimumHeight(160); frame.setStyleSheet("background-color: #161616; border: 1px solid #333; border-radius: 12px;")
|
|
layout = QtWidgets.QHBoxLayout(frame); info = QtWidgets.QVBoxLayout()
|
|
info.setContentsMargins(15, 15, 15, 15)
|
|
|
|
lbl = QtWidgets.QLabel(title); lbl.setStyleSheet(f"color: {color}; font-size: 20px; font-weight: bold; border: none;")
|
|
val = QtWidgets.QLabel("--- V" if is_voltage else "--- °C")
|
|
val.setStyleSheet("color: #FFFFFF; font-size: 32px; font-family: 'Consolas'; font-weight: bold; border: none;")
|
|
|
|
info.addWidget(lbl); info.addWidget(val); info.addStretch()
|
|
|
|
if is_voltage:
|
|
btn = QtWidgets.QPushButton("BALANCE")
|
|
btn.setCheckable(True)
|
|
btn.clicked.connect(self.update_hardware_configs)
|
|
btn.setStyleSheet("QPushButton { background-color: #2A2A2A; color: #A0AAB5; height: 38px; border-radius: 6px; font-size: 13px; font-weight: bold; min-width: 100px; } QPushButton:checked { background-color: #FF3D00; color: #fff; font-weight: bold; }")
|
|
info.addWidget(btn)
|
|
card_data = {'frame': frame, 'val_lbl': val, 'btn': btn}
|
|
else:
|
|
card_data = {'frame': frame, 'val_lbl': val}
|
|
|
|
plot = pg.PlotWidget()
|
|
plot.setBackground('#0E0F12')
|
|
plot.setMouseEnabled(x=False, y=False)
|
|
plot.hideAxis('bottom')
|
|
plot.hideButtons()
|
|
plot.getAxis('left').setWidth(40)
|
|
plot.showGrid(y=True, alpha=0.1)
|
|
|
|
curve = plot.plot(pen=pg.mkPen(color, width=2.5))
|
|
plot.getViewBox().enableAutoRange(axis='y', enable=True)
|
|
|
|
layout.addLayout(info, 1); layout.addWidget(plot, 5)
|
|
card_data['curve'] = curve
|
|
card_data['plot'] = plot
|
|
return card_data
|
|
|
|
def run_enumeration(self):
|
|
count = self.spin_nodes.value()
|
|
_COMMAND_QUEUE.put({"type": "ENUM", "count": count})
|
|
self.node_selector.clear()
|
|
for i in range(count):
|
|
self.node_selector.addItem(f"Board ID {i+1}")
|
|
if (i+1) not in self.node_states: self.node_states[i+1] = {"e6": 0, "e7": 0}
|
|
self.data_history.clear()
|
|
|
|
def change_node_view(self, index):
|
|
if index < 0: return
|
|
self.current_view_node = index + 1
|
|
state = self.node_states.get(self.current_view_node, {"e6": 0, "e7": 0})
|
|
for i, card in enumerate(self.v_cards):
|
|
card['btn'].blockSignals(True)
|
|
mask = state['e6'] if i < 12 else state['e7']
|
|
card['btn'].setChecked(bool(mask & (1 << (i % 12))))
|
|
card['btn'].blockSignals(False); card['plot'].enableAutoRange(axis='y', enable=True)
|
|
|
|
def update_hardware_configs(self):
|
|
node = self.current_view_node
|
|
if node not in self.node_states: self.node_states[node] = {"e6": 0, "e7": 0}
|
|
e6, e7 = 0, 0
|
|
for i, card in enumerate(self.v_cards):
|
|
if card['btn'].isChecked():
|
|
if i < 12: e6 |= (1 << i)
|
|
else: e7 |= (1 << (i - 12))
|
|
self.node_states[node]['e6'], self.node_states[node]['e7'] = e6, e7
|
|
_COMMAND_QUEUE.put({"type": "BAL", "node": node, "e6": e6, "e7": e7})
|
|
|
|
def update_ui(self):
|
|
latest_data = {}
|
|
while not _PLOT_QUEUE.empty():
|
|
try:
|
|
data = _PLOT_QUEUE.get_nowait()
|
|
latest_data[data['node']] = data
|
|
except queue.Empty:
|
|
break
|
|
|
|
for nid, data in latest_data.items():
|
|
if nid not in self.data_history:
|
|
self.data_history[nid] = {
|
|
'v': [[0.0] * CONFIG["MAX_PLOT_POINTS"] for _ in range(18)],
|
|
't': [[25.0] * CONFIG["MAX_PLOT_POINTS"] for _ in range(5)]
|
|
}
|
|
|
|
for i in range(18):
|
|
self.data_history[nid]['v'][i].append(data['v'][i])
|
|
if len(self.data_history[nid]['v'][i]) > CONFIG["MAX_PLOT_POINTS"]:
|
|
self.data_history[nid]['v'][i].pop(0)
|
|
|
|
for i in range(5):
|
|
self.data_history[nid]['t'][i].append(data['t'][i])
|
|
if len(self.data_history[nid]['t'][i]) > CONFIG["MAX_PLOT_POINTS"]:
|
|
self.data_history[nid]['t'][i].pop(0)
|
|
|
|
if nid == self.current_view_node:
|
|
for i in range(18):
|
|
self.v_cards[i]['val_lbl'].setText(f"{data['v'][i]:.4f} V")
|
|
self.v_cards[i]['curve'].setData(self.data_history[nid]['v'][i])
|
|
for i in range(5):
|
|
if math.isnan(data['t'][i]):
|
|
self.t_cards[i]['val_lbl'].setText("Fault Wire")
|
|
else:
|
|
self.t_cards[i]['val_lbl'].setText(f"{data['t'][i]:.2f} °C")
|
|
self.t_cards[i]['curve'].setData(self.data_history[nid]['t'][i])
|
|
|
|
# --- CONFIGURE HARDWARE PARSING ENGINE ---
|
|
def configure_node_registers(isoUART, node_id):
|
|
"""Initializes register maps identically across deployment loops without fault trapping limits."""
|
|
isoUART.writeRegister(node_id, REG['OL_OV_CFG'], 0x03, 0xFF)
|
|
isoUART.writeRegister(node_id, REG['OL_UV_CFG'], 0x00, 0x00)
|
|
|
|
isoUART.writeRegister(node_id, REG['PART_CFG_0'], 0xFF, 0xFF)
|
|
isoUART.writeRegister(node_id, REG['PART_CFG_1'], 0xC0, 0x00)
|
|
|
|
isoUART.writeRegister(node_id, REG['EXT_TEMP_CFG_2'], 0x50, 0x00)
|
|
|
|
isoUART.writeRegister(node_id, 0x3C, RUNTIME_PROFILE["ch_1_3_byte"], RUNTIME_PROFILE["ch_0_1_byte"])
|
|
isoUART.writeRegister(node_id, 0x3D, 0x00, RUNTIME_PROFILE["ch_4_byte"])
|
|
|
|
isoUART.writeRegister(node_id, REG['MISC_CTRL'], 0x00, 0x01)
|
|
|
|
isoUART.writeRegister(node_id, REG['GEN_DIAG'], 0xFF, 0xFF)
|
|
isoUART.writeRegister(node_id, REG['CH_DIAG'], 0xFF, 0xFF)
|
|
|
|
isoUART.writeRegister(node_id, REG['RR_CFG_1'], 0x80, 0x01)
|
|
isoUART.writeRegister(node_id, REG['MEAS_CTRL'], 0xE0, 0xA1)
|
|
|
|
def hardware_loop():
|
|
global _ACTIVE_NODES
|
|
print("\n" + "="*50)
|
|
print(f"STATUS VERIFICATION: Launching baseline hardware backend on {CONFIG['PORT']}...")
|
|
try:
|
|
raw_ser = serial.Serial(CONFIG["PORT"], CONFIG["BAUDRATE"], timeout=0.2)
|
|
isoUART = TLE9018DQK(raw_ser)
|
|
except Exception as e:
|
|
print(f"❌ CRITICAL SERIAL ERROR on {CONFIG['PORT']}: {e}")
|
|
return
|
|
|
|
print("[1/3] Waking up daisy chain transceiver stack...")
|
|
isoUART.wake()
|
|
time.sleep(1.2)
|
|
|
|
print(f"[2/3] Beginning auto-enumeration routine for {_ACTIVE_NODES} node(s)...")
|
|
for i in range(1, _ACTIVE_NODES + 1):
|
|
hi_byte = 0x80 if i == _ACTIVE_NODES else 0x00
|
|
ok, err = isoUART.writeRegister(0x00, REG['IF_CFG'], hi_byte, i)
|
|
if ok:
|
|
print(f" -> Node {i} successfully enumerated and addressed.")
|
|
configure_node_registers(isoUART, i)
|
|
time.sleep(0.01)
|
|
else:
|
|
print(f" ⚠️ Error configuring hardware address on tracking node index {i}")
|
|
|
|
print("[3/3] System locked. Background telemetry scanning loop running.")
|
|
print("="*50 + "\n")
|
|
|
|
while not _STOP_REQUESTED:
|
|
while not _COMMAND_QUEUE.empty():
|
|
t = _COMMAND_QUEUE.get()
|
|
if t['type'] == "ENUM":
|
|
_ACTIVE_NODES = t['count']
|
|
print(f"Re-enumeration requested. Configuring {_ACTIVE_NODES} hardware blocks...")
|
|
isoUART.wake()
|
|
time.sleep(1.2)
|
|
for i in range(1, _ACTIVE_NODES + 1):
|
|
hi_byte = 0x80 if i == _ACTIVE_NODES else 0x00
|
|
ok, err = isoUART.writeRegister(0x00, REG['IF_CFG'], hi_byte, i)
|
|
if ok:
|
|
configure_node_registers(isoUART, i)
|
|
time.sleep(0.01)
|
|
|
|
elif t['type'] == "BAL":
|
|
d1_e6, d0_e6 = (t['e6'] >> 8) & 0xFF, t['e6'] & 0xFF
|
|
d1_e7, d0_e7 = (t['e7'] >> 8) & 0xFF, t['e7'] & 0xFF
|
|
isoUART.writeRegister(t['node'], REG['BAL_CTRL_0'], d1_e6, d0_e6)
|
|
isoUART.writeRegister(t['node'], REG['BAL_CTRL_1'], d1_e7, d0_e7)
|
|
|
|
if _ACTIVE_NODES > 0:
|
|
for n in range(1, _ACTIVE_NODES + 1):
|
|
raw_ser.reset_input_buffer()
|
|
|
|
isoUART.resetWDT(n, 0x7F)
|
|
isoUART.writeRegister(n, REG['MEAS_CTRL'], 0xE0, 0xA1)
|
|
time.sleep(0.006)
|
|
|
|
# 1. Read 18 Cell Voltages
|
|
v_list = []
|
|
for i in range(18):
|
|
ok, data = isoUART.readRegister(n, REG[f'PCVM_{i}'])
|
|
if ok and len(data) >= 2:
|
|
raw_val = (data[1] << 8) | data[0]
|
|
v_list.append((raw_val / 65535.0) * 5.0)
|
|
else:
|
|
v_list.append(0.0)
|
|
|
|
# 2. Read 5 Active NTC Temperature Registers
|
|
t_list = []
|
|
for i in range(5):
|
|
ok, data = raw_data = isoUART.readRegister(n, REG[f'EXT_TEMP_{i}'])
|
|
if ok and len(data) >= 2:
|
|
raw_reg = (data[1] << 8) | data[0]
|
|
|
|
valid = (raw_reg >> 15) & 0x01
|
|
intc = (raw_reg >> 10) & 0x07
|
|
code = raw_reg & 0x03FF
|
|
|
|
if valid and code > 0:
|
|
numerator = code * FSR_TMP_V * (1 << (7 - intc))
|
|
denominator = 1024 * 640e-6
|
|
r_ntc = (numerator / denominator) - R_TMP_OHM
|
|
if r_ntc > 0:
|
|
ntc_r25 = NTC_R25_MAP[i]
|
|
ntc_beta = NTC_BETA_MAP[i]
|
|
|
|
inv_t = (1.0 / NTC_T25_K) + (1.0 / ntc_beta) * math.log(r_ntc / ntc_r25)
|
|
temp_celsius = (1.0 / inv_t) - 273.15
|
|
t_list.append(temp_celsius)
|
|
else:
|
|
t_list.append(float('nan'))
|
|
else:
|
|
t_list.append(float('nan'))
|
|
else:
|
|
t_list.append(float('nan'))
|
|
|
|
try:
|
|
_PLOT_QUEUE.put_nowait({'node': n, 'v': v_list, 't': t_list})
|
|
except queue.Full:
|
|
pass
|
|
|
|
time.sleep(CONFIG["SAMPLE_INTERVAL_S"])
|
|
|
|
if __name__ == "__main__":
|
|
threading.Thread(target=hardware_loop, daemon=True).start()
|
|
app = QtWidgets.QApplication(sys.argv)
|
|
win = MultiCMBDashboard()
|
|
win.showMaximized()
|
|
sys.exit(app.exec()) |