Robot and Controller

Robot is the arm, and Controller drives it with the native 1 kHz loop. The controller’s modes, gains, targets, control laws and recordings are those of NativeFrankaController (see NativeFrankaController and FrankaController), with plain calls instead of awaitable ones.

aiofranka’s API: a Robot, the arm, and a Controller that drives it.

>>> import aiofranka
>>> robot = aiofranka.Robot("172.16.0.2")      # None: a simulated arm in MuJoCo
>>> controller = aiofranka.Controller(robot)
>>> controller.start()                          # takes torque control, starts the 1 kHz loop
>>> controller.move()
>>> controller.switch("osc")
>>> controller.set_freq(50)
>>> controller.set("ee_desired", target)
>>> robot.state["ee"]
>>> controller.stop()

Robot is what is true about the arm whoever drives it: its connection, its MuJoCo model with the tool, and its state. Controller is how it is driven: the 1 kHz loop, which runs in C++ and never waits for Python, and the modes, gains, targets, trajectories and recordings it runs.

Controller’s methods are plain calls. It runs its event loop in a thread of its own, so your code can block, sleep or compute while the loop holds the arm and move() plays its trajectory. For asyncio code, NativeFrankaController is the same controller with awaitable methods, on a RobotInterface.

class aiofranka.franka.Robot(ip=None, *, read_tool=True)[source]

A Franka arm: its connection, its MuJoCo model with the tool, and its state.

Connecting reads the arm, which needs FCI active (aiofranka.unlock()), and Desk’s active end-effector profile, which is merged into the model. Nothing moves until a Controller starts; one Controller at a time drives the arm.

Parameters:
  • ip (str | None) – The robot’s IP address, or None for a simulated arm in MuJoCo, shown in a viewer

  • read_tool (bool) – Read Desk’s active end-effector profile (robot.tool), which Controller.activate() checks configurations against

Example

>>> robot = aiofranka.Robot("172.16.0.2")
>>> robot.state["qpos"]
>>> robot.tool
__init__(ip=None, *, read_tool=True)[source]
property ip

The robot’s IP address, or None in simulation.

property real

Whether the arm is real; False in simulation.

property model

The arm’s MuJoCo model, with the tool merged into its last link.

property tool

Desk’s active end-effector profile when connecting (an aiofranka.Tool), or None.

property load

mass [kg], com [m] and inertia [kg m^2].

Type:

The load set with set_load()

property payload

Desk’s end-effector profile and the load of set_load() (in simulation, the load). Its mass [kg], com in the flange frame [m] and inertia about the com [kg m^2].

Type:

The payload the robot compensates, which the model includes

property controller

The Controller that drives the arm, or None.

property state

The arm’s latest state, a dict of numpy arrays:

  • qpos, qvel: joint positions [rad] and velocities [rad/s] (7,)

  • ee: the flange pose in the base frame (4, 4)

  • jac: the flange’s Jacobian, linear rows first (6, 7)

  • mm: the joint-space mass matrix (7, 7)

  • last_torque: the torque the robot was last commanded [Nm] (7,)

While a controller drives the arm, it is what the 1 kHz loop read at its last cycle; otherwise the arm is read now.

property robot_state

The libfranka state the arm sent last (pylibfranka.RobotState), with every number it reports: tau_J, O_F_ext_hat_K, control_command_success_rate, … None in simulation.

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

Set a load on the flange for this connection, e.g. a tool without a Desk profile: the robot compensates its gravity, and the model includes it. To keep a tool across connections, save it as a profile with aiofranka.save_tool() instead.

Parameters:
  • mass (float) – Load mass [kg]; 0 removes the load

  • 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], (3, 3) or its diagonal (3,)

Raises:

RuntimeError – If a controller drives the arm, as the robot rejects a load then

close()[source]

Stop the controller that drives the arm, if any, and disconnect.

class aiofranka.franka.Controller(robot)[source]

Drives a Robot with the native 1 kHz control loop.

The loop runs in C++, in a thread that never waits for Python. The control modes (impedance, pid, osc, torque and control laws), gains, targets and recordings are those of NativeFrankaController, whose documentation has the details; here they are plain calls. The controller runs its event loop in a thread of its own, so your code can block, sleep or compute while the loop holds the arm. A loop error, e.g. a reflex, stops the loop and is raised by the next call that commands the arm.

Parameters:

robot (Robot) – The arm to drive

kp, kd, ki, ee_kp, ee_kd, null_kp, null_kd

Gains, as in NativeFrankaController

q_desired, ee_desired, torque

Targets of the modes; set them with set() to hold a rate

initial_qpos, initial_ee

The arm’s pose at the last switch()

Example

>>> robot = aiofranka.Robot("172.16.0.2")
>>> controller = aiofranka.Controller(robot)
>>> controller.start()
>>> controller.move()
>>> controller.switch("impedance")
>>> controller.set_freq(50)
>>> for target in targets:
...     controller.set("q_desired", target)
>>> controller.stop()
__init__(robot)[source]
property running

Whether the 1 kHz loop runs.

property error

The error that stopped the loop, e.g. a reflex, or None; start() clears it.

start()[source]

Take the arm’s torque control and start the 1 kHz loop, holding the arm where it is. Returns once the loop runs.

Raises:

RuntimeError – If another controller drives the robot

stop()[source]

Stop the 1 kHz loop and give the arm’s torque control back. Does nothing if stopped.

switch(controller_type)[source]

Switch the control mode: “impedance”, “pid”, “osc”, “torque”, or a control law (see aiofranka.control_law). The targets move to the arm’s current pose.

set_freq(freq)[source]

Set the rate that set() holds [Hz], e.g. a policy’s.

set(attr, value)[source]

Set a target (“q_desired”, “ee_desired” or “torque”) or another attribute, then sleep to hold the rate of set_freq(), as a policy sends its actions: each call returns one period after the last one’s target time, whatever the caller did in between.

The controller’s event loop sets it, as NativeFrankaController.set() does: it wakes up every millisecond, which keeps the period to about a cycle, where a sleep of the caller’s could wake up milliseconds late.

Parameters:
  • attr (str) – Attribute to set

  • value – Its value, e.g. joint positions [rad] (7,) or a pose (4, 4)

move(qpos=[0.0, 0.0, 0.0, -1.57079, 0.0, 1.57079, -0.7853])[source]

Move to joint positions on a smooth, time-optimal trajectory, and hold them. Returns once every joint is within arrival_tolerance of them. Ctrl+C stops the motion, with the arm holding its last target.

Parameters:

qpos (array-like) – Joint positions [rad] (7,), by default the home pose

activate(config, check_tool=True, check_null_target=True)[source]

Apply a controller configuration, a YAML file or a dict: its mode, gains, TCP and policy rate (see NativeFrankaController.activate()).

Returns:

The configuration

Return type:

dict

check_tool(config)[source]

Check that the tool a configuration needs is Desk’s active end-effector profile, as activate() does.

Raises:

RuntimeError – If another tool is active, or Desk could not be read

set_tcp(transform)[source]

Set the point the OSC controls: a translation [m] (3,) or a pose (4, 4) in the flange frame.

register_law(law)[source]

Compile a control law (aiofranka.control_law) and make it a controller type; returns its name.

identify_payload(tool_length=0.2, tool_radius=0.1, floor=0.0, **kwargs)[source]

Identify the mass and center of mass of the tool on the flange: moves the arm through poses around the current one, about 3 minutes (see aiofranka.payload.identify_payload()). A controller that is not running is started for it and stopped after. It only measures: save the result with aiofranka.save_tool().

Returns:

The estimated tool, with standard errors

Return type:

PayloadEstimate

record(fields=None, seconds=60.0, path=None)[source]

Record every cycle of the loop, from its next one on (see NativeFrankaController.record()).

Returns:

The rows; stop() it, or use it in a with block

Return type:

Recording

loop_stats(reset=False)[source]

Timing of the loop since start, or since the last call with reset (NativeFrankaController.loop_stats()).

step()[source]

Run one cycle by hand while the loop is not running, e.g. to try a law in simulation.