Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions mini_bdx_runtime/mini_bdx_runtime/xbox_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from mini_bdx_runtime.buttons import Buttons


AXIS_DEAD_ZONE = 0.1

X_RANGE = [-0.15, 0.15]
Y_RANGE = [-0.2, 0.2]
YAW_RANGE = [-1.0, 1.0]
Expand Down Expand Up @@ -54,17 +56,22 @@ def get_commands(self):
right_trigger = self.last_right_trigger

l_x = -1 * self.p1.get_axis(0)
l_x = l_x if abs(l_x) > AXIS_DEAD_ZONE else 0

l_y = -1 * self.p1.get_axis(1)
l_y = l_y if abs(l_y) > AXIS_DEAD_ZONE else 0

r_x = -1 * self.p1.get_axis(2)
r_x = r_x if abs(r_x) > AXIS_DEAD_ZONE else 0

r_y = -1 * self.p1.get_axis(3)
r_y = r_y if abs(r_y) > AXIS_DEAD_ZONE else 0

right_trigger = np.around((self.p1.get_axis(4) + 1) / 2, 3)
left_trigger = np.around((self.p1.get_axis(5) + 1) / 2, 3)
right_trigger = right_trigger if right_trigger > AXIS_DEAD_ZONE else 0

if left_trigger < 0.1:
left_trigger = 0
if right_trigger < 0.1:
right_trigger = 0
left_trigger = np.around((self.p1.get_axis(5) + 1) / 2, 3)
left_trigger = left_trigger if left_trigger > AXIS_DEAD_ZONE else 0

if not self.head_control_mode:
lin_vel_y = l_x
Expand Down
133 changes: 133 additions & 0 deletions scripts/start_walk_after_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import argparse
import os
import subprocess
import sys
import time

import pygame
from mini_bdx_runtime.sounds import Sounds


HOME_DIR = os.path.expanduser("~")
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))


def wait_for_controller(poll_interval: float = 1.0):
pygame.init()
pygame.joystick.init()

print("Waiting for bluetooth controller to connect...")
while True:
pygame.event.pump()
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
print(f"Controller connected: {joystick.get_name()}")
return

time.sleep(poll_interval)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--onnx_model_path", type=str, required=True)
parser.add_argument(
"--duck_config_path",
type=str,
required=False,
default=f"{HOME_DIR}/duck_config.json",
)
parser.add_argument("--serial_port", type=str, default="/dev/ttyACM0")
parser.add_argument("-a", "--action_scale", type=float, default=0.25)
parser.add_argument("-p", type=int, default=30)
parser.add_argument("-i", type=int, default=0)
parser.add_argument("-d", type=int, default=0)
parser.add_argument("-c", "--control_freq", type=int, default=50)
parser.add_argument("--pitch_bias", type=float, default=0, help="deg")
parser.add_argument(
"--save_obs",
type=str,
required=False,
default=False,
help="save the run's observations",
)
parser.add_argument(
"--replay_obs",
type=str,
required=False,
default=None,
help="replay the observations from a previous run (can be from the robot or from mujoco)",
)
parser.add_argument("--cutoff_frequency", type=float, default=None)
parser.add_argument(
"--sound_path",
type=str,
default=os.path.join(
os.path.dirname(SCRIPT_DIR), "mini_bdx_runtime/assets/happy1.wav"
),
)
args = parser.parse_args()

os.chdir(SCRIPT_DIR)

turn_on_script = os.path.join(SCRIPT_DIR, "turn_on.py")
walk_script = os.path.join(SCRIPT_DIR, "v2_rl_walk_mujoco.py")

# Call the original turn_on script
subprocess.run(
[
sys.executable,
turn_on_script,
],
check=True,
cwd=SCRIPT_DIR,
)

# Play happy sound after turn on
if args.sound_path and os.path.exists(args.sound_path):
try:
pygame.mixer.init()
sound = pygame.mixer.Sound(args.sound_path)
sound.play()
except Exception as e:
print(f"Failed to play sound: {e}")

wait_for_controller()

# Build the walk command
walk_cmd = [
sys.executable,
walk_script,
"--onnx_model_path",
args.onnx_model_path,
"--duck_config_path",
args.duck_config_path,
"-a",
str(args.action_scale),
"-p",
str(args.p),
"-i",
str(args.i),
"-d",
str(args.d),
"-c",
str(args.control_freq),
"--pitch_bias",
str(args.pitch_bias),
]

if args.save_obs is not False:
walk_cmd.extend(["--save_obs", args.save_obs])

if args.replay_obs is not None:
walk_cmd.extend(["--replay_obs", args.replay_obs])

if args.cutoff_frequency is not None:
walk_cmd.extend(["--cutoff_frequency", str(args.cutoff_frequency)])

# Hand off execution to the walking script
os.execv(sys.executable, walk_cmd)


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions scripts/start_walk_after_controller.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash

# Setup Instructions for Raspberry Pi OS Lite (Auto-start on boot):
# 1. Make this script executable:
# chmod +x start_walk_after_controller.sh
# 2. Add to crontab:
# crontab -e
# Add the following line at the end:
# @reboot /bin/bash /home/pi/Open_Duck_Mini_Runtime/scripts/start_walk_after_controller.sh >> /home/pi/startup.log 2>&1
# 3. Ensure your ONNX model and duck_config.json are in the home directory as specified below.

set -euo pipefail

# Ensure HOME is defined (crontab sometimes has a limited environment)
export HOME="${HOME:-/home/duck0}"

# Initialize Conda for the current shell session
CONDA_PROFILE="$HOME/miniconda3/etc/profile.d/conda.sh"
if [ -f "$CONDA_PROFILE" ]; then
source "$CONDA_PROFILE"
conda activate base
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

cd "$SCRIPT_DIR"

exec python3 "$SCRIPT_DIR/start_walk_after_controller.py" \
--duck_config_path "$HOME/duck_config.json" \
--onnx_model_path "$HOME/BEST_WALK_ONNX_2.onnx"