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.
207 lines
6.1 KiB
207 lines
6.1 KiB
#!/usr/bin/env python3
|
|
"""
|
|
Launch Isaac Sim + retargeting bridge, and print the Quest 3 connection URL.
|
|
|
|
Starts everything needed in one command:
|
|
1. Isaac Sim (G1 with Inspire hands, DDS teleoperation)
|
|
2. Retargeting bridge (WebSocket server + body retargeting + DDS publisher)
|
|
|
|
Usage:
|
|
python launch_bridge.py
|
|
python launch_bridge.py --task Isaac-PickPlace-RedBlock-G129-Inspire-Joint
|
|
python launch_bridge.py --no-sim # bridge only (if sim is already running)
|
|
python launch_bridge.py --device cuda # use GPU for simulation
|
|
"""
|
|
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
import signal
|
|
import time
|
|
import threading
|
|
|
|
|
|
SIM_DIR = os.path.expanduser("~/git/unitree_sim_isaaclab")
|
|
BRIDGE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Default Isaac Sim arguments
|
|
DEFAULT_TASK = "Isaac-PickPlace-Cylinder-G129-Inspire-Joint"
|
|
DEFAULT_DEVICE = "cpu"
|
|
|
|
# Message printed by sim when DDS is ready
|
|
SIM_READY_MARKER = "start controller success"
|
|
|
|
|
|
def get_local_ip():
|
|
"""Get local network IP via UDP socket (no traffic sent)."""
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
try:
|
|
s.connect(("8.8.8.8", 80))
|
|
return s.getsockname()[0]
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
def stream_and_watch(proc, marker, event):
|
|
"""Read process stdout line-by-line, print it, and set event when marker seen."""
|
|
for line in iter(proc.stdout.readline, ""):
|
|
sys.stdout.write(f" [sim] {line}")
|
|
sys.stdout.flush()
|
|
if marker in line:
|
|
event.set()
|
|
proc.stdout.close()
|
|
|
|
|
|
def stream_stderr(proc):
|
|
"""Forward process stderr."""
|
|
for line in iter(proc.stderr.readline, ""):
|
|
sys.stderr.write(f" [sim] {line}")
|
|
sys.stderr.flush()
|
|
proc.stderr.close()
|
|
|
|
|
|
def main():
|
|
# --- Parse our args (separate from bridge args) ---
|
|
port = 8765
|
|
task = DEFAULT_TASK
|
|
device = DEFAULT_DEVICE
|
|
no_sim = False
|
|
bridge_args = []
|
|
|
|
# Simple arg parser that pulls out our flags and passes the rest to the bridge
|
|
args = sys.argv[1:]
|
|
i = 0
|
|
while i < len(args):
|
|
if args[i] == "--port" and i + 1 < len(args):
|
|
port = int(args[i + 1])
|
|
bridge_args.extend(args[i:i+2])
|
|
i += 2
|
|
elif args[i] == "--task" and i + 1 < len(args):
|
|
task = args[i + 1]
|
|
i += 2
|
|
elif args[i] == "--device" and i + 1 < len(args):
|
|
device = args[i + 1]
|
|
i += 2
|
|
elif args[i] == "--no-sim":
|
|
no_sim = True
|
|
i += 1
|
|
else:
|
|
bridge_args.append(args[i])
|
|
i += 1
|
|
|
|
ip = get_local_ip()
|
|
|
|
# --- Banner ---
|
|
print()
|
|
print("=" * 60)
|
|
print(" G1 Teleop -> Isaac Sim Retargeting Bridge")
|
|
print("=" * 60)
|
|
print()
|
|
print(f" Quest 3 connection:")
|
|
print(f" IP: {ip}")
|
|
print(f" Port: {port}")
|
|
print()
|
|
if not no_sim:
|
|
print(f" Isaac Sim:")
|
|
print(f" Task: {task}")
|
|
print(f" Device: {device}")
|
|
print()
|
|
print("=" * 60)
|
|
print()
|
|
sys.stdout.flush()
|
|
|
|
sim_proc = None
|
|
bridge_proc = None
|
|
|
|
def cleanup(sig=None, frame=None):
|
|
"""Shut down both processes on exit."""
|
|
print("\nShutting down...")
|
|
if bridge_proc and bridge_proc.poll() is None:
|
|
bridge_proc.send_signal(signal.SIGINT)
|
|
bridge_proc.wait(timeout=5)
|
|
if sim_proc and sim_proc.poll() is None:
|
|
sim_proc.send_signal(signal.SIGINT)
|
|
sim_proc.wait(timeout=10)
|
|
sys.exit(0)
|
|
|
|
signal.signal(signal.SIGINT, cleanup)
|
|
signal.signal(signal.SIGTERM, cleanup)
|
|
|
|
# --- Launch Isaac Sim ---
|
|
if not no_sim:
|
|
sim_cmd = [
|
|
sys.executable, os.path.join(SIM_DIR, "sim_main.py"),
|
|
"--device", device,
|
|
"--enable_cameras",
|
|
"--task", task,
|
|
"--enable_inspire_dds",
|
|
"--robot_type", "g129",
|
|
]
|
|
print(f"[1/2] Starting Isaac Sim...")
|
|
print(f" {' '.join(sim_cmd)}")
|
|
print()
|
|
sys.stdout.flush()
|
|
|
|
sim_proc = subprocess.Popen(
|
|
sim_cmd,
|
|
cwd=SIM_DIR,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
|
|
# Stream sim output and watch for ready marker
|
|
sim_ready = threading.Event()
|
|
stdout_thread = threading.Thread(
|
|
target=stream_and_watch, args=(sim_proc, SIM_READY_MARKER, sim_ready),
|
|
daemon=True)
|
|
stderr_thread = threading.Thread(
|
|
target=stream_stderr, args=(sim_proc,),
|
|
daemon=True)
|
|
stdout_thread.start()
|
|
stderr_thread.start()
|
|
|
|
# Wait for sim to be ready (timeout after 120s)
|
|
print(" Waiting for Isaac Sim to initialize (this may take a minute)...")
|
|
sys.stdout.flush()
|
|
if not sim_ready.wait(timeout=120):
|
|
if sim_proc.poll() is not None:
|
|
print("\n ERROR: Isaac Sim exited unexpectedly.")
|
|
sys.exit(1)
|
|
print("\n WARNING: Timed out waiting for sim ready marker.")
|
|
print(" Starting bridge anyway (sim may still be loading).")
|
|
else:
|
|
print(" Isaac Sim is ready!")
|
|
print()
|
|
sys.stdout.flush()
|
|
|
|
# --- Launch bridge ---
|
|
bridge_cmd = [
|
|
sys.executable, os.path.join(BRIDGE_DIR, "retarget_bridge.py"),
|
|
"--port", str(port),
|
|
] + bridge_args
|
|
|
|
step = "2/2" if not no_sim else "1/1"
|
|
print(f"[{step}] Starting retargeting bridge...")
|
|
print()
|
|
print("=" * 60)
|
|
print(f" READY -- connect Quest 3 to: {ip}:{port}")
|
|
print("=" * 60)
|
|
print()
|
|
sys.stdout.flush()
|
|
|
|
bridge_proc = subprocess.Popen(bridge_cmd, cwd=BRIDGE_DIR)
|
|
|
|
try:
|
|
# Wait for bridge to exit (or Ctrl-C)
|
|
bridge_proc.wait()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
finally:
|
|
cleanup()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|