You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

547 lines
20 KiB

import json
import os
import signal
import subprocess
import sys
from typing import Dict
import library.log as log
import library.qtgui as qtgui
from library.universal import G
from PyQt6.QtCore import QProcess, QSettings, Qt
from PyQt6.QtWidgets import (
QApplication,
QCheckBox,
QComboBox,
QFormLayout,
QGridLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
import gr00t_wbc
signal.signal(signal.SIGINT, signal.SIG_DFL)
GR00T_TELEOP_DATA_ROOT = os.path.join(os.path.dirname(gr00t_wbc.__file__), "./external/teleop/data")
class MainWindow(QMainWindow):
def __init__(
self,
fix_pick_up=True,
fix_pointing=False,
object_A_options=[
"small cup",
"dragon fruit",
"orange",
"apple",
"mango",
"star fruit",
"rubiks cube",
"lemon",
"lime",
"red can",
"blue can",
"green can",
"cucumber",
"bottled water",
"big cup",
"mayo",
"mustard",
"bok choy",
"grapes",
"soup can",
"mouse",
"water apple",
"corn",
"mug",
"orange cup",
"bitter gourd",
"banana",
"mangosteen",
"marker",
"coffee pod",
"plastic cup",
"grapes",
"small mug",
"condiment bottles",
"corn",
"tools",
"pear",
"eggplant",
"canned beans",
"potato",
],
object_from_options=[
"cutting board",
"pan",
"plate",
"bowl",
"tray",
"desk",
"placemat",
"table",
"mesh cup",
"shelf",
],
object_to_options=[
"cutting board",
"pan",
"plate",
"bowl",
"tray",
"microwave",
"basket",
"drawer",
"placemat",
"clear bin",
"mesh cup",
"yellow bin",
"shelf",
],
):
super().__init__()
# Fix the pick up and place object type
self.fix_pick_up = fix_pick_up
self.fix_pointing = fix_pointing
# window settings
self.setWindowTitle("Gr00t Capture Test")
self.setFixedWidth(800)
self.setMinimumHeight(1000)
self.main_layout = QVBoxLayout()
self.main_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# stop button
self.stop_button = QPushButton("Emergency Stop", self)
self.stop_button.setStyleSheet(
"background-color: red; color: white; font-size: 20px; font-weight: bold; height: 60px;"
)
# self.stop_button.setEnabled(False)
self.stop_button.clicked.connect(self.stop_process)
self.main_layout.addWidget(self.stop_button)
# property label
self.property_label = QLabel("Settings")
self.property_label.setStyleSheet(
"font-size: 20px; font-weight: bold; color: #333; max-hedescription.jight: 30px;"
)
self.main_layout.addWidget(self.property_label)
# operator form
settings_form_layout = QFormLayout()
self.operator_input = QLineEdit("Zu")
# self.operator_input.textChanged.connect(self.property_label.setText)
settings_form_layout.addRow(QLabel("Operator Name:"), self.operator_input)
# collector form
self.collector_input = QLineEdit("Zu")
settings_form_layout.addRow(QLabel("Collector Name:"), self.collector_input)
# description form
self.description_input = QLineEdit("3D pick up")
# object A menu
self.object_A_input = QComboBox()
self.object_A_input.addItems(object_A_options)
# object A menu
self.object_from_input = QComboBox()
self.object_from_input.addItems(object_from_options)
# object B menu
self.object_to_input = QComboBox()
self.object_to_input.addItems(object_to_options)
box_layout = QHBoxLayout()
box_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
if self.fix_pick_up:
# box_layout.addWidget(QLabel("Pour Object:"))
box_layout.addWidget(QLabel("Pick up Object:"))
box_layout.addWidget(self.object_A_input)
box_layout.addWidget(QLabel("from:"))
box_layout.addWidget(self.object_from_input)
box_layout.addWidget(QLabel("To:"))
box_layout.addWidget(self.object_to_input)
elif self.fix_pointing:
box_layout.addWidget(QLabel("Pointing Object:"))
box_layout.addWidget(self.object_A_input)
else:
box_layout.addWidget(self.description_input)
# add a button to fix the pick up and place object
self.save_description_button = QPushButton("Save Description")
self.save_description_button.setMinimumWidth(100)
self.save_description_button.clicked.connect(self.save_settings)
box_layout.addWidget(self.save_description_button)
settings_form_layout.addRow(QLabel("Task description:"), box_layout)
# robot form
self.robot_input = QLineEdit("gr00t002")
settings_form_layout.addRow(QLabel("Robot Name:"), self.robot_input)
# vive keyword form
self.vive_keyword_input = QComboBox()
self.vive_keyword_input.addItems(["elbow", "knee", "wrist", "shoulder", "foot"])
settings_form_layout.addRow(QLabel("Vive keyword:"), self.vive_keyword_input)
settings_form_layout.addRow(QLabel("-" * 300))
self.vive_ip_input = QLineEdit("192.168.0.182")
settings_form_layout.addRow(QLabel("Vive ip:"), self.vive_ip_input)
self.vive_port_input = QLineEdit("5555")
settings_form_layout.addRow(QLabel("Vive port:"), self.vive_port_input)
self.manus_port_input = QLineEdit("5556")
settings_form_layout.addRow(QLabel("Manus port:"), self.manus_port_input)
# manus form
# self.manus_input = QLineEdit("foot")
# settings_form_layout.addRow(QLabel("Manus name:"), self.manus_input)
self.main_layout.addLayout(settings_form_layout)
# testing buttons
self.testing_button_layout = QGridLayout()
# vive button
self.test_vive_button = QPushButton("Test Vive")
self.test_vive_button.setCheckable(True)
self.test_vive_button.toggled.connect(self.test_vive)
self.test_vive_button.setMaximumHeight(30)
self.testing_button_layout.addWidget(self.test_vive_button, 0, 1)
self.vive_checkbox = QCheckBox("Vive Ready")
self.testing_button_layout.addWidget(self.vive_checkbox, 0, 2)
# start manus button
self.server_manus_button = QPushButton("Start Manus")
self.server_manus_button.setCheckable(True)
self.server_manus_button.toggled.connect(self.test_manus_server)
self.server_manus_button.setMaximumHeight(30)
self.testing_button_layout.addWidget(self.server_manus_button, 1, 0)
# manus button
self.client_manus_button = QPushButton("Test Manus")
self.client_manus_button.setCheckable(True)
self.client_manus_button.toggled.connect(self.test_manus_client)
self.client_manus_button.setMaximumHeight(30)
self.testing_button_layout.addWidget(self.client_manus_button, 1, 1)
self.manus_checkbox = QCheckBox("Manus Ready")
self.manus_checkbox.setEnabled(False)
self.testing_button_layout.addWidget(self.manus_checkbox, 1, 2)
# self.testing_button_layout.addWidget()
self.main_layout.addLayout(self.testing_button_layout)
container = QWidget()
container.setLayout(self.main_layout)
# Set the central widget of the Window.
self.setCentralWidget(container)
# Process
# QProcess for running scripts
self.process = QProcess(self)
# self.process.readyRead.connect(self.handle_stdout)
self.process.readyReadStandardOutput.connect(self.handle_stdout)
self.process.readyReadStandardError.connect(self.handle_stderr)
self.process.errorOccurred.connect(self.process_error)
self.process.finished.connect(self.process_finished)
self.current_process_name = ""
# QProcess for running scripts
self.manus_process = QProcess(self)
# self.process.readyRead.connect(self.handle_stdout)
self.manus_process.readyReadStandardOutput.connect(self.handle_stdout)
self.manus_process.readyReadStandardError.connect(self.handle_stderr)
self.manus_process.finished.connect(self.process_finished)
# load history
self.load_settings()
# record all buttons
self.test_buttons: Dict[str, QPushButton] = {
"test_vive": self.test_vive_button,
"test_manus": self.client_manus_button,
}
def load_settings(self):
settings = QSettings("Gear", "Teleop")
self.vive_ip_input.setText(settings.value("vive_ip", "192.168.0.182"))
self.vive_keyword_input.setCurrentIndex(int(settings.value("vive_keyword", 0)))
self.robot_input.setText(settings.value("robot_input", ""))
self.operator_input.setText(settings.value("operator", ""))
self.collector_input.setText(settings.value("data_collector", ""))
self.description_input.setText(settings.value("description", ""))
self.object_A_input.setCurrentIndex(int(settings.value("object", 0)))
self.object_from_input.setCurrentIndex(int(settings.value("object_from", 0)))
self.object_to_input.setCurrentIndex(int(settings.value("object_to", 0)))
def save_settings(self):
# Create QSettings object (You can provide your app name and organization for persistent storage)
settings = QSettings("Gear", "Teleop")
# Save the text from the QLineEdit
settings.setValue("vive_ip", self.vive_ip_input.text())
settings.setValue("vive_keyword", self.vive_keyword_input.currentIndex())
settings.setValue("robot_input", self.robot_input.text())
settings.setValue("operator", self.operator_input.text())
settings.setValue("data_collector", self.collector_input.text())
if self.fix_pick_up:
description = (
f"pick {self.object_A_input.currentText()} "
f"{self.object_from_input.currentText()}->"
f"{self.object_to_input.currentText()}"
)
elif self.fix_pointing:
description = f"point at {self.object_A_input.currentText()}"
else:
description = self.description_input.text()
settings.setValue("description", description)
settings.setValue("object", self.object_A_input.currentIndex())
settings.setValue("object_from", self.object_from_input.currentIndex())
settings.setValue("object_to", self.object_to_input.currentIndex())
# Save the settings
descrition_file = os.path.join(GR00T_TELEOP_DATA_ROOT, "description.json")
with open(descrition_file, "w") as f:
json.dump(
{
"operator": self.operator_input.text(),
"data_collector": self.collector_input.text(),
"description": description,
},
f,
)
print("Settings saved to {}".format(descrition_file))
################################# Process #################################
def handle_stdout(self):
data = self.process.readAllStandardOutput().data()
stdout = data.decode("utf-8")
G.app.log_window.addLogMessage(stdout, log.DEBUG)
data = self.manus_process.readAllStandardOutput().data()
stdout = data.decode("utf-8")
G.app.log_window.addLogMessage(stdout, log.DEBUG)
def handle_stderr(self):
data = self.process.readAllStandardError().data()
stderr = data.decode("utf-8")
G.app.log_window.addLogMessage(stderr, log.ERROR)
data = self.manus_process.readAllStandardError().data()
stderr = data.decode("utf-8")
G.app.log_window.addLogMessage(stderr, log.ERROR)
def process_finished(self, exit_code, exit_status):
if exit_status == QProcess.ExitStatus.NormalExit:
G.app.log_window.addLogMessage(
f"Process finished successfully with exit code {exit_code}.", log.INFO
)
else:
G.app.log_window.addLogMessage(
f"Process finished with error code {exit_code}.", log.ERROR
)
G.app.log_window.addLogMessage("---------------------------------------------", log.INFO)
# toggle the button for the current task
print("current_process_name", self.current_process_name, self.test_buttons.keys())
if self.current_process_name in self.test_buttons.keys():
if self.test_buttons[self.current_process_name].isChecked():
self.test_buttons[self.current_process_name].click()
def process_error(self, error):
G.app.log_window.addLogMessage(f"Process Error (or stopped manually): {error}", log.ERROR)
G.app.log_window.addLogMessage("---------------------------------------------", log.INFO)
################################# Timer #################################
def update_log(self):
print("update log")
# G.app.log_window.addLogMessage("Update log", log.DEBUG)
returnBool = self.process.waitForFinished(1000)
print("returnBool", returnBool)
if not returnBool:
data = self.process.readAllStandardOutput().data()
stdout = data.decode("utf8")
if stdout:
G.app.log_window.addLogMessage(stdout, log.DEBUG)
G.app.log_window.updateView()
def test_vive(self, checked):
print("checked", checked)
if not checked:
self.test_vive_button.setText("Test Vive")
self.stop_process()
self.current_process_name = ""
else:
self.current_process_name = "test_vive"
self.test_vive_button.setText("Stop Test")
self.stop_button.setEnabled(True)
G.app.log_window.addLogMessage("Testing Vive", log.DEBUG)
script = "python"
args = [
"gr00t_wbc/control/teleop/main/test_vive.py",
"--keyword",
self.vive_keyword_input.currentText(),
"--ip",
self.vive_ip_input.text(),
"--port",
self.vive_port_input.text(),
]
self.process.start(script, args)
def test_manus_server(self, checked):
if not checked: # 2
self.server_manus_button.setText("Start Manus")
self.stop_manus_server()
self.manus_checkbox.setEnabled(False)
else: # 1
self.server_manus_button.setText("Stop Manus")
self.manus_checkbox.setEnabled(True)
self.start_manus_server()
def start_manus_server(self):
G.app.log_window.addLogMessage("Starting manus server", log.WARNING)
script = "python"
args = ["gr00t_wbc/control/teleop/device/manus.py", "--port", self.manus_port_input.text()]
self.manus_process.start(script, args)
def stop_manus_server(self):
G.app.log_window.addLogMessage("Stopping manus server", log.WARNING)
self.manus_process.terminate()
self.manus_process.waitForFinished(2000)
if self.manus_process.state() != QProcess.ProcessState.NotRunning:
self.manus_process.kill()
self.stop_button.setEnabled(False)
G.app.log_window.addLogMessage("Manus stopped", log.DEBUG)
def test_manus_client(self, checked):
if not checked:
self.client_manus_button.setText("Test Manus")
self.stop_process()
self.current_process_name = ""
else:
self.current_process_name = "test_manus"
self.client_manus_button.setText("Stop Test")
self.stop_button.setEnabled(True)
G.app.log_window.addLogMessage("Testing", log.DEBUG)
print("Testing manus")
script = "python"
args = [
"gr00t_wbc/control/teleop/main/test_manus.py",
"--port",
self.manus_port_input.text(),
]
self.process.start(script, args)
############################### Stop Process #################################
def stop_process(self):
G.app.log_window.addLogMessage("Stopping process", log.WARNING)
# disable all buttons
for button in self.test_buttons.values():
button.setCheckable(False)
button.setChecked(False)
button.setEnabled(False)
# kill the process
if self.process.state() == QProcess.ProcessState.Running:
# os.kill(self.process.processId(), signal.SIGINT)
self.process.waitForFinished(2000) # Wait for up to 2 seconds for termination
if self.process.state() != QProcess.ProcessState.NotRunning:
self.process.kill()
# self.stop_button.setEnabled(False)
G.app.log_window.addLogMessage("Script stopped", log.DEBUG)
if self.current_process_name in ["test_oak", "record_demonstration", "test_robot"]:
command = (
f"ps aux | grep {self.current_process_name}"
+ " | grep -v grep | awk '{print $2}' | xargs kill"
)
# Execute the command with subprocess.Popen, capturing both stdout and stderr
sub_process = subprocess.Popen(
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
# Get the output and errors (if any)
stdout, stderr = sub_process.communicate()
# Ensure the process is successfully terminated
sub_process.wait()
# enable all buttons
for button in self.test_buttons.values():
button.setCheckable(True)
button.setEnabled(True)
# check the button if the button was checked
if self.current_process_name in self.test_buttons.keys():
if self.test_buttons[self.current_process_name].isChecked():
self.test_buttons[self.current_process_name].setChecked(False)
class LogWindow(qtgui.ListView):
def __init__(self):
super(LogWindow, self).__init__()
self.level = log.DEBUG
self.allowMultipleSelection(True)
def setLevel(self, level):
self.level = level
self.updateView()
def keyPressEvent(self, e):
e.ignore()
def updateView(self):
for i in range(self.count()):
ilevel = self.getItemData(i)
self.showItem(i, ilevel >= self.level)
self.setItemColor(i, log.getLevelColor(ilevel))
def addLogMessage(self, text, level=log.INFO):
index = self.count()
color = log.getLevelColor(level)
self.addLogItem(text, color, level)
self.showItem(index, level >= self.level)
class GCApplication(QApplication):
def __init__(self, argv):
super(GCApplication, self).__init__(argv)
# application
if G.app is not None:
raise RuntimeError("MHApplication is a singleton")
G.app = self
self.mainwin = MainWindow()
self.log_window = LogWindow()
self.mainwin.main_layout.addWidget(self.log_window)
self.mainwin.show()
# self.log_window.show()
# Function to terminate the subprocess gracefully
def terminate_subprocess(proc):
if proc.poll() is None: # Check if the process is still running
proc.terminate() # Terminate the process
print("Subprocess terminated.")
if __name__ == "__main__":
app = GCApplication(sys.argv)
sys.exit(app.exec())