Merge branch 'feature/reorganized_repo' into improve_walk

This commit is contained in:
apirrone 2025-08-16 19:04:39 +02:00
commit ed63159bd8
56 changed files with 426 additions and 363 deletions

5
.gitignore vendored
View file

@ -1,2 +1,5 @@
__pycache__/
mini_bdx_runtime.egg-info/
open_duck_mini_runtime.egg-info/
build
dist
.DS_Store

233
README.md
View file

@ -1,40 +1,65 @@
# Open Duck Mini Runtime
## Raspberry Pi zero 2W setup
This repository contains the runtime software for the Open Duck Mini, a small, open-source robotic duck. This guide will walk you through setting up the hardware and software to get your duck waddling.
## Table of Contents
- [Open Duck Mini Runtime](#open-duck-mini-runtime)
- [Table of Contents](#table-of-contents)
- [Raspberry Pi Setup](#raspberry-pi-setup)
- [Install Raspberry Pi OS](#install-raspberry-pi-os)
- [Setup SSH (If not setup during the installation)](#setup-ssh-if-not-setup-during-the-installation)
- [System Updates and Dependencies](#system-updates-and-dependencies)
- [Enable I2C](#enable-i2c)
- [Set the USB Serial Latency Timer](#set-the-usb-serial-latency-timer)
- [Motor Control Board udev Rules](#motor-control-board-udev-rules)
- [Install the Runtime](#install-the-runtime)
- [Make a Virtual Environment and Activate it](#make-a-virtual-environment-and-activate-it)
- [Install the Repository](#install-the-repository)
- [Xbox One Controller Setup](#xbox-one-controller-setup)
- [Hardware Configuration](#hardware-configuration)
- [Speaker Wiring and Configuration](#speaker-wiring-and-configuration)
- [Testing and Calibration](#testing-and-calibration)
- [Test the IMU](#test-the-imu)
- [Make your duck\_config.json](#make-your-duck_configjson)
- [Find the Joints Offsets](#find-the-joints-offsets)
- [Run the walk !](#run-the-walk-)
- [Controls](#controls)
## Raspberry Pi Setup
These instructions are for setting up a Raspberry Pi Zero 2W.
### Install Raspberry Pi OS
Download Raspberry Pi OS Lite (64-bit) from here : https://www.raspberrypi.com/software/operating-systems/
1. Download [Raspberry Pi OS Lite (64-bit)](https://www.raspberrypi.com/software/operating-systems/).
2. Follow the official instructions to install the OS on an SD card: [Getting Started Guide](https://www.raspberrypi.com/documentation/computers/getting-started.html).
3. Using the Raspberry Pi Imager, you can pre-configure your user, Wi-Fi, and SSH settings.
Follow the instructions here to install the OS on the SD card : https://www.raspberrypi.com/documentation/computers/getting-started.html
With the Raspberry Pi Imager, you can pre-configure session, wifi and ssh. Do it like below :
![imager_setup](https://github.com/user-attachments/assets/7a4987b2-de83-41dd-ab7f-585259685f16)
> Tip: I configure the rasp to connect to my phone's hotspot, this way I can connect to it from anywhere.
![imager_setup](https://github.com/user-attachments/assets/7a4987b2-de83-41dd-ab7f-585259685f16)
> **Tip:** Configure the Raspberry Pi to connect to your phone's hotspot for easy access anywhere.
### Setup SSH (If not setup during the installation)
When first booting on the rasp, you will need to connect a screen and a keyboard. The first thing you should do is connect to a wifi network and enable SSH.
If you didn't enable SSH during the OS installation, you'll need a screen and keyboard for the initial boot.
To do so, you can follow this guide : https://www.raspberrypi.com/documentation/computers/configuration.html#setting-up-wifi
1. Connect to a Wi-Fi network.
2. Enable SSH using this guide: [Raspberry Pi Configuration](https://www.raspberrypi.com/documentation/computers/configuration.html#setting-up-wifi).
Then, you can connect to your rasp using SSH without having to plug a screen and a keyboard.
Once SSH is enabled, you can connect to your Raspberry Pi remotely.
### Update the system and install necessary stuff
### System Updates and Dependencies
Update your system and install the required packages:
```bash
sudo apt update
sudo apt upgrade
sudo apt install git
sudo apt install python3-pip
sudo apt install python3-virtualenvwrapper
(optional) sudo apt install python3-picamzero
sudo apt install git python3-pip python3-virtualenvwrapper
# Optional for camera support
sudo apt install python3-picamzero
```
Add this to the end of the `.bashrc`:
Add the following lines to the end of your `.bashrc` file to configure the virtual environment wrapper:
```bash
export WORKON_HOME=$HOME/.virtualenvs
@ -44,132 +69,126 @@ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh
### Enable I2C
Use the Raspberry Pi configuration tool to enable I2C:
`sudo raspi-config` -> `Interface Options` -> `I2C`
TODO set 400KHz ?
*(TODO: Set to 400KHz?)*
### Set the usbserial latency timer
### Set the USB Serial Latency Timer
Create a udev rule to set the latency timer for the USB-to-serial adapter:
```bash
cd /etc/udev/rules.d/
sudo touch 99-usb-serial.rules
sudo nano 99-usb-serial.rules
# copy the following line in the file
sudo nano /etc/udev/rules.d/99-usb-serial.rules
```
Add the following line to the file:
```
SUBSYSTEM=="usb-serial", DRIVER=="ftdi_sio", ATTR{latency_timer}="1"
```
### Set the udev rules for the motor control board
### Motor Control Board udev Rules
TODO
*(TODO)*
## Install the Runtime
### Setup xbox one controller over bluetooth
Turn your xbox one controller on and set it in pairing mode by long pressing the sync button on the top of the controller.
Run the following commands on the rasp :
```bash
bluetoothctl
scan on
```
Wait for the controller to appear in the list, then run :
```bash
pair <controller_mac_address>
trust <controller_mac_address>
connect <controller_mac_address>
```
The led on the controller should stop blinking and stay on.
You can test that it's working by running
```bash
python3 mini_bdx_runtime/mini_bdx_runtime/xbox_controller.py
```
## Speaker wiring and configuration
Follow this tutorial
> For now, don't activate `/dev/zero` when they ask
https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp?view=all
## Install the runtime
### Make a virtual environment and activate it
### Make a Virtual Environment and Activate it
```bash
mkvirtualenv -p python3 open-duck-mini-runtime
workon open-duck-mini-runtime
```
Clone this repository on your rasp, cd into the repo, then :
### Install the Repository
Clone the repository and install it in editable mode:
```bash
git clone https://github.com/apirrone/Open_Duck_Mini_Runtime
cd Open_Duck_Mini_Runtime
git checkout v2
pip install -e .
```
## Test the IMU
**For Raspberry Pi 5:** You may need to replace the GPIO library.
```bash
python3 mini_bdx_runtime/mini_bdx_runtime/raw_imu.py
pip uninstall -y RPi.GPIO
pip install lgpio
```
You can also run `python3 scripts/imu_server.py` on the robot and `python3 scripts/imu_client.py --ip <robot_ip>` on your computer to check that the frame is oriented correctly.
> To find the ip address of the robot, run `ifconfig` on the robot
### Xbox One Controller Setup
## Test motors
1. Turn on your Xbox One controller and put it in pairing mode by long-pressing the sync button.
2. On your Raspberry Pi, run the following commands:
```bash
bluetoothctl
scan on
```
3. Wait for the controller to appear, then pair, trust, and connect to it:
```bash
pair <controller_mac_address>
trust <controller_mac_address>
connect <controller_mac_address>
```
The controller's LED should stop blinking.
This will allow you to verify all your motors are connected and configured.
4. Test the connection:
```bash
python3 src/open_duck_mini_runtime/xbox_controller.py
```
## Hardware Configuration
### Speaker Wiring and Configuration
Follow this Adafruit tutorial for wiring the speaker: [Adafruit MAX98357 I2S Class-D Mono Amp](https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp?view=all).
> **Note:** For now, do not activate `/dev/zero` when prompted in the tutorial.
## Testing and Calibration
### Test the IMU
Run a basic test to ensure the IMU is working:
```bash
python3 scripts/check_motors.py
python3 src/open_duck_mini_runtime/raw_imu.py
```
## Make your duck_config.json
Copy `example_config.json` in the home directory of your duck and rename it `duck_config.json`.
`cp example_config.json ~/duck_config.json`
In this file, you can configure some stuff, like registering if you installed the expression features, installed the imu upside down or and other stuff. You also write the joints offsets of your duck here
## Find the joints offsets
This script will guide you through finding the joints offsets of your robot that you can then write in your `duck_config.json`
> This procedure won't be necessary in the future as we will be flashing the offsets directly in each motor's eeprom.
To visualize the IMU data, run the server on the robot and the client on your computer:
```bash
cd scripts/
python find_soft_offsets.py
# On the robot
python3 dev/hardware/imu_server.py
# On your computer
python3 dev/hardware/imu_client.py --ip <robot_ip>
```
> Use `ifconfig` on the robot to find its IP address.
### Make your duck_config.json
Copy the example configuration file to your home directory and rename it:
```bash
cp example_config.json ~/duck_config.json
```
This file allows you to configure features like expressions, IMU orientation, and joint offsets.
### Find the Joints Offsets
This script helps you find the correct joint offsets for your robot. The offsets should be added to your `duck_config.json` file.
```bash
python3 tools/find_soft_offsets.py
```
> **Note:** This step will be replaced in the future by flashing offsets directly to each motor's EEPROM.
## Run the walk !
Download the [latest policy checkpoint ](https://github.com/apirrone/Open_Duck_Mini/blob/v2/BEST_WALK_ONNX_2.onnx) and copy it to your duck.
1. Download the [latest policy checkpoint](https://github.com/apirrone/Open_Duck_Mini/blob/v2/BEST_WALK_ONNX_2.onnx).
2. Copy the checkpoint file to your duck's home directory.
3. Run the walk by just runnning `walk`. (run `walk -h` to see the possible command line arguments):
`cd scripts/`
## Controls
`python v2_rl_walk_mujoco.py --onnx_model_path <path_to>/BEST_WALK_ONNX_2.onnx`
```
- The commands are :
- A to pause/unpause
- X to turn on/off the projector
- B to play a random sound
- Y to turn on/off head control (very experimental, I don't recommend trying that, it can break your duck's head)
- left and right triggers to control the left and right antennas
- LB (new!) press and hold to increase the walking frequency, kind of a sprint mode 🙂
```
- **A**: Pause/Unpause
- **X**: Turn on/off the projector
- **B**: Play a random sound
- **Y**: Turn on/off head control (experimental, use with caution)
- **Left/Right Triggers**: Control the left and right antennas
- **LB (Hold)**: Increase walking frequency (sprint mode)

View file

@ -1,3 +0,0 @@
- [] Better handle xbox controller
- It's a little bit of a mess right now, how we handle directions and buttons etc
- [] Make the offsets flashing work. This will be in the motor configuration script

View file

@ -4,22 +4,28 @@ Sets up the robot in init position, you control the head with the xbox controlle
import time
import numpy as np
from mini_bdx_runtime.rustypot_position_hwi import HWI
from mini_bdx_runtime.duck_config import DuckConfig
from mini_bdx_runtime.xbox_controller import XBoxController
from open_duck_mini_runtime.hwi import HWI
from open_duck_mini_runtime.duck_config import DuckConfig
from open_duck_mini_runtime.xbox_controller import XBoxController
from mini_bdx_runtime.eyes import Eyes
from mini_bdx_runtime.sounds import Sounds
from mini_bdx_runtime.antennas import Antennas
from mini_bdx_runtime.projector import Projector
from open_duck_mini_runtime.eyes import Eyes
from open_duck_mini_runtime.sounds import Sounds
from open_duck_mini_runtime.antennas import Antennas
from open_duck_mini_runtime.projector import Projector
from importlib.resources import files
import open_duck_mini_runtime
ASSETS_ROOT_PATH: str = str(files(open_duck_mini_runtime).joinpath("assets/"))
duck_config = DuckConfig()
xbox_controller = XBoxController(50, only_head_control=True)
if duck_config.speaker:
sounds = Sounds(volume=1.0, sound_directory="../mini_bdx_runtime/assets/")
sounds = Sounds(volume=1.0, sound_directory=ASSETS_ROOT_PATH)
if duck_config.antennas:
antennas = Antennas()
if duck_config.eyes:
@ -45,7 +51,6 @@ limits = {
try:
while True:
last_commands, buttons, left_trigger, right_trigger = (
xbox_controller.get_last_command()
)

View file

@ -1,7 +1,7 @@
import socket
import time
import pickle
from mini_bdx_runtime.imu import Imu
from open_duck_mini_runtime.imu import Imu
from threading import Thread
import time

View file

@ -1 +0,0 @@

View file

@ -1,74 +0,0 @@
import RPi.GPIO as GPIO
import numpy as np
import time
LEFT_ANTENNA_PIN = 13
RIGHT_ANTENNA_PIN = 12
LEFT_SIGN = 1
RIGHT_SIGN = -1
class Antennas:
def __init__(self):
GPIO.setmode(GPIO.BCM)
GPIO.setup(LEFT_ANTENNA_PIN, GPIO.OUT)
GPIO.setup(RIGHT_ANTENNA_PIN, GPIO.OUT)
self.pwm1 = GPIO.PWM(LEFT_ANTENNA_PIN, 50)
self.pwm2 = GPIO.PWM(RIGHT_ANTENNA_PIN, 50)
self.pwm1.start(0)
self.pwm2.start(0)
def map_input_to_angle(self, value):
return 90 + (value * 90)
def set_position_left(self, position):
self.set_position(1, position, LEFT_SIGN)
def set_position_right(self, position):
self.set_position(2, position, RIGHT_SIGN)
def set_position(self, servo, value, sign=1):
"""
Moves the servo based on an input value in the range [-1, 1].
:param servo: 1 for the first servo, 2 for the second servo
:param value: A float between -1 and 1
"""
# if value == 0:
# return
if -1 <= value <= 1:
angle = self.map_input_to_angle(value * sign)
duty = 2 + (angle / 18) # Convert angle to duty cycle (1ms-2ms)
if servo == 1:
self.pwm1.ChangeDutyCycle(duty)
elif servo == 2:
self.pwm2.ChangeDutyCycle(duty)
else:
print("Invalid servo number!")
# time.sleep(0.01) # Allow time for movement
else:
print("Invalid input! Enter a value between -1 and 1.")
def stop(self):
self.pwm1.stop()
self.pwm2.stop()
GPIO.cleanup()
if __name__ == "__main__":
antennas = Antennas()
s = time.time()
while True:
antennas.set_position_left(np.sin(2 * np.pi * 1 * time.time()))
antennas.set_position_right(np.sin(2 * np.pi * 1 * time.time()))
time.sleep(1 / 50)
if time.time() - s > 5:
break

View file

@ -1,40 +0,0 @@
import RPi.GPIO as GPIO
import numpy as np
import time
from threading import Thread
LEFT_EYE_GPIO = 24
RIGHT_EYE_GPIO = 23
class Eyes:
def __init__(self):
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RIGHT_EYE_GPIO, GPIO.OUT)
GPIO.setup(LEFT_EYE_GPIO, GPIO.OUT)
GPIO.output(RIGHT_EYE_GPIO, GPIO.HIGH)
GPIO.output(LEFT_EYE_GPIO, GPIO.HIGH)
self.blink_duration = 0.1
Thread(target=self.run, daemon=True).start()
def run(self):
while True:
GPIO.output(RIGHT_EYE_GPIO, GPIO.LOW)
GPIO.output(LEFT_EYE_GPIO, GPIO.LOW)
time.sleep(self.blink_duration)
GPIO.output(RIGHT_EYE_GPIO, GPIO.HIGH)
GPIO.output(LEFT_EYE_GPIO, GPIO.HIGH)
next_blink = np.random.rand() * 4 # seconds
time.sleep(next_blink)
if __name__ == "__main__":
e = Eyes()
while True:
time.sleep(1)

View file

@ -1,32 +0,0 @@
import RPi.GPIO as GPIO
import numpy as np
LEFT_FOOT_PIN = 22
RIGHT_FOOT_PIN = 27
class FeetContacts:
def __init__(self):
GPIO.setwarnings(False) # Ignore warning for now
GPIO.setmode(GPIO.BCM) # Use physical pin numbering
GPIO.setup(LEFT_FOOT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(RIGHT_FOOT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def get(self):
left = False
right = False
if GPIO.input(LEFT_FOOT_PIN) == GPIO.LOW:
left = True
if GPIO.input(RIGHT_FOOT_PIN) == GPIO.LOW:
right = True
return np.array([left, right])
if __name__ == "__main__":
import time
feet_contacts = FeetContacts()
while True:
print(feet_contacts.get())
time.sleep(0.05)

View file

@ -1,29 +0,0 @@
import RPi.GPIO as GPIO
import time
PROJECTOR_GPIO = 25
class Projector:
def __init__(self):
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(PROJECTOR_GPIO, GPIO.OUT)
GPIO.output(PROJECTOR_GPIO, GPIO.LOW)
self.on = False
def switch(self):
self.on = not self.on
if self.on:
GPIO.output(PROJECTOR_GPIO, GPIO.HIGH)
else:
GPIO.output(PROJECTOR_GPIO, GPIO.LOW)
if __name__ == "__main__":
p = Projector()
while True:
p.switch()
time.sleep(1)

View file

@ -1,5 +0,0 @@
from mini_bdx_runtime.raw_imu import Imu
if __name__ == "__main__":
imu = Imu(50, calibrate=True, upside_down=False)

View file

@ -1,9 +0,0 @@
from mini_bdx_runtime.rustypot_position_hwi import HWI
from mini_bdx_runtime.duck_config import DuckConfig
import time
duck_config = DuckConfig()
hwi = HWI(duck_config)
hwi.turn_off()
time.sleep(1)

View file

@ -1,9 +0,0 @@
from mini_bdx_runtime.rustypot_position_hwi import HWI
from mini_bdx_runtime.duck_config import DuckConfig
import time
duck_config = DuckConfig()
hwi = HWI(duck_config)
hwi.turn_on()
time.sleep(1)

View file

@ -1,10 +1,10 @@
[metadata]
name = mini-bdx-runtime
version = 0.0.1
name = open-duck-mini-runtime
version = 1.0.0
author = Antoine Pirrone
author_email = antoine.pirrone@gmail.com
url = https://github.com/apirrone/mini_BDX_runtime
description = Runtime code for mini BDX
url = https://github.com/apirrone/Open_Duck_Mini_Runtime
description = Runtime code for Open Duck Mini
long_description = file: README.md
long_description_content_type = text/markdown
@ -14,8 +14,9 @@ packages = find:
zip_safe = True
include_package_data = True
package_dir=
=mini_bdx_runtime
=src
install_requires =
RPi.GPIO==0.7.1
rustypot==0.1.0
onnxruntime==1.18.1
numpy==1.26.4
@ -28,7 +29,7 @@ install_requires =
# adafruit_extended_bus
[options.packages.find]
where=mini_bdx_runtime
where=src
[options.package_data]
@ -36,3 +37,4 @@ where=mini_bdx_runtime
[options.entry_points]
console_scripts =
walk = open_duck_mini_runtime.walk:main

View file

@ -0,0 +1,64 @@
import board
import pwmio
import math
import time
LEFT_ANTENNA_PIN = board.D13
RIGHT_ANTENNA_PIN = board.D12
LEFT_SIGN = 1
RIGHT_SIGN = -1
MIN_UPDATE_INTERVAL = 1 / 50 # 20ms
def value_to_duty_cycle(v):
pulse_width_ms = 1.5 + (v * 0.5) # 1ms to 2ms
duty_cycle = int((pulse_width_ms / 20) * 65535)
return min(max(duty_cycle, 3277), 6553)
class Antennas:
def __init__(self):
neutral_duty = value_to_duty_cycle(0)
self.pwm_left = pwmio.PWMOut(LEFT_ANTENNA_PIN, frequency=50, duty_cycle=neutral_duty)
self.pwm_right = pwmio.PWMOut(RIGHT_ANTENNA_PIN, frequency=50, duty_cycle=neutral_duty)
def set_position_left(self, position):
self.set_position(self.pwm_left, position, LEFT_SIGN)
def set_position_right(self, position):
self.set_position(self.pwm_right, position, RIGHT_SIGN)
def set_position(self, pwm, value, sign=1):
# if value == 0:
# return
if -1 <= value <= 1:
duty_cycle = value_to_duty_cycle(value * sign) # Convert value to duty cycle (1ms-2ms)
pwm.duty_cycle = duty_cycle
else:
print("Invalid input! Enter a value between -1 and 1.")
def stop(self):
time.sleep(MIN_UPDATE_INTERVAL)
self.set_position_left(0)
self.set_position_right(0)
time.sleep(MIN_UPDATE_INTERVAL)
self.pwm_left.deinit()
self.pwm_right.deinit()
if __name__ == "__main__":
antennas = Antennas()
try:
start_time = time.monotonic()
current_time = start_time
while current_time - start_time < 5:
value = math.sin(2 * math.pi * 1 * current_time)
antennas.set_position_left(value)
antennas.set_position_right(value)
time.sleep(MIN_UPDATE_INTERVAL)
current_time = time.monotonic()
finally:
antennas.stop()

View file

@ -50,7 +50,7 @@ class Buttons:
if __name__ == "__main__":
from mini_bdx_runtime.xbox_controller import XBoxController
from open_duck_mini_runtime.xbox_controller import XBoxController
xbox_controller = XBoxController(30)
buttons = Buttons()

View file

@ -0,0 +1,58 @@
import board
import digitalio
import random
import time
from threading import Thread, Event
LEFT_EYE_PIN = board.D24
RIGHT_EYE_PIN = board.D23
class Eyes:
def __init__(self, blink_duration=0.1, min_interval=1.0, max_interval=4.0):
self.left_eye = digitalio.DigitalInOut(LEFT_EYE_PIN)
self.left_eye.direction = digitalio.Direction.OUTPUT
self.right_eye = digitalio.DigitalInOut(RIGHT_EYE_PIN)
self.right_eye.direction = digitalio.Direction.OUTPUT
self.blink_duration = blink_duration
self.min_interval = min_interval
self.max_interval = max_interval
self._stop_event = Event()
self._thread = Thread(target=self.run, daemon=True)
self._thread.start()
def _set_eyes(self, state):
self.left_eye.value = state
self.right_eye.value = state
def run(self):
try:
while not self._stop_event.is_set():
self._set_eyes(False)
time.sleep(self.blink_duration)
self._set_eyes(True)
next_blink = random.uniform(self.min_interval, self.max_interval)
time.sleep(next_blink)
except Exception as err:
print(f"Error in eye thread: {err}")
self._stop_event.set()
def stop(self):
self._stop_event.set()
self._thread.join()
self._set_eyes(False)
self.left_eye.deinit()
self.right_eye.deinit()
if __name__ == "__main__":
e = Eyes()
try:
while True:
time.sleep(1)
finally:
e.stop()

View file

@ -0,0 +1,34 @@
import board
import digitalio
import time
LEFT_FOOT_PIN = board.D22
RIGHT_FOOT_PIN = board.D27
class FeetContacts:
def __init__(self):
self.left_foot = digitalio.DigitalInOut(LEFT_FOOT_PIN)
self.left_foot.direction = digitalio.Direction.INPUT
self.left_foot.pull = digitalio.Pull.UP
self.right_foot = digitalio.DigitalInOut(RIGHT_FOOT_PIN)
self.right_foot.direction = digitalio.Direction.INPUT
self.right_foot.pull = digitalio.Pull.UP
def get(self):
left = not self.left_foot.value
right = not self.right_foot.value
return [left, right]
def stop(self):
self.left_foot.deinit()
self.right_foot.deinit()
if __name__ == "__main__":
feet_contacts = FeetContacts()
try:
while True:
print(feet_contacts.get())
time.sleep(0.05)
finally:
feet_contacts.stop()

View file

@ -2,7 +2,7 @@ import time
import numpy as np
import rustypot
from mini_bdx_runtime.duck_config import DuckConfig
from open_duck_mini_runtime.duck_config import DuckConfig
class HWI:

View file

@ -0,0 +1,31 @@
import board
import digitalio
import time
PROJECTOR_GPIO = board.D25
class Projector:
def __init__(self):
self.project = digitalio.DigitalInOut(PROJECTOR_GPIO)
self.project.direction = digitalio.Direction.OUTPUT
self.on = False
def switch(self):
self.on = not self.on
self.project.value = self.on
def stop(self):
self.project.value = False
self.project.deinit()
if __name__ == "__main__":
p = Projector()
try:
while True:
p.switch()
time.sleep(1)
finally:
p.stop()

View file

@ -2,24 +2,27 @@ import time
import pickle
import numpy as np
from mini_bdx_runtime.rustypot_position_hwi import HWI
from mini_bdx_runtime.onnx_infer import OnnxInfer
from open_duck_mini_runtime.hwi import HWI
from open_duck_mini_runtime.onnx_infer import OnnxInfer
from mini_bdx_runtime.raw_imu import Imu
# from mini_bdx_runtime.imu import Imu
from mini_bdx_runtime.poly_reference_motion import PolyReferenceMotion
from mini_bdx_runtime.xbox_controller import XBoxController
from mini_bdx_runtime.feet_contacts import FeetContacts
from mini_bdx_runtime.eyes import Eyes
from mini_bdx_runtime.sounds import Sounds
from mini_bdx_runtime.antennas import Antennas
from mini_bdx_runtime.projector import Projector
from mini_bdx_runtime.rl_utils import make_action_dict, LowPassActionFilter
from mini_bdx_runtime.duck_config import DuckConfig
from open_duck_mini_runtime.raw_imu import Imu
from open_duck_mini_runtime.poly_reference_motion import PolyReferenceMotion
from open_duck_mini_runtime.xbox_controller import XBoxController
from open_duck_mini_runtime.feet_contacts import FeetContacts
from open_duck_mini_runtime.eyes import Eyes
from open_duck_mini_runtime.sounds import Sounds
from open_duck_mini_runtime.antennas import Antennas
from open_duck_mini_runtime.projector import Projector
from open_duck_mini_runtime.rl_utils import make_action_dict, LowPassActionFilter
from open_duck_mini_runtime.duck_config import DuckConfig
from importlib.resources import files
import open_duck_mini_runtime
import os
HOME_DIR = os.path.expanduser("~")
ASSETS_ROOT_PATH: str = str(files(open_duck_mini_runtime).joinpath("assets/"))
class RLWalk:
@ -37,7 +40,6 @@ class RLWalk:
replay_obs=None,
cutoff_frequency=None,
):
self.duck_config = DuckConfig(config_json_path=duck_config_path)
self.commands = commands
@ -101,8 +103,9 @@ class RLWalk:
self.xbox_controller = XBoxController(self.command_freq)
# Reference motion, but we only really need the length of one phase
# TODO
self.PRM = PolyReferenceMotion("./polynomial_coefficients.pkl")
self.PRM = PolyReferenceMotion(
f"{ASSETS_ROOT_PATH}/polynomial_coefficients.pkl"
)
self.imitation_i = 0
self.imitation_phase = np.array([0, 0])
self.phase_frequency_factor = 1.0
@ -116,17 +119,11 @@ class RLWalk:
if self.duck_config.projector:
self.projector = Projector()
if self.duck_config.speaker:
self.sounds = Sounds(
volume=1.0, sound_directory="../mini_bdx_runtime/assets/"
)
self.sounds = Sounds(volume=1.0, sound_directory=ASSETS_ROOT_PATH)
if self.duck_config.antennas:
self.antennas = Antennas()
def get_obs(self):
# imu_data = self.imu.get_data(as_mat=True)
# raw
imu_data = self.imu.get_data()
if imu_data is None:
print("IMU data is None, skipping observation retrieval")
@ -197,7 +194,6 @@ class RLWalk:
time.sleep(2)
def get_phase_frequency_factor(self, x_velocity):
max_phase_frequency = 1.2
min_phase_frequency = 1.0
@ -345,17 +341,27 @@ class RLWalk:
except KeyboardInterrupt:
if self.duck_config.antennas:
self.antennas.stop()
if self.duck_config.eyes:
self.eyes.stop()
if self.duck_config.projector:
self.projector.stop()
self.feet_contacts.stop()
if self.save_obs:
pickle.dump(self.saved_obs, open("robot_saved_obs.pkl", "wb"))
print("TURNING OFF")
if __name__ == "__main__":
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--onnx_model_path", type=str, required=True)
parser.add_argument(
"--onnx_model_path",
type=str,
required=False,
default=f"{HOME_DIR}/BEST_WALK_ONNX_2.onnx",
)
parser.add_argument(
"--duck_config_path",
type=str,
@ -408,3 +414,7 @@ if __name__ == "__main__":
)
print("Done instantiating RLWalk")
rl_walk.run()
if __name__ == "__main__":
main()

View file

@ -3,7 +3,7 @@ from threading import Thread
from queue import Queue
import time
import numpy as np
from mini_bdx_runtime.buttons import Buttons
from open_duck_mini_runtime.buttons import Buttons
X_RANGE = [-0.15, 0.15]

View file

@ -1,5 +1,5 @@
from mini_bdx_runtime.xbox_controller import XBoxController
from mini_bdx_runtime.antennas import Antennas
from open_duck_mini_runtime.xbox_controller import XBoxController
from open_duck_mini_runtime.antennas import Antennas
import time
controller = XBoxController(60)

View file

@ -1,3 +1,7 @@
"""
Warning, Obsolete code, kept for reference.
"""
from openai import OpenAI
import time
import json
@ -5,10 +9,10 @@ import os
from io import BytesIO
import base64
from v2_rl_walk_mujoco import RLWalk
from v2_rl_walk_mujoco import RLWalk # TODO
from threading import Thread
import cv2
from mini_bdx_runtime.camera import Cam
from open_duck_mini_runtime.camera import Cam
# TODO mission : find an object ?

View file

@ -3,8 +3,8 @@ Debug script to check all motors in the robot.
Verifies each motor is accessible and allows testing movement.
"""
from mini_bdx_runtime.rustypot_position_hwi import HWI
from mini_bdx_runtime.duck_config import DuckConfig
from open_duck_mini_runtime.hwi import HWI
from open_duck_mini_runtime.duck_config import DuckConfig
import time
import numpy as np
import traceback

5
tools/calibrate_imu.py Normal file
View file

@ -0,0 +1,5 @@
from open_duck_mini_runtime.raw_imu import Imu
if __name__ == "__main__":
imu = Imu(50, calibrate=True, upside_down=False)

View file

@ -2,8 +2,8 @@
Find the offsets to set in self.joints_offsets in hwi_feetech_pwm_control.py
"""
from mini_bdx_runtime.rustypot_position_hwi import HWI
from mini_bdx_runtime.duck_config import DuckConfig
from open_duck_mini_runtime.hwi import HWI
from open_duck_mini_runtime.duck_config import DuckConfig
import time
dummy_config = DuckConfig(config_json_path=None, ignore_default=True)

15
tools/turn_off.py Normal file
View file

@ -0,0 +1,15 @@
from open_duck_mini_runtime.hwi import HWI
from open_duck_mini_runtime.duck_config import DuckConfig
import time
def main():
duck_config = DuckConfig()
hwi = HWI(duck_config)
hwi.turn_off()
time.sleep(1)
if __name__ == "__main__":
main()

15
tools/turn_on.py Normal file
View file

@ -0,0 +1,15 @@
from open_duck_mini_runtime.hwi import HWI
from open_duck_mini_runtime.duck_config import DuckConfig
import time
def main():
duck_config = DuckConfig()
hwi = HWI(duck_config)
hwi.turn_on()
time.sleep(1)
if __name__ == "__main__":
main()