Added connect and disconnect button
This commit is contained in:
parent
25d8dc7d05
commit
db38c17a5e
320
gui.py
320
gui.py
|
|
@ -1,181 +1,106 @@
|
|||
import sys
|
||||
import time
|
||||
import canopen
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QHBoxLayout, QLabel, QGroupBox, QFormLayout
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout,
|
||||
QHBoxLayout, QLabel, QGroupBox, QFormLayout, QPushButton, QMessageBox, QHeaderView
|
||||
)
|
||||
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.motorBoardTpdoData = {}
|
||||
self.PuBoardData = {}
|
||||
self.connected = False
|
||||
self.network = None
|
||||
self.num_nodes = 0
|
||||
self.init_ui()
|
||||
self.start_timer()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QHBoxLayout(self)
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Left side - Table
|
||||
# Control bar with connect button
|
||||
button_row = QHBoxLayout()
|
||||
self.connect_btn = QPushButton("Connect to CAN", self)
|
||||
self.disconnect_btn = QPushButton("Disconnect", self)
|
||||
self.disconnect_btn.setEnabled(False)
|
||||
|
||||
self.connect_btn.clicked.connect(self.handle_connect)
|
||||
self.disconnect_btn.clicked.connect(self.handle_disconnect)
|
||||
|
||||
button_row.addWidget(self.connect_btn)
|
||||
button_row.addWidget(self.disconnect_btn)
|
||||
layout.addLayout(button_row)
|
||||
|
||||
# Main layout split: Table (left) and Details (right)
|
||||
main_layout = QHBoxLayout()
|
||||
|
||||
# Valve Table
|
||||
self.table_widget = QWidget(self)
|
||||
table_layout = QVBoxLayout(self.table_widget)
|
||||
table_layout = QVBoxLayout()
|
||||
|
||||
# Heading for the table
|
||||
table_heading = QLabel("Valve Details", self)
|
||||
# Valve Details Label
|
||||
table_heading = QLabel("Valve Details")
|
||||
table_heading.setAlignment(Qt.AlignLeft)
|
||||
table_layout.addWidget(table_heading)
|
||||
|
||||
# Table
|
||||
self.table = QTableWidget(self.num_nodes, 4)
|
||||
# Table Setup
|
||||
self.table = QTableWidget(0, 4)
|
||||
self.table.setHorizontalHeaderLabels(["Node ID", "Setpoint", "Feedback", "Status"])
|
||||
self.table.horizontalHeader().setStretchLastSection(True)
|
||||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
|
||||
# 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)
|
||||
|
||||
# Add table to layout
|
||||
table_layout.addWidget(self.table)
|
||||
|
||||
# Apply layout to table_widget
|
||||
self.table_widget.setLayout(table_layout)
|
||||
|
||||
# Right side - Pump, Flowmeter, and Pressure Sensor Details
|
||||
|
||||
# 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)
|
||||
self.pump_labels = []
|
||||
for i in range(1, 5):
|
||||
label = QLabel("Speed: 0 RPM", self)
|
||||
self.pump_labels.append(label)
|
||||
pump_layout.addRow(f"Pump {i}", 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)
|
||||
self.flow_rate_label = []
|
||||
for i in range(1, 5):
|
||||
label = QLabel("Flow: 0 L/h", self)
|
||||
self.flow_rate_label.append(label)
|
||||
flow_layout.addRow(f"Flowmeter {i}", 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")
|
||||
self.pressure_value_label = QLabel("Pressure: 0 bar", self)
|
||||
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)
|
||||
# Combine layouts
|
||||
main_layout.addWidget(self.table_widget)
|
||||
main_layout.addWidget(self.details_widget)
|
||||
layout.addLayout(main_layout)
|
||||
|
||||
# Set window size
|
||||
screen = QApplication.primaryScreen()
|
||||
|
|
@ -186,56 +111,157 @@ class NodeTableWindow(QWidget):
|
|||
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 handle_connect(self):
|
||||
try:
|
||||
self.network = canopen.Network()
|
||||
self.network.connect(channel='PCAN_USBBUS1', bustype='pcan', bitrate=250000)
|
||||
|
||||
self.nodes = {}
|
||||
self.motorBoardTpdoData = {}
|
||||
self.PuBoardData = {}
|
||||
|
||||
self.init_motor_nodes()
|
||||
self.init_pu_node()
|
||||
|
||||
self.connected = True
|
||||
self.num_nodes = len(self.nodes)
|
||||
self.table.setRowCount(self.num_nodes)
|
||||
|
||||
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)
|
||||
|
||||
self.connect_btn.setStyleSheet("background-color: lightgreen;")
|
||||
self.disconnect_btn.setEnabled(True)
|
||||
QMessageBox.information(self, "Connected", "Successfully connected to CAN and initialized nodes.")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Connection Error", f"Failed to connect:\n{e}")
|
||||
self.connected = False
|
||||
|
||||
|
||||
def handle_disconnect(self):
|
||||
try:
|
||||
if self.network:
|
||||
self.network.disconnect()
|
||||
|
||||
self.connected = False
|
||||
self.nodes.clear()
|
||||
self.motorBoardTpdoData.clear()
|
||||
self.PuBoardData.clear()
|
||||
|
||||
self.table.clearContents()
|
||||
self.table.setRowCount(0)
|
||||
|
||||
self.connect_btn.setEnabled(True)
|
||||
self.disconnect_btn.setEnabled(False)
|
||||
self.connect_btn.setStyleSheet("") # Resets to default style
|
||||
|
||||
QMessageBox.information(self, "Disconnected", "CAN network disconnected.")
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Disconnect Error", f"Error while disconnecting:\n{e}")
|
||||
|
||||
|
||||
def init_motor_nodes(self):
|
||||
for node_id in range(5, 27):
|
||||
try:
|
||||
node = canopen.RemoteNode(node_id, motorBoardEdsPath)
|
||||
self.network.add_node(node)
|
||||
time.sleep(0.1)
|
||||
|
||||
self.nodes[node_id] = node
|
||||
self.motorBoardTpdoData[node_id] = {'setpoint': '-', 'feedback': '-'}
|
||||
|
||||
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:
|
||||
self.motorBoardTpdoData[nid]['setpoint'] = var.raw
|
||||
elif var.index == 0x6004:
|
||||
self.motorBoardTpdoData[nid]['feedback'] = var.raw
|
||||
return cb
|
||||
|
||||
node.tpdo[1].add_callback(make_callback(node_id))
|
||||
except Exception as e:
|
||||
print(f"Motor node {node_id} error: {e}")
|
||||
|
||||
def init_pu_node(self):
|
||||
try:
|
||||
node = canopen.RemoteNode(0x1, puBoardEdsPath)
|
||||
self.network.add_node(node)
|
||||
time.sleep(0.1)
|
||||
self.nodes[1] = node
|
||||
self.PuBoardData = {'flowmeter': {}, 'pressure': {}}
|
||||
|
||||
if node.nmt.state == 'OPERATIONAL':
|
||||
node.tpdo.read()
|
||||
node.tpdo[1].enabled = True
|
||||
|
||||
def make_callback():
|
||||
def cb(map_obj):
|
||||
for var in map_obj:
|
||||
if var.index == 0x6005:
|
||||
self.PuBoardData['flowmeter'][f'0x{var.subindex:02X}'] = var.raw
|
||||
elif var.index == 0x6006:
|
||||
self.PuBoardData['pressure'][f'0x{var.subindex:02X}'] = var.raw
|
||||
return cb
|
||||
|
||||
node.tpdo[1].add_callback(make_callback())
|
||||
except Exception as e:
|
||||
print(f"PU node error: {e}")
|
||||
|
||||
def update_table(self):
|
||||
if not self.connected:
|
||||
return
|
||||
|
||||
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"))
|
||||
if node_item:
|
||||
node_item.setBackground(
|
||||
QColor("green") if status == "OPERATIONAL" else
|
||||
QColor("yellow") if status == "PRE-OPERATIONAL" else
|
||||
QColor("red")
|
||||
)
|
||||
|
||||
# Setpoint
|
||||
set_val = str(motorBoardTpdoData.get(node_id, {}).get('setpoint', '-'))
|
||||
set_val = str(self.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', '-'))
|
||||
fb_val = str(self.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 = NodeTableWindow({})
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user