G1_Lootah/Controller/g1_arm_actions_cli.py

173 lines
4.6 KiB
Python

#!/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()