Added a simple gui to see the status of the testbench and test script base for future logging (requiremnts to be fixed)
This commit is contained in:
parent
e904c98fac
commit
25d8dc7d05
241
gui.py
Normal file
241
gui.py
Normal file
|
|
@ -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()
|
||||||
91
test.py
Normal file
91
test.py
Normal file
|
|
@ -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}")
|
||||||
Loading…
Reference in New Issue
Block a user