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.

659 lines
35 KiB

import time
import argparse
import struct
import numpy as np
from multiprocessing import Value, Array, Lock
import threading
import logging_mp
logging_mp.basicConfig(level=logging_mp.INFO)
logger_mp = logging_mp.getLogger(__name__)
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
from unitree_sdk2py.core.channel import ChannelFactoryInitialize # dds
from televuer import TeleVuerWrapper
from teleop.robot_control.robot_arm import G1_29_ArmController, G1_23_ArmController, H1_2_ArmController, H1_ArmController
from teleop.robot_control.robot_arm_ik import G1_29_ArmIK, G1_23_ArmIK, H1_2_ArmIK, H1_ArmIK
from teleimager.image_client import ImageClient
from teleop.utils.episode_writer import EpisodeWriter
from teleop.utils.ipc import IPC_Server
from teleop.utils.motion_switcher import MotionSwitcher, LocoClientWrapper
from sshkeyboard import listen_keyboard, stop_listening
# for simulation
from unitree_sdk2py.core.channel import ChannelPublisher
from unitree_sdk2py.idl.std_msgs.msg.dds_ import String_
def publish_reset_category(category: int, publisher): # Scene Reset signal
msg = String_(data=str(category))
publisher.Write(msg)
logger_mp.info(f"published reset category: {category}")
# state transition
START = False # Enable to start robot following VR user motion
STOP = False # Enable to begin system exit procedure
READY = False # Ready to (1) enter START state, (2) enter RECORD_RUNNING state
RECORD_RUNNING = False # True if [Recording]
RECORD_TOGGLE = False # Toggle recording state
INTEGRAL_ENABLED = True # Toggle integral drift correction via 'i' key
RESET_ARMS = False # Trigger arm reset via 'h' key
# ------- --------- ----------- ----------- ---------
# state [Ready] ==> [Recording] ==> [AutoSave] --> [Ready]
# ------- --------- | ----------- | ----------- | ---------
# START True |manual True |manual True | True
# READY True |set False |set False |auto True
# RECORD_RUNNING False |to True |to False | False
# ∨ ∨ ∨
# RECORD_TOGGLE False True False True False False
# ------- --------- ----------- ----------- ---------
# ==> manual: when READY is True, set RECORD_TOGGLE=True to transition.
# --> auto : Auto-transition after saving data.
def parse_r3_buttons(data):
"""Parse R3 controller button bytes from wireless_remote. Returns dict of button states."""
if len(data) < 4:
return {}
btn1 = data[2] # R1=0x01, L1=0x02, Start=0x04, Select=0x08, R2=0x10, L2=0x20
btn2 = data[3] # A=0x01, B=0x02, X=0x04, Y=0x08, Up=0x10, Right=0x20, Down=0x40, Left=0x80
return {
'A': bool(btn2 & 0x01),
'B': bool(btn2 & 0x02),
'X': bool(btn2 & 0x04),
'Y': bool(btn2 & 0x08),
'Start': bool(btn1 & 0x04),
}
# Previous button state for edge detection
_r3_prev_buttons = {}
def on_press(key):
global STOP, START, RECORD_TOGGLE, INTEGRAL_ENABLED, RESET_ARMS
if key == 'r':
START = True
elif key == 'q':
START = False
STOP = True
elif key == 's' and START == True:
RECORD_TOGGLE = True
elif key == 'i':
INTEGRAL_ENABLED = not INTEGRAL_ENABLED
logger_mp.info(f"[on_press] Integral correction {'ENABLED' if INTEGRAL_ENABLED else 'DISABLED'}")
elif key == 'h' and START == True:
RESET_ARMS = True
logger_mp.info("[on_press] Arm reset requested (return to home)")
else:
logger_mp.warning(f"[on_press] {key} was pressed, but no action is defined for this key.")
def get_state() -> dict:
"""Return current heartbeat state"""
global START, STOP, RECORD_RUNNING, READY
return {
"START": START,
"STOP": STOP,
"READY": READY,
"RECORD_RUNNING": RECORD_RUNNING,
}
if __name__ == '__main__':
parser = argparse.ArgumentParser()
# basic control parameters
parser.add_argument('--frequency', type = float, default = 30.0, help = 'control and record \'s frequency')
parser.add_argument('--input-mode', type=str, choices=['hand', 'controller'], default='hand', help='Select XR device input tracking source')
parser.add_argument('--display-mode', type=str, choices=['immersive', 'ego', 'pass-through'], default='immersive', help='Select XR device display mode')
parser.add_argument('--arm', type=str, choices=['G1_29', 'G1_23', 'H1_2', 'H1'], default='G1_29', help='Select arm controller')
parser.add_argument('--ee', type=str, choices=['dex1', 'dex3', 'inspire_ftp', 'inspire_dfx', 'brainco'], help='Select end effector controller')
parser.add_argument('--img-server-ip', type=str, default='192.168.123.164', help='IP address of image server, used by teleimager and televuer')
parser.add_argument('--network-interface', type=str, default=None, help='Network interface for dds communication, e.g., eth0, wlan0. If None, use default interface.')
# mode flags
parser.add_argument('--motion', action = 'store_true', help = 'Enable motion control mode')
parser.add_argument('--headless', action='store_true', help='Enable headless mode (no display)')
parser.add_argument('--sim', action = 'store_true', help = 'Enable isaac simulation mode')
parser.add_argument('--ipc', action = 'store_true', help = 'Enable IPC server to handle input; otherwise enable sshkeyboard')
parser.add_argument('--affinity', action = 'store_true', help = 'Enable high priority and set CPU affinity mode')
# integral drift correction
parser.add_argument('--ki', type=float, default=0.3, help='Integral gain for drift correction (0.0 = disabled)')
parser.add_argument('--i-clamp', type=float, default=0.10, help='Anti-windup clamp for I term in meters (default: 0.10)')
parser.add_argument('--i-decay', type=float, default=0.995, help='Per-frame decay factor for I term (default: 0.995)')
# record mode and task info
parser.add_argument('--record', action = 'store_true', help = 'Enable data recording mode')
parser.add_argument('--task-dir', type = str, default = './utils/data/', help = 'path to save data')
parser.add_argument('--task-name', type = str, default = 'pick cube', help = 'task file name for recording')
parser.add_argument('--task-goal', type = str, default = 'pick up cube.', help = 'task goal for recording at json file')
parser.add_argument('--task-desc', type = str, default = 'task description', help = 'task description for recording at json file')
parser.add_argument('--task-steps', type = str, default = 'step1: do this; step2: do that;', help = 'task steps for recording at json file')
args = parser.parse_args()
logger_mp.info(f"args: {args}")
try:
# setup dds communication domains id
if args.sim:
ChannelFactoryInitialize(1, networkInterface=args.network_interface)
else:
ChannelFactoryInitialize(0, networkInterface=args.network_interface)
# ipc communication mode. client usage: see utils/ipc.py
if args.ipc:
ipc_server = IPC_Server(on_press=on_press,get_state=get_state)
ipc_server.start()
# sshkeyboard communication mode
else:
listen_keyboard_thread = threading.Thread(target=listen_keyboard,
kwargs={"on_press": on_press, "until": None, "sequential": False,},
daemon=True)
listen_keyboard_thread.start()
# image client
img_client = ImageClient(host=args.img_server_ip, request_bgr=True)
camera_config = img_client.get_cam_config()
logger_mp.debug(f"Camera config: {camera_config}")
xr_need_local_img = not (args.display_mode == 'pass-through' or camera_config['head_camera']['enable_webrtc'])
# televuer_wrapper: obtain hand pose data from the XR device and transmit the robot's head camera image to the XR device.
tv_wrapper = TeleVuerWrapper(use_hand_tracking=args.input_mode == "hand",
binocular=camera_config['head_camera']['binocular'],
img_shape=camera_config['head_camera']['image_shape'],
# maybe should decrease fps for better performance?
# https://github.com/unitreerobotics/xr_teleoperate/issues/172
# display_fps=camera_config['head_camera']['fps'] ? args.frequency? 30.0?
display_mode=args.display_mode,
zmq=camera_config['head_camera']['enable_zmq'],
webrtc=camera_config['head_camera']['enable_webrtc'],
webrtc_url=f"https://{args.img_server_ip}:{camera_config['head_camera']['webrtc_port']}/offer",
)
# motion mode (G1: Regular mode R1+X, not Running mode R2+A)
if args.motion:
if args.input_mode == "controller":
loco_wrapper = LocoClientWrapper()
else:
motion_switcher = MotionSwitcher()
status, result = motion_switcher.Enter_Debug_Mode()
logger_mp.info(f"Enter debug mode: {'Success' if status == 0 else 'Failed'}")
# arm
if args.arm == "G1_29":
arm_ik = G1_29_ArmIK()
arm_ctrl = G1_29_ArmController(motion_mode=args.motion, simulation_mode=args.sim)
elif args.arm == "G1_23":
arm_ik = G1_23_ArmIK()
arm_ctrl = G1_23_ArmController(motion_mode=args.motion, simulation_mode=args.sim)
elif args.arm == "H1_2":
arm_ik = H1_2_ArmIK()
arm_ctrl = H1_2_ArmController(motion_mode=args.motion, simulation_mode=args.sim)
elif args.arm == "H1":
arm_ik = H1_ArmIK()
arm_ctrl = H1_ArmController(simulation_mode=args.sim)
# end-effector
if args.ee == "dex3":
from teleop.robot_control.robot_hand_unitree import Dex3_1_Controller
left_hand_pos_array = Array('d', 75, lock = True) # [input]
right_hand_pos_array = Array('d', 75, lock = True) # [input]
dual_hand_data_lock = Lock()
dual_hand_state_array = Array('d', 14, lock = False) # [output] current left, right hand state(14) data.
dual_hand_action_array = Array('d', 14, lock = False) # [output] current left, right hand action(14) data.
hand_ctrl = Dex3_1_Controller(left_hand_pos_array, right_hand_pos_array, dual_hand_data_lock,
dual_hand_state_array, dual_hand_action_array, simulation_mode=args.sim)
elif args.ee == "dex1":
from teleop.robot_control.robot_hand_unitree import Dex1_1_Gripper_Controller
left_gripper_value = Value('d', 0.0, lock=True) # [input]
right_gripper_value = Value('d', 0.0, lock=True) # [input]
dual_gripper_data_lock = Lock()
dual_gripper_state_array = Array('d', 2, lock=False) # current left, right gripper state(2) data.
dual_gripper_action_array = Array('d', 2, lock=False) # current left, right gripper action(2) data.
gripper_ctrl = Dex1_1_Gripper_Controller(left_gripper_value, right_gripper_value, dual_gripper_data_lock,
dual_gripper_state_array, dual_gripper_action_array, simulation_mode=args.sim)
elif args.ee == "inspire_dfx":
from teleop.robot_control.robot_hand_inspire import Inspire_Controller_DFX
left_hand_pos_array = Array('d', 75, lock = True) # [input]
right_hand_pos_array = Array('d', 75, lock = True) # [input]
dual_hand_data_lock = Lock()
dual_hand_state_array = Array('d', 12, lock = False) # [output] current left, right hand state(12) data.
dual_hand_action_array = Array('d', 12, lock = False) # [output] current left, right hand action(12) data.
hand_ctrl = Inspire_Controller_DFX(left_hand_pos_array, right_hand_pos_array, dual_hand_data_lock, dual_hand_state_array, dual_hand_action_array, simulation_mode=args.sim)
elif args.ee == "inspire_ftp":
from teleop.robot_control.robot_hand_inspire import Inspire_Controller_FTP
left_hand_pos_array = Array('d', 75, lock = True) # [input]
right_hand_pos_array = Array('d', 75, lock = True) # [input]
dual_hand_data_lock = Lock()
dual_hand_state_array = Array('d', 12, lock = False) # [output] current left, right hand state(12) data.
dual_hand_action_array = Array('d', 12, lock = False) # [output] current left, right hand action(12) data.
hand_ctrl = Inspire_Controller_FTP(left_hand_pos_array, right_hand_pos_array, dual_hand_data_lock, dual_hand_state_array, dual_hand_action_array, simulation_mode=args.sim)
elif args.ee == "brainco":
from teleop.robot_control.robot_hand_brainco import Brainco_Controller
left_hand_pos_array = Array('d', 75, lock = True) # [input]
right_hand_pos_array = Array('d', 75, lock = True) # [input]
dual_hand_data_lock = Lock()
dual_hand_state_array = Array('d', 12, lock = False) # [output] current left, right hand state(12) data.
dual_hand_action_array = Array('d', 12, lock = False) # [output] current left, right hand action(12) data.
hand_ctrl = Brainco_Controller(left_hand_pos_array, right_hand_pos_array, dual_hand_data_lock,
dual_hand_state_array, dual_hand_action_array, simulation_mode=args.sim)
else:
pass
# affinity mode (if you dont know what it is, then you probably don't need it)
if args.affinity:
import psutil
p = psutil.Process(os.getpid())
p.cpu_affinity([0,1,2,3]) # Set CPU affinity to cores 0-3
try:
p.nice(-20) # Set highest priority
logger_mp.info("Set high priority successfully.")
except psutil.AccessDenied:
logger_mp.warning("Failed to set high priority. Please run as root.")
for child in p.children(recursive=True):
try:
logger_mp.info(f"Child process {child.pid} name: {child.name()}")
child.cpu_affinity([5,6])
child.nice(-20)
except psutil.AccessDenied:
pass
# simulation mode
if args.sim:
reset_pose_publisher = ChannelPublisher("rt/reset_pose/cmd", String_)
reset_pose_publisher.Init()
from teleop.utils.sim_state_topic import start_sim_state_subscribe
sim_state_subscriber = start_sim_state_subscribe()
# record + headless / non-headless mode
if args.record:
recorder = EpisodeWriter(task_dir = os.path.join(args.task_dir, args.task_name),
task_goal = args.task_goal,
task_desc = args.task_desc,
task_steps = args.task_steps,
frequency = args.frequency,
rerun_log = not args.headless)
logger_mp.info("----------------------------------------------------------------")
logger_mp.info(" Keyboard | R3 Controller | Action")
logger_mp.info(" --------- --------------- ------")
logger_mp.info(" [r] | [A] | Start arm tracking")
logger_mp.info(" [q] | [B] | Stop and exit")
logger_mp.info(" [h] | [X] | Reset arms to home")
logger_mp.info(" [i] | [Y] | Toggle I-term correction")
if args.record:
logger_mp.info(" [s] | [Start] | Toggle recording")
if args.ki > 0.0:
logger_mp.info(f" I-term: Ki={args.ki}, clamp={args.i_clamp}m, decay={args.i_decay}")
logger_mp.info("----------------------------------------------------------------")
logger_mp.info("⚠️ IMPORTANT: Please keep your distance and stay safe.")
READY = True # now ready to (1) enter START state
while not START and not STOP: # wait for start or stop signal.
time.sleep(0.033)
# Poll R3 A button in wait loop (edge-detected)
r3_data = arm_ctrl.get_wireless_remote()
r3_btns = parse_r3_buttons(r3_data)
if r3_btns.get('A') and not _r3_prev_buttons.get('A'):
START = True
logger_mp.info("[R3] A pressed → START tracking")
_r3_prev_buttons = r3_btns
if camera_config['head_camera']['enable_zmq'] and xr_need_local_img:
head_img = img_client.get_head_frame()
tv_wrapper.render_to_xr(head_img)
logger_mp.info("---------------------🚀start Tracking🚀-------------------------")
arm_ctrl.speed_gradual_max()
# Integral drift correction state
i_error_left = np.zeros(3) # Accumulated task-space position error (meters)
i_error_right = np.zeros(3)
last_loop_time = time.time()
i_frame_count = 0
# main loop. robot start to follow VR user's motion
while not STOP:
start_time = time.time()
# handle arm reset request
if RESET_ARMS:
RESET_ARMS = False
logger_mp.info("[main] Resetting arms to home position...")
i_error_left = np.zeros(3)
i_error_right = np.zeros(3)
arm_ctrl.ctrl_dual_arm(np.zeros(14), np.zeros(14))
reset_start = time.time()
while time.time() - reset_start < 3.0:
if np.all(np.abs(arm_ctrl.get_current_dual_arm_q()) < 0.1):
break
time.sleep(0.05)
logger_mp.info("[main] Arms reset complete, resuming tracking")
arm_ctrl.speed_gradual_max()
last_loop_time = time.time()
continue
# Poll R3 controller buttons (edge-detected)
r3_data = arm_ctrl.get_wireless_remote()
r3_btns = parse_r3_buttons(r3_data)
if r3_btns.get('A') and not _r3_prev_buttons.get('A'):
if not START:
START = True
logger_mp.info("[R3] A pressed → START tracking")
if r3_btns.get('B') and not _r3_prev_buttons.get('B'):
START = False
STOP = True
logger_mp.info("[R3] B pressed → STOP")
if r3_btns.get('X') and not _r3_prev_buttons.get('X') and START:
RESET_ARMS = True
logger_mp.info("[R3] X pressed → Reset arms")
if r3_btns.get('Y') and not _r3_prev_buttons.get('Y'):
INTEGRAL_ENABLED = not INTEGRAL_ENABLED
logger_mp.info(f"[R3] Y pressed → Integral {'ENABLED' if INTEGRAL_ENABLED else 'DISABLED'}")
if r3_btns.get('Start') and not _r3_prev_buttons.get('Start') and START:
RECORD_TOGGLE = True
logger_mp.info("[R3] Start pressed → Toggle recording")
_r3_prev_buttons = r3_btns
# get image
if camera_config['head_camera']['enable_zmq']:
if args.record or xr_need_local_img:
head_img = img_client.get_head_frame()
if xr_need_local_img:
tv_wrapper.render_to_xr(head_img)
if camera_config['left_wrist_camera']['enable_zmq']:
if args.record:
left_wrist_img = img_client.get_left_wrist_frame()
if camera_config['right_wrist_camera']['enable_zmq']:
if args.record:
right_wrist_img = img_client.get_right_wrist_frame()
# record mode
if args.record and RECORD_TOGGLE:
RECORD_TOGGLE = False
if not RECORD_RUNNING:
if recorder.create_episode():
RECORD_RUNNING = True
else:
logger_mp.error("Failed to create episode. Recording not started.")
else:
RECORD_RUNNING = False
recorder.save_episode()
if args.sim:
publish_reset_category(1, reset_pose_publisher)
# get xr's tele data
tele_data = tv_wrapper.get_tele_data()
if (args.ee == "dex3" or args.ee == "inspire_dfx" or args.ee == "inspire_ftp" or args.ee == "brainco") and args.input_mode == "hand":
with left_hand_pos_array.get_lock():
left_hand_pos_array[:] = tele_data.left_hand_pos.flatten()
with right_hand_pos_array.get_lock():
right_hand_pos_array[:] = tele_data.right_hand_pos.flatten()
elif args.ee == "dex1" and args.input_mode == "controller":
with left_gripper_value.get_lock():
left_gripper_value.value = tele_data.left_ctrl_triggerValue
with right_gripper_value.get_lock():
right_gripper_value.value = tele_data.right_ctrl_triggerValue
elif args.ee == "dex1" and args.input_mode == "hand":
with left_gripper_value.get_lock():
left_gripper_value.value = tele_data.left_hand_pinchValue
with right_gripper_value.get_lock():
right_gripper_value.value = tele_data.right_hand_pinchValue
else:
pass
# high level control
if args.input_mode == "controller" and args.motion:
# quit teleoperate
if tele_data.right_ctrl_aButton:
START = False
STOP = True
# command robot to enter damping mode. soft emergency stop function
if tele_data.left_ctrl_thumbstick and tele_data.right_ctrl_thumbstick:
loco_wrapper.Damp()
# https://github.com/unitreerobotics/xr_teleoperate/issues/135, control, limit velocity to within 0.3
loco_wrapper.Move(-tele_data.left_ctrl_thumbstickValue[1] * 0.3,
-tele_data.left_ctrl_thumbstickValue[0] * 0.3,
-tele_data.right_ctrl_thumbstickValue[0]* 0.3)
# get current robot state data.
current_lr_arm_q = arm_ctrl.get_current_dual_arm_q()
current_lr_arm_dq = arm_ctrl.get_current_dual_arm_dq()
# --- Integral drift correction ---
now = time.time()
dt = min(now - last_loop_time, 0.1) # clamp to prevent huge jumps after stalls
last_loop_time = now
left_wrist_adjusted = tele_data.left_wrist_pose.copy()
right_wrist_adjusted = tele_data.right_wrist_pose.copy()
if args.ki > 0.0 and INTEGRAL_ENABLED:
# FK: where are the robot hands actually?
left_ee_pos, right_ee_pos = arm_ik.compute_fk(current_lr_arm_q)
# Error: target - actual (task-space position)
left_error = tele_data.left_wrist_pose[:3, 3] - left_ee_pos
right_error = tele_data.right_wrist_pose[:3, 3] - right_ee_pos
# Leaky integrator with anti-windup
i_error_left = i_error_left * args.i_decay + left_error * dt
i_error_right = i_error_right * args.i_decay + right_error * dt
i_error_left = np.clip(i_error_left, -args.i_clamp, args.i_clamp)
i_error_right = np.clip(i_error_right, -args.i_clamp, args.i_clamp)
# Bias IK target position (rotation unchanged)
left_wrist_adjusted[:3, 3] += args.ki * i_error_left
right_wrist_adjusted[:3, 3] += args.ki * i_error_right
# Log every ~1 second (every 30 frames at 30Hz)
i_frame_count += 1
if i_frame_count % 30 == 0:
logger_mp.info(
f"[I-term] L_err={np.linalg.norm(left_error):.4f}m "
f"R_err={np.linalg.norm(right_error):.4f}m | "
f"L_I={np.linalg.norm(i_error_left):.4f} "
f"R_I={np.linalg.norm(i_error_right):.4f} | "
f"L_corr={np.linalg.norm(args.ki * i_error_left):.4f}m "
f"R_corr={np.linalg.norm(args.ki * i_error_right):.4f}m"
)
# solve ik using motor data and adjusted wrist pose
time_ik_start = time.time()
sol_q, sol_tauff = arm_ik.solve_ik(left_wrist_adjusted, right_wrist_adjusted, current_lr_arm_q, current_lr_arm_dq)
time_ik_end = time.time()
logger_mp.debug(f"ik:\t{round(time_ik_end - time_ik_start, 6)}")
arm_ctrl.ctrl_dual_arm(sol_q, sol_tauff)
# record data
if args.record:
READY = recorder.is_ready() # now ready to (2) enter RECORD_RUNNING state
# dex hand or gripper
if args.ee == "dex3" and args.input_mode == "hand":
with dual_hand_data_lock:
left_ee_state = dual_hand_state_array[:7]
right_ee_state = dual_hand_state_array[-7:]
left_hand_action = dual_hand_action_array[:7]
right_hand_action = dual_hand_action_array[-7:]
current_body_state = []
current_body_action = []
elif args.ee == "dex1" and args.input_mode == "hand":
with dual_gripper_data_lock:
left_ee_state = [dual_gripper_state_array[0]]
right_ee_state = [dual_gripper_state_array[1]]
left_hand_action = [dual_gripper_action_array[0]]
right_hand_action = [dual_gripper_action_array[1]]
current_body_state = []
current_body_action = []
elif args.ee == "dex1" and args.input_mode == "controller":
with dual_gripper_data_lock:
left_ee_state = [dual_gripper_state_array[0]]
right_ee_state = [dual_gripper_state_array[1]]
left_hand_action = [dual_gripper_action_array[0]]
right_hand_action = [dual_gripper_action_array[1]]
current_body_state = arm_ctrl.get_current_motor_q().tolist()
current_body_action = [-tele_data.left_ctrl_thumbstickValue[1] * 0.3,
-tele_data.left_ctrl_thumbstickValue[0] * 0.3,
-tele_data.right_ctrl_thumbstickValue[0] * 0.3]
elif (args.ee == "inspire_dfx" or args.ee == "inspire_ftp" or args.ee == "brainco") and args.input_mode == "hand":
with dual_hand_data_lock:
left_ee_state = dual_hand_state_array[:6]
right_ee_state = dual_hand_state_array[-6:]
left_hand_action = dual_hand_action_array[:6]
right_hand_action = dual_hand_action_array[-6:]
current_body_state = []
current_body_action = []
else:
left_ee_state = []
right_ee_state = []
left_hand_action = []
right_hand_action = []
current_body_state = []
current_body_action = []
# arm state and action
left_arm_state = current_lr_arm_q[:7]
right_arm_state = current_lr_arm_q[-7:]
left_arm_action = sol_q[:7]
right_arm_action = sol_q[-7:]
if RECORD_RUNNING:
colors = {}
depths = {}
if camera_config['head_camera']['binocular']:
if head_img is not None:
colors[f"color_{0}"] = head_img.bgr[:, :camera_config['head_camera']['image_shape'][1]//2]
colors[f"color_{1}"] = head_img.bgr[:, camera_config['head_camera']['image_shape'][1]//2:]
else:
logger_mp.warning("Head image is None!")
if camera_config['left_wrist_camera']['enable_zmq']:
if left_wrist_img is not None:
colors[f"color_{2}"] = left_wrist_img.bgr
else:
logger_mp.warning("Left wrist image is None!")
if camera_config['right_wrist_camera']['enable_zmq']:
if right_wrist_img is not None:
colors[f"color_{3}"] = right_wrist_img.bgr
else:
logger_mp.warning("Right wrist image is None!")
else:
if head_img is not None:
colors[f"color_{0}"] = head_img
else:
logger_mp.warning("Head image is None!")
if camera_config['left_wrist_camera']['enable_zmq']:
if left_wrist_img is not None:
colors[f"color_{1}"] = left_wrist_img.bgr
else:
logger_mp.warning("Left wrist image is None!")
if camera_config['right_wrist_camera']['enable_zmq']:
if right_wrist_img is not None:
colors[f"color_{2}"] = right_wrist_img.bgr
else:
logger_mp.warning("Right wrist image is None!")
states = {
"left_arm": {
"qpos": left_arm_state.tolist(), # numpy.array -> list
"qvel": [],
"torque": [],
},
"right_arm": {
"qpos": right_arm_state.tolist(),
"qvel": [],
"torque": [],
},
"left_ee": {
"qpos": left_ee_state,
"qvel": [],
"torque": [],
},
"right_ee": {
"qpos": right_ee_state,
"qvel": [],
"torque": [],
},
"body": {
"qpos": current_body_state,
},
}
actions = {
"left_arm": {
"qpos": left_arm_action.tolist(),
"qvel": [],
"torque": [],
},
"right_arm": {
"qpos": right_arm_action.tolist(),
"qvel": [],
"torque": [],
},
"left_ee": {
"qpos": left_hand_action,
"qvel": [],
"torque": [],
},
"right_ee": {
"qpos": right_hand_action,
"qvel": [],
"torque": [],
},
"body": {
"qpos": current_body_action,
},
}
if args.sim:
sim_state = sim_state_subscriber.read_data()
recorder.add_item(colors=colors, depths=depths, states=states, actions=actions, sim_state=sim_state)
else:
recorder.add_item(colors=colors, depths=depths, states=states, actions=actions)
current_time = time.time()
time_elapsed = current_time - start_time
sleep_time = max(0, (1 / args.frequency) - time_elapsed)
time.sleep(sleep_time)
logger_mp.debug(f"main process sleep: {sleep_time}")
except KeyboardInterrupt:
logger_mp.info("⛔ KeyboardInterrupt, exiting program...")
except Exception:
import traceback
logger_mp.error(traceback.format_exc())
finally:
try:
arm_ctrl.ctrl_dual_arm_go_home()
except Exception as e:
logger_mp.error(f"Failed to ctrl_dual_arm_go_home: {e}")
try:
if args.ipc:
ipc_server.stop()
else:
stop_listening()
listen_keyboard_thread.join()
except Exception as e:
logger_mp.error(f"Failed to stop keyboard listener or ipc server: {e}")
try:
img_client.close()
except Exception as e:
logger_mp.error(f"Failed to close image client: {e}")
try:
tv_wrapper.close()
except Exception as e:
logger_mp.error(f"Failed to close televuer wrapper: {e}")
try:
if not args.motion:
pass
# status, result = motion_switcher.Exit_Debug_Mode()
# logger_mp.info(f"Exit debug mode: {'Success' if status == 3104 else 'Failed'}")
except Exception as e:
logger_mp.error(f"Failed to exit debug mode: {e}")
try:
if args.sim:
sim_state_subscriber.stop_subscribe()
except Exception as e:
logger_mp.error(f"Failed to stop sim state subscriber: {e}")
try:
if args.record:
recorder.close()
except Exception as e:
logger_mp.error(f"Failed to close recorder: {e}")
logger_mp.info("✅ Finally, exiting program.")
exit(0)