Browse Source
Document code deployment workflow, update launch/pull scripts
Document code deployment workflow, update launch/pull scripts
- CLAUDE.md: Add CODE DEPLOYMENT WORKFLOW section — git push to Gitea then pull on robot, never SFTP. Document repo layout and two xr_teleoperate locations on robot. - pull_on_robot.py: Updated verification markers (webcam, head_R_at_cal, hasattr guards) - start_teleop_webcam_full.py: Use standalone ~/xr_teleoperate path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>master
3 changed files with 148 additions and 14 deletions
@ -0,0 +1,111 @@ |
|||
"""Launch full teleop with webcam (no image server, with hand drivers). |
|||
|
|||
Replicates start_teleop.sh but skips the image server that kills the UVC driver. |
|||
""" |
|||
import paramiko |
|||
import sys |
|||
import time |
|||
|
|||
sys.stdout.reconfigure(encoding='utf-8', errors='replace') |
|||
|
|||
ssh = paramiko.SSHClient() |
|||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
|||
ssh.connect('10.0.0.64', username='unitree', password='123', |
|||
timeout=15, look_for_keys=False, allow_agent=False) |
|||
|
|||
# ── Step 0: Kill any existing teleop / hand driver processes ── |
|||
print("Cleaning up old processes...") |
|||
_, o, _ = ssh.exec_command( |
|||
'ps aux | grep -E "teleop_hand_and_arm|ModbusDataHandler|image_server" | grep -v grep', |
|||
timeout=5) |
|||
procs = o.read().decode().strip() |
|||
if procs: |
|||
print(f" Found:\n{procs}") |
|||
for line in procs.split('\n'): |
|||
parts = line.split() |
|||
if len(parts) > 1: |
|||
ssh.exec_command(f'kill {parts[1]}', timeout=5) |
|||
time.sleep(1) |
|||
print(" Killed stale processes") |
|||
else: |
|||
print(" No stale processes") |
|||
|
|||
# ── Step 1: Start Inspire hand Modbus-to-DDS drivers ── |
|||
print("\nStarting Inspire hand drivers...") |
|||
|
|||
TELEOP_DIR = "/home/unitree/xr_teleoperate/teleop" |
|||
CYCLONE_HOME = "/home/unitree/g1-control/repos/cyclonedds/install/cyclonedds" |
|||
CYCLONE_URI = "file:///home/unitree/g1-control/repos/cyclonedds/cyclonedds.xml" |
|||
PYTHON = "/home/unitree/miniforge3/envs/tv/bin/python" |
|||
|
|||
env_prefix = ( |
|||
f'export CYCLONEDDS_HOME="{CYCLONE_HOME}" && ' |
|||
f'export CYCLONEDDS_URI="{CYCLONE_URI}" && ' |
|||
) |
|||
|
|||
# Right hand (also initializes DDS) |
|||
right_hand_cmd = ( |
|||
f'{env_prefix} ' |
|||
f'cd {TELEOP_DIR} && ' |
|||
f'nohup {PYTHON} -m inspire_hand.ModbusDataHandler ' |
|||
f'--ip 192.168.123.211 --initDDS True > /tmp/right_hand.log 2>&1 &' |
|||
) |
|||
ssh.exec_command(right_hand_cmd, timeout=10) |
|||
print(" Right hand driver started (192.168.123.211)") |
|||
time.sleep(2) |
|||
|
|||
# Left hand (check reachability first) |
|||
_, o, _ = ssh.exec_command('ping -c 1 -W 1 192.168.123.210 2>&1 | grep "1 received"', timeout=5) |
|||
left_reachable = bool(o.read().decode().strip()) |
|||
if left_reachable: |
|||
left_hand_cmd = ( |
|||
f'{env_prefix} ' |
|||
f'cd {TELEOP_DIR} && ' |
|||
f'nohup {PYTHON} -m inspire_hand.ModbusDataHandler ' |
|||
f'--ip 192.168.123.210 > /tmp/left_hand.log 2>&1 &' |
|||
) |
|||
ssh.exec_command(left_hand_cmd, timeout=10) |
|||
print(" Left hand driver started (192.168.123.210)") |
|||
else: |
|||
print(" Left hand not reachable (skipped)") |
|||
|
|||
time.sleep(2) |
|||
|
|||
# ── Step 2: Launch teleop with webcam ── |
|||
print("\nStarting teleop with webcam...") |
|||
print("=" * 60) |
|||
|
|||
teleop_cmd = ( |
|||
f'{env_prefix} ' |
|||
f'cd {TELEOP_DIR} && ' |
|||
f'{PYTHON} teleop_hand_and_arm.py ' |
|||
f'--arm=G1_29 --ee=inspire_ftp --webcam 0 --webcam-res 720p' |
|||
) |
|||
|
|||
stdin, stdout, stderr = ssh.exec_command(teleop_cmd, timeout=300, get_pty=True) |
|||
|
|||
# Stream output in real-time |
|||
start_time = time.time() |
|||
while time.time() - start_time < 120: |
|||
if stdout.channel.recv_ready(): |
|||
data = stdout.channel.recv(4096).decode('utf-8', errors='replace') |
|||
print(data, end='') |
|||
if stderr.channel.recv_stderr_ready(): |
|||
data = stderr.channel.recv_stderr(4096).decode('utf-8', errors='replace') |
|||
print(f"[err] {data}", end='') |
|||
if stdout.channel.exit_status_ready(): |
|||
remaining = stdout.channel.recv(65536).decode('utf-8', errors='replace') |
|||
if remaining: |
|||
print(remaining, end='') |
|||
remaining_err = stderr.channel.recv_stderr(65536).decode('utf-8', errors='replace') |
|||
if remaining_err: |
|||
print(f"[err] {remaining_err}", end='') |
|||
exit_code = stdout.channel.recv_exit_status() |
|||
print(f"\n\nProcess exited with code {exit_code}") |
|||
break |
|||
time.sleep(0.1) |
|||
else: |
|||
print("\n\nTeleop is running! (2 min output window elapsed)") |
|||
print("Connect Quest 3 to: https://10.0.0.64:8012") |
|||
|
|||
ssh.close() |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue