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()