#!/usr/bin/env bash # ============================================================================= # record.sh — run the recorder WITHOUT the web dashboard. # # The backup plan: it finds a working way to run g1_record_replay.py and uses it, # instead of you remembering which python has the Unitree SDK on which machine. # # ./record.sh doctor what's available here, what's missing # ./record.sh record --output wave --seconds 20 # ./record.sh replay --input wave --speed 0.5 # ./record.sh eth0 record --output wave # explicit interface (else auto) # # Any flag the script takes is passed straight through — this adds nothing to the # recorder, it only starts it. # # How it picks a runtime, best first: # 1. $RECORDER_PY — you said which python to use # 2. a python that can already import unitree_sdk2py (conda g1_env, system, …) # 3. ../vendor/unitree_sdk2_python on PYTHONPATH, if that makes it importable # 4. the inspire-hand container, which has the SDK baked in # Use --how to see which one it chose without running anything. # # Env: IFACE (default: eth0 on the robot, enp3s0 elsewhere) # ============================================================================= set -uo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" SCRIPT="$HERE/g1_record_replay.py" VENDOR="$(cd "$HERE/.." 2>/dev/null && pwd)/vendor/unitree_sdk2_python" CONTAINER="${CONTAINER:-inspire-hand}" [ "$(uname -m)" = "aarch64" ] && DEF_IFACE=eth0 || DEF_IFACE=enp3s0 IFACE="${IFACE:-$DEF_IFACE}" g=$'\033[32m'; r=$'\033[31m'; y=$'\033[33m'; d=$'\033[2m'; n=$'\033[0m' have_sdk() { # $1 = python, $2 = optional PYTHONPATH PYTHONPATH="${2:-}" "$1" -c 'import unitree_sdk2py' >/dev/null 2>&1 } # ---- pick a runtime --------------------------------------------------------- MODE=""; PY=""; PYPATH="" if [ -n "${RECORDER_PY:-}" ] && have_sdk "$RECORDER_PY"; then MODE=local; PY="$RECORDER_PY" else for cand in "${RECORDER_PY:-}" "$HOME/miniconda3/envs/g1_env/bin/python3" \ "$HOME/anaconda3/envs/g1_env/bin/python3" python3; do [ -n "$cand" ] || continue command -v "$cand" >/dev/null 2>&1 || [ -x "$cand" ] || continue if have_sdk "$cand"; then MODE=local; PY="$cand"; break; fi # The vendored SDK is pure python, but it still needs the compiled # cyclonedds bindings — so this only helps where those already exist. if [ -d "$VENDOR" ] && have_sdk "$cand" "$VENDOR"; then MODE=local; PY="$cand"; PYPATH="$VENDOR"; break fi done fi if [ -z "$MODE" ] && command -v docker >/dev/null 2>&1 \ && docker exec "$CONTAINER" true >/dev/null 2>&1; then MODE=docker fi describe() { case "$MODE" in local) printf ' %sruntime%s local python: %s%s\n' "$g" "$n" "$PY" \ "${PYPATH:+ (PYTHONPATH=$PYPATH)}" printf ' %stakes%s %s/DataG1\n' "$d" "$n" "$PWD" ;; docker) printf ' %sruntime%s the %s container (SDK baked in)\n' "$g" "$n" "$CONTAINER" printf ' %stakes%s /opt/recorder/DataG1 in the container = ~/hand_data/DataG1 on the host\n' "$d" "$n" ;; *) printf ' %sno runtime%s — no python here can import unitree_sdk2py, and the %s container is not running\n' \ "$r" "$n" "$CONTAINER" ;; esac } # ---- doctor ----------------------------------------------------------------- if [ "${1:-}" = "doctor" ] || [ "${1:-}" = "--how" ]; then printf '\nRecorder runtime\n'; describe printf '\nDetail\n' printf ' script %s\n' "$([ -f "$SCRIPT" ] && echo "$SCRIPT" || echo "MISSING")" printf ' vendored SDK %s\n' "$([ -d "$VENDOR" ] && echo "$VENDOR" || echo "not next to this folder")" printf ' home pose %s\n' "$([ -f "$HERE/arm_home.jsonl" ] && echo "arm_home.jsonl" || echo "MISSING — replay skips return-to-home")" printf ' interface %s\n' "$IFACE" for c in "$HOME/miniconda3/envs/g1_env/bin/python3" python3; do [ -x "$c" ] || command -v "$c" >/dev/null 2>&1 || continue if have_sdk "$c"; then s="${g}has unitree_sdk2py${n}"; else s="${y}no unitree_sdk2py${n}"; fi printf ' python %-46s %s\n' "$c" "$s" done if [ "$MODE" = "" ]; then printf '\n%sFix one of these%s\n' "$y" "$n" printf ' · conda activate g1_env (workstation — the env that has the SDK)\n' # Only suggest it where it exists: a standalone bundle has no start.sh. [ -f "$HERE/../start.sh" ] && \ printf ' · start the hand stack: ../start.sh (then this uses the container)\n' printf ' · install the bindings: pip install "cyclonedds==0.10.2" && pip install -e %s\n' "$VENDOR" exit 1 fi exit 0 fi [ -f "$SCRIPT" ] || { echo "${r}missing${n} $SCRIPT"; exit 1; } if [ -z "$MODE" ]; then printf '%sno way to run the recorder here%s — try: %s doctor\n' "$r" "$n" "$0" >&2 exit 1 fi # ---- interface: accept it as $1, or insert the default ---------------------- # The recorder takes the interface as its first positional arg. Typing it every # time is noise, so if the first argument is already an action we insert one. case "${1:-}" in record|replay) set -- "$IFACE" "$@" ;; esac describe >&2 printf '%s>> %s %s%s\n' "$d" "$(basename "$SCRIPT")" "$*" "$n" >&2 if [ "$MODE" = "docker" ]; then # -it: the in-take keys (o/c/[/]/;/' and 1-9) and the pause key need a TTY. exec docker exec -it -w /opt/recorder "$CONTAINER" python3 g1_record_replay.py "$@" fi cd "$PWD" || exit 1 # takes land in the CALLER's DataG1, not the script's exec env ${PYPATH:+PYTHONPATH="$PYPATH"} "$PY" "$SCRIPT" "$@"