Robot Interface

aiofranka.robot.set_macos_control_thread_qos()[source]

Give the calling thread the scheduling that libfranka needs on macOS.

On macOS, libfranka busy-waits for robot states, which only keeps up with the 1 kHz control loop on a performance core. libfranka sets the QoS class USER_INTERACTIVE for the thread that creates the Robot; call this at the start of the thread that runs the control loop. Does nothing on other platforms or when busy-waiting is disabled with LIBFRANKA_MACOS_BUSY_WAIT=0.

Returns:

True if the QoS class was set.

Return type:

bool

aiofranka.robot.require_pylibfranka()[source]

Import pylibfranka, explaining how to install it on macOS if it is missing.

On macOS, aiofranka only depends on pylibfranka-macos, an unofficial build of pylibfranka, for Apple Silicon and macOS 15 or newer.

Returns:

The pylibfranka module.

Return type:

module

Raises:

ModuleNotFoundError – If pylibfranka is not installed.

aiofranka.robot.merge_payload(model, link_inertial, mass, com, inertia, site_id=None)[source]

Merge a payload on the flange into the last link of a MuJoCo FR3 model.

Sets fr3_link7’s mass, center of mass and inertia to those of the link and the payload together (parallel axis theorem), as RobotInterface.sync_payload() does for the model aiofranka’s controllers use. Call mj_setConst() afterwards.

Parameters:
  • model (mujoco.MjModel) – Model to change

  • link_inertial (tuple) – fr3_link7’s own mass, ipos, iquat and inertia, as in the unmodified model

  • mass (float) – Payload mass [kg]

  • com (array-like) – Payload center of mass in the flange frame [m] (3,)

  • inertia (array-like) – Payload inertia about its center of mass in the flange frame [kg m^2] (3, 3)

  • site_id (int | None) – The flange site (default: attachment_site)

fr3_link7’s mass, ipos, iquat and inertia, for merge_payload().

class aiofranka.robot.RobotInterface(ip=None, read_tool=True)[source]

Bases: object

High-level interface for Franka FR3 robot control.

This class provides a unified interface for both real robot control (via pylibfranka) and simulation (via MuJoCo). It handles low-level communication, state synchronization, and kinematics/dynamics computation.

real

True if connected to real robot, False for simulation

Type:

bool

model

MuJoCo model for kinematics/dynamics

Type:

mujoco.MjModel

data

MuJoCo data structure with current state

Type:

mujoco.MjData

robot

Real robot interface (if real=True)

Type:

pylibfranka.Robot

torque_controller

Active torque control interface

site_name

Name of end-effector site in MuJoCo model

Type:

str

site_id

MuJoCo site ID for end-effector

Type:

int

viewer

MuJoCo viewer window (if real=False)

Type:

mujoco.viewer

Examples

Real robot:
>>> robot = RobotInterface("172.16.0.2")
>>> robot.start()
>>> state = robot.state
>>> robot.step(np.zeros(7))  # Send zero torques
>>> robot.stop()
Simulation:
>>> robot = RobotInterface(None)
>>> state = robot.state
>>> robot.step(np.zeros(7))  # Updates MuJoCo simulation
Caveats:
  • Must call start() before step() on real robot

  • Collision behavior is set to high thresholds by default

  • State is synced from robot on every access (thread-safe)

  • MuJoCo model must match real robot configuration

__init__(ip=None, read_tool=True)[source]

Initialize robot interface.

Parameters:
  • ip (str | None) – Robot IP address (e.g., “172.16.0.2”) for real robot, or None for simulation mode.

  • read_tool (bool) – Read the end-effector profile active in Desk (robot.tool), which FrankaController.activate() checks configurations against, with the Desk credentials saved by aiofranka; it waits up to 3 s for Desk and never takes the control token

Raises:

Note

In real mode, collision thresholds are set to [100.0] * 7 for joints and [100.0] * 6 for Cartesian space. Adjust via robot.robot.set_collision_behavior() for more conservative behavior.

start()[source]

Start torque control mode on the real robot.

This must be called before sending torque commands. Does nothing in simulation.

Raises:

RuntimeError – If robot is not ready or already in control mode

Caveat:

After calling start(), you must send torque commands at ~1kHz to maintain control. Use FrankaController for automatic control loop management.

stop()[source]

Stop torque control mode on the real robot.

This gracefully terminates the control session. Does nothing in simulation.

Caveat:

Robot will hold position briefly then release brakes. Ensure robot is in a safe configuration before stopping.

set_load(mass, com=(0.0, 0.0, 0.0), inertia=(0.0, 0.0, 0.0))[source]

Set the load attached to the flange, e.g. a tool without a gripper.

The load is set on the robot, whose internal controller compensates its gravity, and merged into the last link of the MuJoCo model with sync_payload(), so the mass matrix used by the OSC and the simulated dynamics include it. The flange frame is the attachment_site frame. A mass of 0 removes the load.

Parameters:
  • mass (float) – Load mass [kg]

  • com (array-like) – Center of mass in the flange frame [m] (3,)

  • inertia (array-like) – Inertia about the center of mass in the flange frame [kg m^2], as a (3, 3) tensor or its diagonal (3,)

Raises:

RuntimeError – If called while torque control is running

Caveats:
  • Call before start(); the robot rejects the load during control

  • libfranka adds the load to the active end-effector profile in Desk

  • The load lasts until the next connection, which resets it. To keep a tool, save it as a profile with aiofranka.save_tool() and activate it with aiofranka.load_tool().

  • The robot rejects a mass without inertia

sync_payload()[source]

Merge the payload that the robot compensates into the MuJoCo model.

On the real robot, the payload is the end effector configured in Desk plus the load set with set_load(), from the last robot state read. In simulation, it is the load. It replaces the payload merged before, so the mass matrix used by the OSC matches what the robot compensates.

RobotInterface calls this when it connects and in set_load(). Call it after changing the end effector in Desk while connected, after sync_mj().

sync_mj()[source]

Sync mujoco state with real robot state

property state

Get current robot state with kinematics and dynamics.

Returns:

Dictionary containing:
  • qpos (np.ndarray): Joint positions [rad] (7,)

  • qvel (np.ndarray): Joint velocities [rad/s] (7,)

  • ee (np.ndarray): End-effector pose as 4x4 homogeneous transform

    [[R, p], [0, 1]] where R is rotation, p is position

  • jac (np.ndarray): End-effector Jacobian (6, 7) - [linear; angular]

  • mm (np.ndarray): Joint-space mass matrix (7, 7)

  • last_torque (np.ndarray): Last commanded torques [Nm] (7,)

Return type:

dict

Note

State is synchronized from real robot on every access. MuJoCo model is updated with latest robot state before computing kinematics/dynamics.

Example

>>> state = robot.state
>>> print(f"Joint 1 position: {state['qpos'][0]:.3f} rad")
>>> print(f"EE position: {state['ee'][:3, 3]}")
>>> print(f"EE orientation: {state['ee'][:3, :3]}")
property state_minimal

Get minimal robot state (fast, no kinematics/dynamics computation).

Use this for impedance/PID control where Jacobian and mass matrix are not needed. ~10x faster than full state property.

Returns:

Dictionary containing only:
  • qpos (np.ndarray): Joint positions [rad] (7,)

  • qvel (np.ndarray): Joint velocities [rad/s] (7,)

  • last_torque (np.ndarray): Last commanded torques [Nm] (7,)

Return type:

dict

step(torque)[source]

Send torque command to robot or step simulation.

Parameters:

torque (np.ndarray) – Joint torques [Nm] (7,)

Raises:

RuntimeError – If real robot not started or communication error

Note

  • Real robot: Sends torque command via pylibfranka at current timestep

  • Simulation: Updates MuJoCo with torques and advances one timestep

Caveats:
  • Must be called at ~1kHz for real robot to maintain control

  • Large torque changes may trigger safety limits

  • Torques should respect robot limits: |tau_i| < 87 Nm for joints 1-4, |tau_i| < 12 Nm for joints 5-7

Example

>>> # Send gravity compensation torques
>>> torque = robot.state['mm'] @ np.array([0, 0, 0, 0, 0, 0, -9.81])
>>> robot.step(torque)