A locomotion RL policy for the Sunfounder PiDog: trained in simulation, deployed on the robot's onboard Raspberry Pi 4.
The starting problem is that Sunfounder publishes no URDF and no CAD model
for PiDog — a moderator confirmed as much on their own forum — and no community
model exists either. So the robot description here is derived from the
ground-truth kinematic constants hardcoded in Sunfounder's own control library
(sunfounder/pidog) and its servo driver (sunfounder/robot-hat), rather than
measured off a drawing that doesn't exist.
That origin shapes the whole repo: every constant is labelled with where it came from. Sourced, derived, or estimated — never silently mixed.
| Task | State |
|---|---|
1. URDF + joint limits derived from pidog.py |
done, validated in MuJoCo |
| 2. Real mass / inertia measurements | partly — bugs fixed, numbers pending |
| 3. Isaac Lab training environment | scaffolded, not yet run on a GPU |
| 4. ONNX export + on-robot bridge | not started |
urdf_description/ the robot model and how it was derived
generate_urdf.py SINGLE SOURCE OF TRUTH -- edit constants, re-run
derive_joint_limits.py reproduces pidog.py's IK; derives the joint limits
validate_urdf.py loads in MuJoCo, cross-checks FK against that IK
MEASUREMENTS.md worksheet of what still needs a scale and calipers
README.md sourced vs derived vs estimated, constant by constant
urdf/pidog.urdf generated output -- never hand-edit, it gets overwritten
pidog_rl/ sim-agnostic layer, shared by training and deployment
hardware.py every "what the real robot does" constant, cited
servo_model.py latency / slew / backlash model for domain randomization
isaac/ Isaac Lab training environment (see isaac/README.md)
tests/ local tests -- no Isaac Sim, no GPU required
Nothing here needs a GPU except the actual training run.
# regenerate the robot description
python urdf_description/generate_urdf.py
# reproduce the joint-limit derivation from pidog.py's own IK
python urdf_description/derive_joint_limits.py
# load it in MuJoCo, check FK against that IK, render a pose
python urdf_description/validate_urdf.py
# test the sim-agnostic layer
python tests/test_hardware_and_servo.pyvalidate_urdf.py needs mujoco; the tests need torch. Training additionally
needs Isaac Sim and Isaac Lab on an NVIDIA RTX machine — see
isaac/README.md.
PiDog has no joint encoders and no torque sensing. At deploy time the only proprioception is the SH3001 IMU and the angles the policy itself commanded. Standard quadruped RL environments feed measured joint positions and velocities into the policy; doing that here trains something that cannot be deployed at all — and it fails silently, learning beautifully in sim and then flailing on hardware.
So the policy observation is restricted to what the robot can actually sense,
and a test in tests/ statically parses the environment and fails if a
non-observable term ever leaks in. The critic is allowed privileged state, since
it is discarded at export.
pidog.py's _imu_thread sleeps 0.05 s between sensor reads. Training at
200 Hz and deploying at 20 Hz is a classic silent sim-to-real gap, so the
environment pins its control period to the real sensor rate.
An RPi 4 runs a small MLP inside a 50 ms control period without difficulty. The
real risk is the open-loop hobby servo between the action and the world:
command latency, a 428 °/s slew ceiling, gear backlash, and stale zero
calibration. pidog_rl/servo_model.py models all four and randomizes them per
episode.
These are load-bearing, not preferences:
generate_urdf.pyis the only place the URDF is written. Edit the constants block and re-run.urdf/pidog.urdfis generated output.- Every constant is labelled by provenance.
# ESTmarks a guess and stays until a real measurement replaces it. Sourced constants cite the file and symbol they came from. Derived constants cite the script that derives them. - Validate by behaviour, not by parsing. A URDF change is checked by loading
it in MuJoCo and confirming its forward kinematics reproduce
pidog.py's inverse kinematics — not by confirming the XML is well-formed.
Sourced (exact, from pidog.py / robot_hat): link lengths 42/76 mm, hip
mount rectangle 117×98 mm, 2-DOF sagittal-only legs, 3-DOF head with its 45°
pitch offset, servo command clamp ±90°, slew ceilings 428/300/500 °/s, servo
interpolation at 100 Hz, IMU at 20 Hz.
Derived (reproducible via derive_joint_limits.py): hip and knee limits,
the knee's 90° mechanical zero offset, and the leg joint axis direction — which
determines which way the robot walks. Details in
urdf_description/README.md.
Estimated (still guesses): all masses and inertias, the chassis bounding box, centre-of-mass offset, servo stall torque, and the neck/tail mount offsets. Listed with instructions in urdf_description/MEASUREMENTS.md.
Kinematic constants, IK, gait tables and servo timing all reverse-engineered from sunfounder/pidog and sunfounder/robot-hat.