From 25d8dc7d056eb8d5c2ebf576901de99a1d3e71c1 Mon Sep 17 00:00:00 2001 From: VineetaGupta Date: Thu, 10 Apr 2025 16:44:25 +0200 Subject: [PATCH] Added a simple gui to see the status of the testbench and test script base for future logging (requiremnts to be fixed) --- gui.py | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.py | 91 +++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 gui.py create mode 100644 test.py diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..81f88a8 --- /dev/null +++ b/gui.py @@ -0,0 +1,241 @@ +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() diff --git a/test.py b/test.py new file mode 100644 index 0000000..19d3e25 --- /dev/null +++ b/test.py @@ -0,0 +1,91 @@ +import pandas as pd +from datetime import datetime +import os +import time +import canopen +import random # Required for status (Success/Error) + +num_devices = 20 # One device (Valve 26) +tpdo_object_ids = [6004, 6005, 6006] + +# Initialize data blocks to store time, written, read, and status +device_data_blocks = {f"Valve {device_id}": {"Time": [], "Written": [], "Read": [], "Status": []} for device_id in range(1, num_devices + 1)} + +# CANopen Network Setup +network = canopen.Network() +network.connect(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000) + +# Load node with EDS file +node = canopen.RemoteNode(26, r'C:\Users\vineetagupta\Documents\NorthStar-Motor-Valve-Board-Firmware\MotorValveBoard\coappl\motorcontrollervalve.eds') +network.add_node(node) + +# Set node to operational +node.nmt.state = 'OPERATIONAL' +time.sleep(0.5) + +# Read TPDO config +node.tpdo.read() +node.tpdo[1].enabled = True # Assuming TPDO1 has the object mapped + +# Print mapped variables for debugging +print("Mapped TPDO1 objects:") +for var in node.tpdo[1].map: + print(f" - {var.name} (Index: {hex(var.index)}, Subindex: {var.subindex})") + +# Create a callback to handle incoming TPDO data +written_data = None +read_data = None + +def pdo_callback(map_obj): + global written_data, read_data + for var in map_obj: + # Check the index of the received TPDO + if var.index == 0x6004: # Written data + written_data = var.raw + print(f"Received Written data (6004): {written_data}") + elif var.index == 0x6005: # Read data + read_data = var.raw + print(f"Received Read data (6005): {read_data}") + +# Add the callback to TPDO +node.tpdo[1].add_callback(pdo_callback) + +# Append data to the data structure +def append_data(): + global written_data, read_data + for device_id in range(1, num_devices + 1): + if written_data is not None and read_data is not None: + # Append the new data with current timestamp + current_time = datetime.now().strftime('%H:%M:%S') + device_data_blocks[f"Valve {device_id}"]["Time"].append(current_time) + device_data_blocks[f"Valve {device_id}"]["Written"].append(written_data) + device_data_blocks[f"Valve {device_id}"]["Read"].append(read_data) + device_data_blocks[f"Valve {device_id}"]["Status"].append(random.choice(["Success", "Error"])) + +# Run the loop to keep fetching and logging data +try: + while True: + append_data() + time.sleep(1) # Adjust the time as needed + +except KeyboardInterrupt: + print("\nLogging stopped by user (Ctrl+C). Saving data...") + + # After stopping, convert the dynamic data to a pandas DataFrame + device_dataframes = [] + for device_id in range(1, num_devices + 1): + data = device_data_blocks[f"Valve {device_id}"] + df = pd.DataFrame(data) + df.columns = pd.MultiIndex.from_product([[f"Valve {device_id}"], df.columns]) + device_dataframes.append(df) + + # Combine all device dataframes + final_df = pd.concat(device_dataframes, axis=1) + + # Save to Excel with multi-level headers + file_name = 'ValveData.xlsx' + final_df.to_excel(file_name) + + # Show file path + file_path = os.path.abspath(file_name) + print(f"Grouped data saved to: {file_path}")