#!/usr/bin/env python3 """ g1_arm_actions_cli.py Run Unitree G1 Arm ActionClient actions directly on the robot from CLI. Changes from your request: - Network interface is OPTIONAL (no longer required). If you pass it, it will use it; otherwise it uses default DDS interface. Usage: # list actions python3 g1_arm_actions_cli.py list # run by id python3 g1_arm_actions_cli.py 6 # run by name (quotes recommended) python3 g1_arm_actions_cli.py "face wave" # specify interface explicitly (optional) python3 g1_arm_actions_cli.py "shake hand" --iface enp3s0 # force auto release python3 g1_arm_actions_cli.py "hug" --auto-release --sleep 2 """ import argparse import sys import time from dataclasses import dataclass from typing import Optional from unitree_sdk2py.core.channel import ChannelFactoryInitialize from unitree_sdk2py.g1.arm.g1_arm_action_client import G1ArmActionClient, action_map @dataclass class TestOption: name: str id: int OPTION_LIST = [ TestOption(name="release arm", id=0), TestOption(name="shake hand", id=1), TestOption(name="high five", id=2), TestOption(name="hug", id=3), TestOption(name="high wave", id=4), TestOption(name="clap", id=5), TestOption(name="face wave", id=6), TestOption(name="left kiss", id=7), TestOption(name="heart", id=8), TestOption(name="right heart", id=9), TestOption(name="hands up", id=10), TestOption(name="x-ray", id=11), TestOption(name="right hand up", id=12), TestOption(name="reject", id=13), TestOption(name="right kiss", id=14), TestOption(name="two-hand kiss", id=15), ] # Actions that your original script auto-released after ~2s DEFAULT_AUTO_RELEASE_IDS = {1, 2, 3, 8, 9, 10, 11, 12, 13} def find_option(query: str) -> Optional[TestOption]: q = query.strip().lower() # try id try: as_int = int(q) for opt in OPTION_LIST: if opt.id == as_int: return opt except ValueError: pass # try name for opt in OPTION_LIST: if opt.name.lower() == q: return opt return None def print_list(): print("\nAvailable actions:") for opt in OPTION_LIST: print(f" {opt.id:>2} {opt.name}") print("") def execute_action(client: G1ArmActionClient, opt: TestOption): if opt.name not in action_map: raise RuntimeError(f"Action '{opt.name}' not found in action_map. Check SDK version.") print(f"[RUN] {opt.name} (id={opt.id})") client.ExecuteAction(action_map.get(opt.name)) def init_dds(iface: Optional[str]): # If iface provided, use it; otherwise default interface. if iface: ChannelFactoryInitialize(0, iface) else: ChannelFactoryInitialize(0) def main(): ap = argparse.ArgumentParser() ap.add_argument( "action", help='Action id/name, or "list". Examples: 6 | "face wave" | list', ) ap.add_argument("--iface", default=None, help="Optional network interface, e.g. enp3s0/eth0/wlan0") ap.add_argument("--timeout", type=float, default=10.0, help="Action client timeout (seconds)") ap.add_argument( "--auto-release", action="store_true", help="After action, run 'release arm' automatically (like your demo for some actions).", ) ap.add_argument( "--sleep", type=float, default=2.0, help="Seconds to wait before auto-release (only used with auto-release).", ) ap.add_argument( "--no-prompt", action="store_true", help="Skip the 'Press Enter to continue' safety prompt.", ) args = ap.parse_args() if args.action.strip().lower() == "list": print_list() return opt = find_option(args.action) if not opt: print(f"[ERR] No matching action for: {args.action!r}") print_list() sys.exit(2) print("WARNING: Please ensure there are no obstacles around the robot while running this script.") if not args.no_prompt: input("Press Enter to continue... ") # DDS init (optional iface) init_dds(args.iface) # Arm action client client = G1ArmActionClient() client.SetTimeout(args.timeout) client.Init() execute_action(client, opt) # Decide auto-release behavior do_auto_release = args.auto_release or (opt.id in DEFAULT_AUTO_RELEASE_IDS) if do_auto_release and opt.id != 0: time.sleep(max(0.0, args.sleep)) print("[AUTO] release arm") client.ExecuteAction(action_map.get("release arm")) time.sleep(0.5) print("[DONE]") if __name__ == "__main__": main()