Franka Simulation Server
A high-fidelity simulation server that communicates with the Franka robot's network protocol, enabling seamless switching between simulation and hardware. Runs on MuJoCo by default; Genesis is available as an optional backend.
Overview
The Franka Simulation Server provides a drop-in replacement for the real Franka robot, implementing the complete libfranka network protocol. This allows developers to:
- Test and debug robot controllers in simulation before deployment
- Develop applications that work identically on both simulation and hardware
- Validate error handling and safety features
- Experiment with different control strategies risk-free
Compatibility
franka_sim implements the latest libfranka wire protocol — robot server version 10.
In this protocol the robot model is built client-side: during connection the client
fetches the robot URDF via the GetRobotModel command and builds its own Pinocchio model.
The server therefore just serves a valid URDF (a hand-less FR3 arm by default) instead of a
precompiled model library.
You need a libfranka version that speaks server protocol 10:
| libfranka Version | Robot System Version | Robot/Gripper Server |
|---|---|---|
| >= 0.18.0 | >= 5.9.0 | 10 / 3 ✅ supported |
| >= 0.15.0 | >= 5.7.2 | 9 / 3 — not supported |
See the Franka software compatibility matrix
for the full list. Older libfranka releases (server version 9 and below) use a different wire
format — double-based RobotState and server-side LoadModelLibrary instead of GetRobotModel —
and are not compatible with this server.
Related Projects
- libfranka-python - Python bindings for libfranka
- franka-gym - Franka gym implementation
Preview Video
- Native libfranka control
- With Python
Architecture
In this repository, we only provide the simulation server backend, with a choice of physics engines behind it (MuJoCo by default, Genesis optionally).
The libfranka python bindings will become available in a separate repository.
The system consists of several key components:
-
libfranka Interface Layer
- Implements the standard Franka robot network protocol
- Handles TCP command interface and UDP state updates
- Targets the latest libfranka wire protocol (robot server version 10)
-
Physics Simulation Backend
- Physics-based robot simulation using MuJoCo (default) or Genesis (
--physics genesis) - Real-time joint state computation and dynamics
- Physics-based robot simulation using MuJoCo (default) or Genesis (
-
State Management
- Complete robot state tracking and synchronization
- Accurate error reporting and status updates
- Real-time state transmission (1kHz update rate)
-
Control Modes
- Joint Position Control
- Joint Velocity Control
- Joint Torque Control
- Supports seamless switching between modes
Key Features
- Protocol Compatibility: Full implementation of the Franka robot network protocol
- Real-time Simulation: High-frequency state updates and control (1kHz)
- Multiple Control Modes: Supports position, velocity, and torque control
- Error Handling: Replicates real robot error states and recovery
Getting Started
Prerequisites
- Python 3.9+
- numpy==1.26.4
- mujoco>=3.2,<3.3 (the default physics backend, installed automatically)
Genesis is an optional backend (--physics genesis) — see below.
Installation
Option 1: Install from PyPI (Recommended)
The package is available on PyPI and can be installed with pip. This pulls in MuJoCo, the default physics backend:
pip install franka-sim
To also use the Genesis backend, install the genesis extra:
pip install 'franka-sim[genesis]'
Option 2: Install from Source
# Clone the repository
git clone git@github.com:BarisYazici/libfranka-sim.git
# Install the package
cd libfranka-sim/simulation
pip install -e .
Basic Usage
After installation, you can run the server using the command-line executable:
# Start the server without visualization
run-franka-sim-server
# Start the server with visualization
run-franka-sim-server -v
Alternatively, if you installed from source, you can use:
# Start the simulation server
python -m franka_sim.run_server -v
In your application, use standard libfranka commands. The simulation will respond exactly like the real robot.
Testing in CI (no robot required)
Because the sim speaks the real wire protocol, your libfranka / franka_ros2 code can be regression-tested on every push. Three ways in — see the Testing in CI guide for details:
# GitHub Actions: start a simulated FR3, then run your tests against 127.0.0.1
- uses: BarisYazici/libfranka-sim@v1
with:
args: '--enforce-comm-constraints --enforce-motion-limits'
# Docker: self-contained image (models baked in, works offline)
docker run -d --network host ghcr.io/barisyazici/franka-sim
docker exec <container> franka-sim-check --timeout 30 # readiness gate
# pytest: `pip install franka-sim` ships a plugin; one server per session on a free port
def test_my_controller(franka_sim_server):
connect_my_stack(franka_sim_server.host, franka_sim_server.port)
With --enforce-comm-constraints and --enforce-motion-limits the sim aborts motions the way
the real FCI does (lost-cycle reflexes, discontinuity limits), so your error-recovery paths get
exercised in CI instead of on hardware.
Gripper (Franka Hand)
The gripper server (libfranka gripper protocol, TCP port 1338) runs by default alongside
the arm. Drive it with the standard franka::Gripper client (homing / move / grasp / stop).
# Arm + gripper, kinematic hand (no mesh; width tracked analytically, CI-friendly)
python -m franka_sim.run_server -v
# Arm + gripper, physics-backed Franka Hand: the hand mesh is loaded and the
# finger DOFs are simulated, so homing/move/grasp visibly move the fingers in
# the viewer (a grasp succeeds when the fingers settle within epsilon of the
# commanded width, whether they stalled on an object or reached it in free air)
python -m franka_sim.run_server -v --gripper-physics
# Disable the gripper server entirely
python -m franka_sim.run_server -v --no-gripper
| Flag | Gripper backend | Hand in viewer |
|---|---|---|
| (default) | FrankaHandSim (kinematic) |
no |
--gripper-physics |
FrankaHandPhysics (physics) |
yes, fingers move |
--no-gripper |
none (arm only) | no |
Mobile duo (two arms + TMR base on one scene)
--mobile-duo serves the mobile FR3 duo: one physics scene (both arms and the
TMR mobile base, physically rigid to each other) driven by three FCI
bridges, one per role. libfranka clients cannot be told to use a port other
than 1337, so the bridges are separated by host IP instead — each is
bound to its own loopback alias with --bind ROLE=HOST, repeated once per
role (left, right, base):
# Bring up the loopback aliases once per boot (Linux; 127.0.0.0/8 is all loopback)
sudo ip addr add 127.0.0.11/8 dev lo 2>/dev/null
sudo ip addr add 127.0.0.12/8 dev lo 2>/dev/null
sudo ip addr add 127.0.0.13/8 dev lo 2>/dev/null # only needed with --spine
python -m franka_sim.run_server --mobile-duo \
--scene-urdf /path/to/mobile_fr3_duo.urdf \
--mesh-root /path/to/franka_description \
--bind left=127.0.0.11 \
--bind right=127.0.0.12 \
--bind base=127.0.0.10
| Flag | Meaning |
|---|---|
--mobile-duo |
Serve the mobile duo instead of the classic single-arm server |
--scene-urdf PATH |
The combined mobile_fr3_duo URDF loaded into the one physics scene (required with --mobile-duo; see below for generating it) |
--mesh-root PATH |
Package root used to resolve package:// mesh URIs in the URDF — a franka_description checkout; defaults to the URDF's own directory |
--bind ROLE=HOST |
Bind one bridge to a host address; repeat for left, right and base (all three are required) |
--physics {genesis,mujoco} |
Physics backend for the scene (default mujoco) |
By convention this repo uses three loopback aliases on 127.0.0.0/8, one per
role, plus a fourth for the spine device:
| Role | Address |
|---|---|
| base | 127.0.0.10 |
| left arm | 127.0.0.11 |
| right arm | 127.0.0.12 |
| spine | 127.0.0.13 |
Every bridge still listens on the standard libfranka command port (1337);
override it for all three at once with --port.
Physics backend. MuJoCo is the default (--physics mujoco, or just omit
the flag) and is installed as a core dependency. --physics genesis runs the
same scene on Genesis instead, and needs the genesis extra:
pip install 'franka-sim[genesis]'. Genesis' per-call kernel-launch overhead
caps the scene at ~0.4x real time at its 2.5 ms step; MuJoCo holds 1.00x
real time at a 1 ms step — the rate the FCI bridges actually serve — using
about a third of one core. The protocol surface, the joint and link names,
the initial pose and the reported state are identical between the two, so a
client cannot tell them apart. Contacts are disabled on the MuJoCo path (the
chassis' URDF collision meshes interpenetrate as authored, and nothing in
this scene depends on contact: the base pose is integrated kinematically and
both arms are servo-driven).
Generating --scene-urdf. scripts/generate_mobile_duo_urdf.sh <franka_description_dir> <output.urdf> runs the upstream xacro with the
options this server expects (hand:=false, explicit robot_types, no
ROS 2 control/Gazebo) from a sourced ROS 2 Jazzy environment. It asserts the
franka_description checkout is pinned to the exact sha the mesh paths and
joint names were generated against, and refuses to run otherwise — a
different checkout can silently rename meshes or joints out from under the
sim.
The fake spine device. The duo's prismatic lift
(franka_spine_vertical_joint) is driven by a separate REST device on real
hardware, not by libfranka. --spine runs a fake version of that device
in-process (franka_sim.mobile.spine_stub) and shares its motion model with the
scene, so a REST move visibly raises the lift (and everything mounted on
it — the head and both arms) in the viewer:
python -m franka_sim.run_server --mobile-duo \
--scene-urdf /path/to/mobile_fr3_duo.urdf --mesh-root /path/to/franka_description \
--bind left=127.0.0.11 --bind right=127.0.0.12 --bind base=127.0.0.10 \
--spine
| Flag | Meaning |
|---|---|
--spine |
Also run the spine stub in-process (requires --mobile-duo) |
--spine-host |
Address the spine stub binds (default: 127.0.0.13) |
--spine-port |
Port the spine stub binds; SpineApiClient hardcodes 443 with no port, so leave this at the default unless you know why you're changing it (default: 443) |
--spine-cert / --spine-key |
TLS certificate/key for the stub; a throwaway self-signed pair is generated automatically when omitted |
The spine stub also runs standalone via the run-franka-spine-stub console
script (installed with the package), useful for exercising just the REST
device without a physics scene:
run-franka-spine-stub --host 127.0.0.13 --port 443
Cartesian-velocity protocol mode. The TMR base has no joint interface, so
it is driven by libfranka's kCartesianVelocity motion generator: the
client's commanded body-frame twist (O_dP_EE_c) is routed straight to the
base's swerve inverse kinematics instead of to a joint position/velocity/
torque path.
Environment variables.
| Variable | Meaning |
|---|---|
FRANKA_SIM_SPINE_PORT |
Test-suite only. SpineApiClient hardcodes port 443, which needs root to bind; set this to point the mobile-duo end-to-end tests at an unprivileged port instead (e.g. via an iptables REDIRECT 443 -> 8443) |
Troubleshooting
If you're running the Genesis backend (--physics genesis) and encounter
issues related to missing asset files, make sure you have the genesis
extra installed with the correct version of genesis-world:
pip install 'franka-sim[genesis]'
The simulator automatically uses the assets provided by the Genesis package,
so no additional asset files are needed. Note that genesis-world==0.2.1
also needs libigl<2.6 (newer libigl changes igl.signed_distance's return
arity); install that pin alongside the extra if Genesis import fails with an
unpacking error.
Configuration
Switching Between Simulation and Hardware
To switch between simulation and hardware:
-
Update the robot IP address in your application:
- Use
localhostor127.0.0.1for simulation - Use the real robot's IP for hardware
- Use
-
No other changes needed - your application code remains identical
Development Status
What the simulator implements today, checked against the real robot's own clients rather than against a spec:
- libfranka v10 wire protocol: Connect, 1 kHz float RobotState, GetRobotModel (URDF), Move / StopMove / SetCollisionBehavior / AutomaticErrorRecovery
- All joint interfaces: joint position, joint velocity, torque (external controller), with
q_d/dq_dechoed as the robot does - Cartesian interfaces: Cartesian pose and Cartesian velocity, with and without elbow (exercised by
franka_ros2'scartesian_pose,cartesian_velocity,cartesian_orientationandcartesian_elbowexample controllers) - Physics backends: MuJoCo (default, FR3 from MuJoCo Menagerie) and Genesis
- Gripper / Franka Hand server on 1338: kinematic by default, physics-simulated fingers with
--gripper-physics,--no-gripperto disable - Automatic error recovery, so
franka_hardware/ franka_ros2 can activate and recover on their own - Motion-limit checks with the robot's error names: joint/Cartesian velocity, acceleration and jerk discontinuities, torque rate, elbow limit / sign / start-elbow, self-collision avoidance (MuJoCo contact margin, per link pair) — logged always, enforced as reflex aborts with
--enforce-motion-limits - Communication-constraint checks: lost-cycle extrapolation and
communication_constraints_violationwith--enforce-comm-constraints - Visualization (MuJoCo viewer,
--vis) that never teleports the robot: a stalled render slows the simulation instead of bursting physics steps - CI-first packaging: Docker image, GitHub Action (
BarisYazici/libfranka-sim@v1), pytest plugin,franka-sim-check - Verified against Franka's own hardware smoke suite in
franka_ros2(jazzy): 47/47 on the simulator, permissive and with motion limits enforced - Contact-force reflexes (
cartesian_reflex/joint_reflexfromSetCollisionBehaviorthresholds): the arm has no external-force estimate yet - Reflex state persisting across client reconnects (the sim resets on disconnect; the robot stays in reflex until recovery)
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
This project is licensed under the Apache License Version 2.0 - see the LICENSE file for details.
Acknowledgments
- Franka Robotics GmbH for the original libfranka implementation
- The Genesis Simulator team for the physics engine
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file franka_sim-1.1.3.tar.gz.
File metadata
- Download URL: franka_sim-1.1.3.tar.gz
- Upload date:
- Size: 13.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3415e72b8f6426b553e2d5a58051bcc0d102fb143594f288157b4abf735e2167
|
|
| MD5 |
9cf62a173f2ca635e491fcd761d51156
|
|
| BLAKE2b-256 |
05d92500a4fc43d7bdf1528c4b2c246ccb0f9594a7c523049c9f08b35a6f0402
|
File details
Details for the file franka_sim-1.1.3-cp311-cp311-manylinux2014_x86_64.whl.
File metadata
- Download URL: franka_sim-1.1.3-cp311-cp311-manylinux2014_x86_64.whl
- Upload date:
- Size: 561.5 kB
- Tags: CPython 3.11
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb2f65c695bd0e988cd22590222363744dc679906889035ac0ef36921cd28a1f
|
|
| MD5 |
7743c08d71415b0a7111df5d53b134c0
|
|
| BLAKE2b-256 |
8643663e9e94578d3cf4a57d478a0cdcfa70fb62ef141aeba53ebd4cc4372430
|
File details
Details for the file franka_sim-1.1.3-cp311-cp311-macosx_11_0_universal2.whl.
File metadata
- Download URL: franka_sim-1.1.3-cp311-cp311-macosx_11_0_universal2.whl
- Upload date:
- Size: 561.5 kB
- Tags: CPython 3.11, macOS 11.0+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c121615ec77a7317730508e9e022a56553b7275e4cf138277c38b0c34af2365
|
|
| MD5 |
733d8367179ebc78086ce9d44a40139d
|
|
| BLAKE2b-256 |
d0647da26166c1c2b8aae516affef58581acadacc2a71b23624890b5ced26fb6
|
File details
Details for the file franka_sim-1.1.3-cp310-cp310-manylinux2014_x86_64.whl.
File metadata
- Download URL: franka_sim-1.1.3-cp310-cp310-manylinux2014_x86_64.whl
- Upload date:
- Size: 561.5 kB
- Tags: CPython 3.10
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1680dd310862d56656aad9db17db3764570353938a7a7ed267a6697347e5374
|
|
| MD5 |
cfcf56f5e612d3559ad8876bc0a38837
|
|
| BLAKE2b-256 |
8e231c5346554fd971310d4ab1c9288b2f86b7fe752d4c28a81dc34d1ccd95be
|
File details
Details for the file franka_sim-1.1.3-cp310-cp310-macosx_11_0_universal2.whl.
File metadata
- Download URL: franka_sim-1.1.3-cp310-cp310-macosx_11_0_universal2.whl
- Upload date:
- Size: 561.5 kB
- Tags: CPython 3.10, macOS 11.0+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
023e2f59e3c2c98b3c18e470870681ceee7f5ecb1a944e7a8f313b6044fa4c2f
|
|
| MD5 |
dc7dfe04753fe5f03f3b2ef2e947793e
|
|
| BLAKE2b-256 |
023b53cc8c8f7cf515530fbed84be1e48a6bda2b82b162d08d2027d36ed7d9aa
|
File details
Details for the file franka_sim-1.1.3-cp39-cp39-manylinux2014_x86_64.whl.
File metadata
- Download URL: franka_sim-1.1.3-cp39-cp39-manylinux2014_x86_64.whl
- Upload date:
- Size: 561.5 kB
- Tags: CPython 3.9
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be1ced2ae72f2ba53ef3ca9f0ce64657e8258797a0c5b69ce10ed697fab55cc3
|
|
| MD5 |
c7a2d262e6dd3b3e7cb0ec10400ef306
|
|
| BLAKE2b-256 |
d7d2c53618df8f183d6ac88815a18a4d004424cbf2df257fcd78ec4c3b4b5290
|