Skip to content
SHINR DOCS
GITHUB ↗

Python Modules

Python Modules is Shinro’s registry-based Python composition pattern for whole-body robot control — the concrete implementation of the Control Architecture described conceptually elsewhere in these docs. It is robot-agnostic by design: five abstract base classes, registry-based factories, and TOML config compose a control stack for any robot that implements a Plant. The current reference robot is LeKiwi, a holonomic-base mobile manipulator, used throughout the demos below — but nothing about the framework is LeKiwi-specific.

The code lives in shinro-python-modules.

Architecture overview

Five ABCs — Controller, Plant, StateEstimator, TrajectoryGenerator, PhysicsEngine — each with a small interface and one or more concrete implementations. Every concrete class self-registers with a decorator (@register_controller("LQR"), @register_plant("ArmRobot"), and so on), and a generic factory instantiates a class by name from a TOML config file. Adding a new controller, estimator, trajectory generator, or plant means writing the class, adding the decorator plus a from_config classmethod, and a TOML file — no factory code changes.

See Control Architecture for why this decomposition exists and how the five ABCs compose. This page covers running and extending the framework as it stands today.

Getting started

Terminal window
pip install numpy scipy osqp mujoco

Run a demo:

Terminal window
# Terminal-only, no graphs or viewer
python -m demos.demo_simple
# Arm trajectory demo with live viewer
python -m demos.demo_arm_trajectory
# Base tracking with LQR + observer
python -m demos.demo_base_tracking
# Base tracking with MPC instead of LQR
python -m demos.demo_base_tracking --controller mpc
# Full pick-and-place sequence
python -m demos.demo_pick_and_place

Usage patterns

Programmatic (low-level)

Wire a plant to a physics engine and drive it directly:

from physics_engine import MuJoCoEngine
from plants.armrobot import ArmRobot
engine = MuJoCoEngine("path/to/model.xml")
arm = ArmRobot(num_dof=6, dt=0.02, ...)
arm.physics_engine(engine)
arm.step(np.array([0.05, 0.0, 0.0, 0.0, 0.0, 0.0]))

TOML-driven (generic)

Compose a controller and trajectory from config, without touching robot-specific code:

from simulation import RobotSim
from factories import ControllerFactory, TrajectoryFactory
sim = RobotSim("robot_config.toml")
ctrl = ControllerFactory("configs/controllers/lqr_base.toml").create()
schedule = TrajectoryFactory("configs/trajectories/base_straight.toml").create()
for step, target in enumerate(schedule):
u = ctrl.compute(sim.base.get_state(), target)
sim.base.step(u)
sim.step()

Auto-generating config from a robot’s MJCF model

Terminal window
python scripts/generate_robot_config.py lekiwi-sim/mjcf_lcmm_robot.xml > robot_config.toml

robot_config.toml is generated from a robot’s MuJoCo XML model rather than hand-written — the intended path for pointing the framework at a new robot.

Component catalog

Controllers

ControllerRegistered asClassPlantUse Case
PIDPIDPIDControllerArm (joint space)Position servo — send joint angles directly
MPC (Δu)MPC_DeltaUMPC_LTI_DeltaUBase (3D)Trajectory optimization with Δu regularization
MPC (base)MPC_LTIMPC_LTI_BaseBase (3D)Standard linear time-invariant MPC
LQRLQRLQRBase (3D)Regulation / stabilization

State estimators

EstimatorUse Case
KalmanFilterOptimal state estimation with process/measurement noise
LuenbergerObserverDeterministic state estimation with user-specified gain

Trajectory generators

GeneratorContinuityConfig type
CubicPolynomialPosition + velocitycubic_segments
QuinticPolynomialPosition + velocity + accelerationquintic_segments
WaypointSchedulePiecewise constantwaypoints
PhaseScheduleMulti-signal (arm + base + jaw)phase_list

Extending the framework

Adding support for a new robot means implementing a new Plant — the controllers, estimators, trajectory generators, and factories are already robot-agnostic and require no changes:

@register_plant("MyRobot")
class MyRobot(Plant):
@classmethod
def from_config(cls, config):
return cls(...)

Adding a new controller:

@register_controller("MyType")
class MyController(Controller):
@classmethod
def from_config(cls, config):
return cls(...)

Adding a new physics engine (an alternative to MuJoCo):

from components import PhysicsEngine
class MyEngine(PhysicsEngine):
def get_joint_qpos(self, name): ...
def set_joint_ctrl(self, name, value): ...
# ... implement all abstract methods

Plants written against the PhysicsEngine protocol accept a new engine automatically — no plant code changes.

Reference robot: LeKiwi

LeKiwi — a 3-DOF holonomic mobile base carrying a 6-DOF arm — is the robot the demos above target today. Its Plant implementations (HolonomicMobileRobot, ArmRobot) are concrete instances of the same ABCs any other robot would implement; nothing in the framework depends on LeKiwi specifically.

The arm’s step() method takes a Cartesian velocity twist [dx, dy, dz, droll, dpitch, dyaw], integrates it into a target pose, runs inverse kinematics internally, and sends joint angles to the simulated or real servos — the controller never touches joint space. This is the “nonlinearity encapsulated in the Plant” pattern from Control Architecture applied concretely.

Known simulation limitations: the MuJoCo model welds the base in place and tracks the arm’s kinematic state separately from the base’s, rather than simulating both as one articulated body — a simplification of the demo environment, not a framework constraint.

Status

This framework runs today, standalone, outside Shinro Studio — install it and run the demos above with no Shinro-specific tooling. Wiring it into Shinro Studio (Simulator mode, module composition) is on our roadmap; nothing on this page describes Studio integration as already working.

100%