NorthStar-Endurance-TestBench/gui.py

242 lines
8.8 KiB
Python

import sys
import time
import canopen
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QHBoxLayout, QLabel, QGroupBox, QFormLayout
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QColor
# Connect to CAN network
network = canopen.Network()
network.connect(channel='PCAN_USBBUS1', bustype='pcan', bitrate=250000)
motorBoardEdsPath = r'C:\Users\vineetagupta\Documents\NorthStar-Motor-Valve-Board-Firmware\MotorValveBoard\coappl\motorcontrollervalve.eds'
puBoardEdsPath = r'C:\Users\vineetagupta\Documents\NorthStar-Production-Unit-Board-Firmware\ProcessBoardV1\coappl\processBoard_1.eds'
nodes = {}
motorBoardTpdoData = {} # Store latest setpoint and feedback per node
PuBoardData = {}
def PUBoardNode():
global PuBoardData
try:
PuBoardData = canopen.RemoteNode(0x1, puBoardEdsPath)
network.add_node(PuBoardData)
time.sleep(0.1)
# Add to node list early for visibility
nodes[1] = PuBoardData
# Now check if extra node is OPERATIONAL
if PuBoardData.nmt.state == 'OPERATIONAL':
PuBoardData.tpdo.read()
PuBoardData.tpdo[1].enabled = True
def make_extra_callback():
def cb(map_obj):
global PuBoardData
for var in map_obj:
# Flow meter data (0x6005, 0x01 to 0x04)
if var.index == 0x6005:
if var.subindex == 0x01:
PuBoardData['flowmeter']['0x01'] = var.raw
elif var.subindex == 0x02:
PuBoardData['flowmeter']['0x02'] = var.raw
elif var.subindex == 0x03:
PuBoardData['flowmeter']['0x03'] = var.raw
elif var.subindex == 0x04:
PuBoardData['flowmeter']['0x04'] = var.raw
# Pressure sensor data (0x6006, 0x01 to 0x04)
elif var.index == 0x6006:
if var.subindex == 0x01:
PuBoardData['pressure']['0x01'] = var.raw
elif var.subindex == 0x02:
PuBoardData['pressure']['0x02'] = var.raw
elif var.subindex == 0x03:
PuBoardData['pressure']['0x03'] = var.raw
elif var.subindex == 0x04:
PuBoardData['pressure']['0x04'] = var.raw
return cb
PuBoardData.tpdo[1].add_callback(make_extra_callback())
else:
pass
except Exception as e:
print(f"Extra Node {1} error: {e}")
def motorNode():
for node_id in range(5, 27):
try:
node = canopen.RemoteNode(node_id, motorBoardEdsPath)
network.add_node(node)
time.sleep(0.1)
# Add to node list early so it's visible in GUI
nodes[node_id] = node
motorBoardTpdoData[node_id] = {'setpoint': '-', 'feedback': '-'}
# Now check if node is OPERATIONAL
if node.nmt.state == 'OPERATIONAL':
node.tpdo.read()
node.tpdo[1].enabled = True
def make_callback(nid):
def cb(map_obj):
for var in map_obj:
if var.index == 0x6002:
motorBoardTpdoData[nid]['setpoint'] = var.raw
elif var.index == 0x6004:
motorBoardTpdoData[nid]['feedback'] = var.raw
return cb
node.tpdo[1].add_callback(make_callback(node_id))
else:
pass
except Exception as e:
print(f"Node {node_id} error: {e}")
# --------------------------- PyQt GUI -------------------------------
class NodeTableWindow(QWidget):
def __init__(self, nodes):
super().__init__()
self.setWindowTitle("Valve Node Status")
self.nodes = nodes
self.num_nodes = len(nodes)
self.init_ui()
self.start_timer()
def init_ui(self):
layout = QHBoxLayout(self)
# Left side - Table
self.table_widget = QWidget(self)
table_layout = QVBoxLayout(self.table_widget)
# Heading for the table
table_heading = QLabel("Valve Details", self)
table_layout.addWidget(table_heading)
# Table
self.table = QTableWidget(self.num_nodes, 4)
self.table.setHorizontalHeaderLabels(["Node ID", "Setpoint", "Feedback", "Status"])
# Dynamic height based on the number of nodes
self.table.setFixedHeight(30 * self.num_nodes) # 30px height per row
self.table.setFixedWidth(500)
# Adjust column width to content
self.table.resizeColumnsToContents
for row, node_id in enumerate(sorted(self.nodes.keys())):
item = QTableWidgetItem(str(node_id))
item.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item)
table_layout.addWidget(self.table)
self.table_widget.setLayout(table_layout)
# Right side - Pump, Flowmeter, and Pressure Sensor Details
self.details_widget = QWidget(self)
details_layout = QVBoxLayout(self.details_widget)
# Pump Details
pump_group = QGroupBox("Pump Details", self)
pump_layout = QFormLayout(pump_group)
pump_speed_label = QLabel("Speed: 0 RPM", self) # Example pump speed
pump_layout.addRow("Pump 1", pump_speed_label)
pump_layout.addRow("Pump 2", pump_speed_label)
pump_layout.addRow("Pump 3", pump_speed_label)
pump_layout.addRow("Pump 4", pump_speed_label)
details_layout.addWidget(pump_group)
# Flow Meter Details
flow_group = QGroupBox("Flow Meter Details", self)
flow_layout = QFormLayout(flow_group)
self.flow_rate_label = QLabel("Flow Rate: 0 L/min", self) # Assign object name
self.flow_rate_label.setObjectName("Flow Rate")
flow_layout.addRow("Flow Rate", self.flow_rate_label)
details_layout.addWidget(flow_group)
# Pressure Sensor Details
pressure_group = QGroupBox("Pressure Sensor Details", self)
pressure_layout = QFormLayout(pressure_group)
self.pressure_value_label = QLabel("Pressure: 0 bar", self) # Assign object name
self.pressure_value_label.setObjectName("Pressure")
pressure_layout.addRow("Pressure", self.pressure_value_label)
details_layout.addWidget(pressure_group)
self.details_widget.setLayout(details_layout)
# Add both widgets (table and details) to the main layout
layout.addWidget(self.table_widget)
layout.addWidget(self.details_widget)
self.setLayout(layout)
# Set window size
screen = QApplication.primaryScreen()
screen_geometry = screen.availableGeometry()
width = int(screen_geometry.width() * 0.4)
height = int(screen_geometry.height() * 0.7)
x = int((screen_geometry.width() - width) / 2)
y = int((screen_geometry.height() - height) / 2)
self.setGeometry(x, y, width, height)
def start_timer(self):
self.timer = QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_table)
self.timer.start()
def update_table(self):
for row, node_id in enumerate(sorted(self.nodes.keys())):
node = self.nodes[node_id]
# Node status
try:
status = node.nmt.state
except:
status = "UNKNOWN"
# Color Node ID based on status
node_item = self.table.item(row, 0)
node_item.setBackground(QColor("green") if status == "OPERATIONAL" else QColor("red"))
# Setpoint
set_val = str(motorBoardTpdoData.get(node_id, {}).get('setpoint', '-'))
setpoint_item = QTableWidgetItem(set_val)
setpoint_item.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 1, setpoint_item)
# Feedback
fb_val = str(motorBoardTpdoData.get(node_id, {}).get('feedback', '-'))
feedback_item = QTableWidgetItem(fb_val)
feedback_item.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, feedback_item)
# Status
status_item = QTableWidgetItem(status)
status_item.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 3, status_item)
def main():
# Initialize all the nodes
motorNode()
PUBoardNode()
# Start the GUI
app = QApplication(sys.argv)
window = NodeTableWindow(nodes)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()