Skip to main content

Hardware-in-the-loop robotics simulation for AI agents, built on MuJoCo

Project description

physsim

Hardware-in-the-loop robotics simulation. Describe a robot, write firmware for a real board, and run that firmware against MuJoCo physics — with a window to watch it in.

The firmware is the same code you would flash. Python firmware runs unmodified on a real ESP32 under MicroPython; C++ firmware is written against an Arduino-compatible API. Pin assignments, PWM resolution, I²C transaction time, ADC quantisation and motor back-EMF are all in the loop, so code that would miss its deadline on a bench misses it here too.

Install

pip install physsim                 # the library
pip install "physsim[plot,video]"   # plus matplotlib and mp4 export

C/C++ firmware additionally needs a host compiler (clang++, c++ or g++). The QEMU tier needs Espressif's qemu-system-xtensa and xtensa-esp32-elf-gccphyssim.runtime.qemu.install_hint() prints exactly what to download and where to put it. Everything else works without either.

Working on physsim itself:

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest

.venv/bin/python -m physsim.examples.pid_arm --view

What it looks like

from physsim import Board, Robot, run_firmware

FIRMWARE = """
from machine import Pin, PWM
import utime

servo = PWM(Pin(13), freq=50)

def main():
    servo.duty_us(2000)
    utime.sleep_ms(800)
    log("done")
"""

esp = Board("esp32")
arm = Robot("arm", board=esp)
base = arm.base(mass=1.0)
link = base.link(length=0.30, mass=0.25, name="upper")
link.joint("revolute", axis="y", range=(-90, 90), name="shoulder") \
    .servo(esp.pwm(13), stall_torque=2.5)

run = run_firmware(arm.build(), FIRMWARE, duration=3.0, view=True)
print(run.summary())

view=True shows the robot in MuJoCo's own interactive viewer — orbit, zoom, contact forces — and a second window with the camera views, meters, plots, buttons and inputs you compose. MuJoCo draws the scene; physsim builds only what MuJoCo has no opinion about.

Run it with a plain python; no mjpython needed. On macOS, Tk and mjpython cannot share a process, so physsim runs MuJoCo's viewer in a child process and streams it the pose. You get both windows either way.

.venv/bin/python -m physsim.examples.voice_arm --view

Why it catches things

The compiler validates the design before a run happens, and reports problems as structured diagnostics with a code, a message and a fix:

PIN002 [servo1]: 'servo1' needs pwm on GPIO34 (pwm), but that pin only supports
                 digital_in|adc — input-only: no output driver, no internal pull-up/down
    fix: pins on esp32 with pwm: 0, 1, 2, 3, 4, 5, 12, 13, 14, 15, ...

PHY006 [servo1]: actuator 'servo1' can supply 0.05 N·m but holding 'shoulder' against
                 gravity needs up to 1.6 N·m (32x) somewhere in its range
    fix: use a stronger actuator, gear it down, shorten or lighten the links beyond
         this joint, or restrict the joint range

Testing a controller

from physsim import Scenario

s = Scenario(model, FIRMWARE, duration=9.0)
s.disturb(body="fore", at=3.0, force=(4, 0, 0), duration=0.15)
s.payload(body="fore", mass=0.12, at=6.0)
s.assert_settles("shoulder.angle_deg", target=40.0, within=2.0, by=2.5)
s.assert_recovers("shoulder.angle_deg", target=40.0, within=2.0, at=3.0, by=5.0)

report = s.run()
print(report.summary())        # PASSED/FAILED, metrics, every check with its numbers
report.save_csv("trace.csv")

Examples

Command Shows
python -m physsim.examples.arm2dof Building and validating; gravity vs. powered servos
python -m physsim.examples.blink Firmware and physics in lockstep
python -m physsim.examples.pid_arm --view Closed-loop PID written as firmware
python -m physsim.examples.pid_arm_cpp The same controller in C++, identical trajectory
python -m physsim.examples.disturbance --view Disturbance rejection with assertions
python -m physsim.examples.camera_arm Live window with a hand-mounted camera
python -m physsim.examples.voice_arm --view Custom dashboard: 2 cameras, mic meter, E-STOP, text input
python -m physsim.examples.predict_tune Predict → tune → run → verify, without touching firmware first
python -m physsim.examples.pi_vision --view Pi 5 firmware tracking a red target through the camera

Add --view to watch. A plain python is all you need — MuJoCo's interactive scene viewer and the panel window come up together.


Predicting before you run

physsim.predict answers design questions from the physics alone — no firmware, no bus, no GUI, and about 100x faster than a real run:

from physsim import predict

predict.worst_case_torque(model)        # "shoulder: needs 1.09 N.m, has 5.04 N.m (4.62x)"
predict.payload_capacity(model, "fore") # 0.98 kg
predict.solve_ik(model, "tip", [0.3, 0, 0.2])

gains = predict.tune_pid(model, "shoulder", "sh_motor", target=40.0)
report = Scenario(model, firmware_using(gains), duration=6.0).run()
print(predict.verify(gains, report))    # how far was the prediction from reality?

A rollout has no sensor noise or bus timing, so reality is always slower and less exact. verify() measures that gap instead of letting you assume it away.


Requirements

  • Python 3.11+, mujoco and numpy (installed with the package)
  • C/C++ firmware additionally needs clang++, c++ or g++ on PATH
  • Plots need matplotlib; video export needs imageio. Everything else — traces, CSV, rendered frames, the live window — needs neither.

Documentation

  • The website — landing page, full guide, and a searchable reference for every board, pin and diagnostic code. Static, no build step, no dependencies. Deploys to Vercel as-is (npx vercel --prod, config in vercel.json) or to GitHub Pages from /docs. See docs/README.md.
  • AGENTS.md — the reference, written for AI agents using the library
  • PLAN.md — architecture, design decisions, and the milestone plan
  • CLAUDE.md — invariants and conventions for working on the library

Or ask the library directly:

import physsim
physsim.capabilities()        # boards, peripherals, runtimes, signals, diagnostics
physsim.board_info("esp32")   # which pins can do what, and which to avoid
physsim.explain("PHY006")     # what a diagnostic means

Status

Every planned milestone (M0–M9) is complete: robot DSL and validating compiler, MJCF/URDF import, lockstep scheduler, Python and C++ firmware runtimes, peripheral models, the scenario harness, composable GUIs with recorded-and-replayable input, the microphone peripheral, live tunables, a 13-board catalog with electrical validation, a QEMU tier running real Xtensa firmware on an emulated ESP32, a Raspberry Pi tier with gpiozero/picamera2, firmware-side camera capture, and M9 prediction. 411 tests, 5 slow.

The same C/C++ firmware produces a byte-identical trajectory host-compiled and on emulated Xtensa — one conformance suite runs across all three runtimes, and the reference PID controller settles to the same hash on Python, host C++, and a real Xtensa ELF.

The electrical checks are worth calling out because a simulator cannot find what they find. Wiring an HC-SR04's 5 V echo line into an ESP32 input works perfectly in simulation and stresses a real pin, so it is a build error (PIN008). Two servos off the board's own 5 V pin draw 1400 mA against a 500 mA regulator, so that is reported too (PIN009) — along with ADC ranges that clip, I²C devices slower than their bus, PWM pins forced to share a timer, and software PWM that jitters.

Not yet built: camera formats beyond raw RGB888, audio waveforms or speech recognition (the microphone is level + keyword), memory-limit modelling, multi-board runs, FreeRTOS task semantics. The Pi tier is a gpiozero/picamera2 shim rather than CPU emulation, because upstream QEMU does not model the RP1 southbridge that owns Pi 5 GPIO. physsim.capabilities()["not_yet_implemented"] is the authoritative list.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

physsim-0.1.0.tar.gz (289.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

physsim-0.1.0-py3-none-any.whl (223.9 kB view details)

Uploaded Python 3

File details

Details for the file physsim-0.1.0.tar.gz.

File metadata

  • Download URL: physsim-0.1.0.tar.gz
  • Upload date:
  • Size: 289.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.0

File hashes

Hashes for physsim-0.1.0.tar.gz
Algorithm Hash digest
SHA256 373a1bfaf212be3d8e2a6c5e386400cfd4a44dcae3087556174d3c94230de6e7
MD5 0a7abd08722199f338b46d6f676d7274
BLAKE2b-256 0fe8758b3a2ff215bb203e2e85f3a193c22a3910afcde8fc7254fcc0655d4efd

See more details on using hashes here.

File details

Details for the file physsim-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: physsim-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 223.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.0

File hashes

Hashes for physsim-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 12cc9b6f73964eee56ed5dfbc9940ffe1a33ecd455e39770ab6e2ad1d39d7c8b
MD5 98fef0f8c3b00218ce929f45826a13c7
BLAKE2b-256 ac13c3c0b905af9f6e64ab4b785504022609909c2c38ef8e80f3b9c7ae1b0166

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page