Skip to main content

High-level control library for Franka robots.

Project description

High-Level Control Library for Franka Robots with Python and C++ Support

CI Publish Issues Releases LGPL

franky is a high-level control library for Franka robots, offering Python and C++ support. By providing a high-level control interface, franky eliminates the need for strict real-time programming at 1 kHz, making control from non-real-time environments, such as Python programs, feasible. Instead of relying on low-level control commands, franky expects high-level position or velocity targets and uses Ruckig to plan time-optimal trajectories in real-time.

Although Python does not provide real-time guarantees, franky strives to maintain as much real-time control as possible. Motions can be preempted at any moment, prompting franky to re-plan trajectories on the fly. To handle unforeseen situations—such as unexpected contact with the environment — franky includes a reaction system that allows for updating motion commands dynamically. Furthermore, most non-real-time functionality of libfranka, such as Gripper control is made directly available in Python.

Check out the tutorial and the examples for an introduction. The full documentation can be found at https://timschneider42.github.io/franky/.

🚀 Features

  • Control your Franka robot directly from Python in just a few lines! No more endless hours setting up ROS, juggling packages, or untangling dependencies. Just pip install — no ROS at all.

  • Four control modes: Cartesian position, Cartesian velocity, Joint position, Joint velocity franky uses Ruckig to generate smooth, time-optimal trajectories while respecting velocity, acceleration, and jerk limits.

  • Real-time control from Python and C++ Need to change the target while the robot’s moving? No problem. franky replans trajectories on the fly so that you can preempt motions anytime.

  • Reactive behavior Robots don’t always go according to plan. franky lets you define reactions to unexpected events—like contact with the environment — so you can change course in real-time.

  • Motion and reaction callbacks Want to monitor what’s happening under the hood? Add callbacks to your motions and reactions. They won’t block the control thread and are super handy for debugging or logging.

  • Things are moving too fast? Tune the robot's dynamics to your needs Adjust max velocity, acceleration, and jerk to match your setup or task. Fine control for smooth, safe operation.

  • Full Python access to the libfranka API Want to tweak impedance, read the robot state, set force thresholds, or mess with the Jacobian? Go for it. If libfranka supports it, chances are franky does, too.

📖 Python Quickstart Guide

Real-time kernel already installed and real-time permissions granted? Just install franky via

pip install franky-control

Otherwise, follow the setup instructions first.

Now we are already ready to go! Unlock the brakes in the web interface, activate FCI, and start coding:

from franky import *

robot = Robot("10.90.90.1")  # Replace this with your robot's IP

# Let's start slow (this lets the robot use a maximum of 5% of its velocity, acceleration, and jerk limits)
robot.relative_dynamics_factor = 0.05

# Move the robot 20cm along the relative X-axis of its end-effector
motion = CartesianMotion(Affine([0.2, 0.0, 0.0]), ReferenceType.Relative)
robot.move(motion)

If you are seeing server version mismatch errors, such as

franky.IncompatibleVersionException: libfranka: Incompatible library version (server version: 5, library version: 9)

then your Franka robot is either not on the most recent firmware version, or you are using the older Franka Panda model. In any case, it's no big deal; just check here which libfranka version you need and follow our instructions to install the appropriate franky wheels.

⚙️ Setup

To install franky, you have to follow three steps:

  1. Ensure that you are using a real-time kernel
  2. Ensure that the executing user has permission to run real-time applications
  3. Install franky via pip or build it from source

Installing a real-time kernel

In order for Franky to function properly, it requires the underlying OS to use a real-time kernel. Otherwise, you might see communication_constrains_violation errors.

To check whether your system is currently using a real-time kernel, type uname -a. You should see something like this:

$ uname -a
Linux [PCNAME] 5.15.0-1056-realtime #63-Ubuntu SMP PREEMPT_RT ...

If it does not say PREEMPT_RT, you are not currently running a real-time kernel.

There are multiple ways of installing a real-time kernel. You can build it from source or, if you are using Ubuntu, it can be enabled through Ubuntu Pro.

Allowing the executing user to run real-time applications

First, create a group realtime and add your user (or whoever is running franky) to this group:

sudo addgroup realtime
sudo usermod -a -G realtime $(whoami)

Afterward, add the following limits to the real-time group in /etc/security/limits.conf:

@realtime soft rtprio 99
@realtime soft priority 99
@realtime soft memlock 102400
@realtime hard rtprio 99
@realtime hard priority 99
@realtime hard memlock 102400

Log out and log in again to let the changes take effect.

To verify that the changes were applied, check if your user is in the realtime group:

$ groups
... realtime

If real-time is not listed in your groups, try rebooting.

Installing franky

To start using franky with Python and libfranka 0.16.0, just install it via

pip install franky-control

We also provide wheels for libfranka versions 0.7.1, 0.8.0, 0.9.2, 0.12.1, 0.13.3, 0.14.2, 0.17.0, and 0.18.0. They can be installed via

VERSION=0-9-2
wget https://github.com/TimSchneider42/franky/releases/latest/download/libfranka_${VERSION}_wheels.zip
unzip libfranka_${VERSION}_wheels.zip
pip install numpy
pip install --no-index --find-links=./dist franky-control

Using Docker

To use franky within Docker we provide a Dockerfile and accompanying docker-compose file.

git clone --recurse-submodules https://github.com/timschneider42/franky.git
cd franky/
docker compose build franky-run

To use another version of libfranka than the default (0.16.0), add a build argument:

docker compose build franky-run --build-arg LIBFRANKA_VERSION=0.9.2

To run the container:

docker compose run franky-run bash

The container requires access to the host machine's network and elevated user rights to allow the Docker user to set RT capabilities of the processes run from within it.

Can I use CUDA jointly with franky?

Yes. However, you need to set IGNORE_PREEMPT_RT_PRESENCE=1 during the installation and all subsequent updates of the CUDA drivers on the real-time kernel.

First, make sure that you have rebooted your system after installing the real-time kernel. Then, add IGNORE_PREEMPT_RT_PRESENCE=1 to /etc/environment, call export IGNORE_PREEMPT_RT_PRESENCE=1 to also set it in the current session, and follow the instructions of Nvidia to install CUDA on your system.

If you are on Ubuntu, you can also use this script to install CUDA on your real-time system:

# Download the script
wget https://raw.githubusercontent.com/timschneider42/franky/master/tools/install_cuda_realtime.bash

# Inspect the script to ensure it does what you expect

# Make it executable
chmod +x install_cuda_realtime.bash

# Execute the script
./install_cuda_realtime.bash

Alternatively, if you are a cowboy and do not care about security, you can also use this one-liner to directly call the script without checking it:

bash <(wget -qO- https://raw.githubusercontent.com/timschneider42/franky/master/tools/install_cuda_realtime.bash)

Robot is connected to a different machine?

Check out franky-remote! It lets you run franky remotely via RPC with minimal effort.

Please note that I’m not involved in the development of franky-remote, so I cannot take any liability for its use. If you decide to use it, please ensure that you credit the developers of the repository for their work.

Building franky

franky is based on libfranka, Eigen for transformation calculations and pybind11 for the Python bindings. As the Franka is sensitive to acceleration discontinuities, it requires jerk-constrained motion generation, for which franky uses the Ruckig community version for Online Trajectory Generation (OTG).

After installing the dependencies (the exact versions can be found here), you can build and install franky via

git clone --recurse-submodules git@github.com:timschneider42/franky.git
cd franky
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
make install

To use franky, you can also include it as a subproject in your parent CMake via add_subdirectory(franky) and then target_link_libraries(<target> franky).

If you need only the Python module, you can install franky via

pip install .

Make sure that the built library _franky.cpython-3**-****-linux-gnu.so is in the Python path, e.g. by adjusting PYTHONPATH accordingly.

Building franky with Docker

For building franky and its wheels, we provide another Docker container that can also be launched using docker-compose:

docker compose build franky-build
docker compose run --rm franky-build run-tests  # To run the tests
docker compose run --rm franky-build build-wheels  # To build wheels for all supported python versions

📚 Tutorial

franky comes with both a C++ and Python API that differ only regarding real-time capability. We will introduce both languages next to each other. In your C++ project, just include include <franky.hpp> and link the library. For Python, just import franky. As a first example, only four lines of code are needed for simple robotic motions.

#include <franky.hpp>
using namespace franky;

// Connect to the robot with the FCI IP address
Robot robot("10.90.90.1");

// Reduce velocity and acceleration of the robot
robot.setRelativeDynamicsFactor(0.05);

// Move the end-effector 20cm in positive x-direction
auto motion = std::make_shared<CartesianMotion>(RobotPose(Affine({0.2, 0.0, 0.0}), 0.0), ReferenceType::Relative);

// Finally move the robot
robot.move(motion);

The corresponding program in Python is

from franky import Affine, CartesianMotion, Robot, ReferenceType

robot = Robot("10.90.90.1")
robot.relative_dynamics_factor = 0.05

motion = CartesianMotion(Affine([0.2, 0.0, 0.0]), ReferenceType.Relative)
robot.move(motion)

Before executing any code, make sure that you have enabled the Franka Control Interface (FCI) in the Franka UI web interface.

Furthermore, we will introduce methods for geometric calculations, for moving the robot according to different motion types, how to implement real-time reactions and changing waypoints in real time, as well as controlling the gripper.

🧮 Geometry

franky.Affine is a python wrapper for Eigen::Affine3d. It is used for Cartesian poses, frames and transformation. franky adds its own constructor, which takes a position and a quaternion as inputs:

import math
from scipy.spatial.transform import Rotation
from franky import Affine

z_translation = Affine([0.0, 0.0, 0.5])

quat = Rotation.from_euler("xyz", [0, 0, math.pi / 2]).as_quat()
z_rotation = Affine([0.0, 0.0, 0.0], quat)

combined_transformation = z_translation * z_rotation

In all cases, distances are in [m] and rotations in [rad].

🤖 Robot

franky exposes most of the libfanka API for Python. Moreover, we added methods to adapt the dynamic limits of the robot for all motions.

from franky import *

robot = Robot("10.90.90.1")

# Recover from errors
robot.recover_from_errors()

# Set velocity, acceleration, and jerk to 5% of the maximum
robot.relative_dynamics_factor = 0.05

# Alternatively, you can define each constraint individually
robot.relative_dynamics_factor = RelativeDynamicsFactor(
    velocity=0.1, acceleration=0.05, jerk=0.1
)

# Or, for more fine-grained access, set individual limits
robot.translation_velocity_limit.set(3.0)
robot.rotation_velocity_limit.set(2.5)
robot.elbow_velocity_limit.set(2.62)
robot.translation_acceleration_limit.set(9.0)
robot.rotation_acceleration_limit.set(17.0)
robot.elbow_acceleration_limit.set(10.0)
robot.translation_jerk_limit.set(4500.0)
robot.rotation_jerk_limit.set(8500.0)
robot.elbow_jerk_limit.set(5000.0)
robot.joint_velocity_limit.set([2.62, 2.62, 2.62, 2.62, 5.26, 4.18, 5.26])
robot.joint_acceleration_limit.set([10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0])
robot.joint_jerk_limit.set([5000.0, 5000.0, 5000.0, 5000.0, 5000.0, 5000.0, 5000.0])
# By default, these limits are set to their respective maxima (the values shown here)

# Get the max of each limit (as provided by Franka) with the max function, e.g.:
print(robot.joint_jerk_limit.max)

Robot State

The robot state can be retrieved by accessing the following properties:

from franky import *

robot = Robot("10.90.90.1")

# Get the current state as `franky.RobotState`. See the documentation for a list of fields.
state = robot.state

# Get the robot's cartesian state
cartesian_state = robot.current_cartesian_state
robot_pose = cartesian_state.pose  # Contains end-effector pose and elbow position
ee_pose = robot_pose.end_effector_pose
elbow_pos = robot_pose.elbow_state
robot_velocity = cartesian_state.velocity  # Contains end-effector twist and elbow velocity
ee_twist = robot_velocity.end_effector_twist
elbow_vel = robot_velocity.elbow_velocity

# Get the robot's joint state
joint_state = robot.current_joint_state
joint_pos = joint_state.position
joint_vel = joint_state.velocity

# Use the robot model to compute kinematics
q = [-0.3, 0.1, 0.3, -1.4, 0.1, 1.8, 0.7]
f_t_ee = Affine()
ee_t_k = Affine()
ee_pose_kin = robot.model.pose(Frame.EndEffector, q, f_t_ee, ee_t_k)

# Get the Jacobian of the current robot state
jacobian = robot.model.body_jacobian(Frame.EndEffector, state)

# Alternatively, just get the URDF as a string and do the kinematics computation yourself (only
# for libfranka >= 0.15.0)
urdf_model = robot.model_urdf

For a full list of state-related features, check the Robot and Model sections of the documentation.

🏃‍♂️ Motion Types

franky currently supports four different impedance control modes: joint position control, joint velocity control, cartesian position control, and cartesian velocity control. Each of these control modes is invoked by passing the robot an appropriate Motion object.

In the following, we provide a brief example for each motion type implemented by franky in Python. The C++ interface is generally analogous, though some variable and method names are different because we follow PEP 8 naming conventions in Python and Google naming conventions in C++.

All units are in $m$, $\frac{m}{s}$, $\textit{rad}$, or $\frac{\textit{rad}}{s}$.

Joint Position Control

from franky import *

# A point-to-point motion in the joint space
m_jp1 = JointMotion([-0.3, 0.1, 0.3, -1.4, 0.1, 1.8, 0.7])

# A motion in joint space with multiple waypoints. The robot will stop at each of these
# waypoints. If you want the robot to move continuously, you have to specify a target velocity
# at every waypoint as shown in the example following this one.
m_jp2 = JointWaypointMotion(
    [
        JointWaypoint([-0.3, 0.1, 0.3, -1.4, 0.1, 1.8, 0.7]),
        JointWaypoint([0.0, 0.3, 0.3, -1.5, -0.2, 1.5, 0.8]),
        JointWaypoint([0.1, 0.4, 0.3, -1.4, -0.3, 1.7, 0.9]),
    ]
)

# Intermediate waypoints also permit specifying target velocities. The default target velocity
# is 0, meaning that the robot will stop at every waypoint.
m_jp3 = JointWaypointMotion(
    [
        JointWaypoint([-0.3, 0.1, 0.3, -1.4, 0.1, 1.8, 0.7]),
        JointWaypoint(
            JointState(
                position=[0.0, 0.3, 0.3, -1.5, -0.2, 1.5, 0.8],
                velocity=[0.1, 0.0, 0.0, 0.0, -0.0, 0.0, 0.0],
            )
        ),
        JointWaypoint([0.1, 0.4, 0.3, -1.4, -0.3, 1.7, 0.9]),
    ]
)

# Stop the robot in joint position control mode. The difference between JointStopMotion to other
# stop-motions, such as CartesianStopMotion, is that JointStopMotion stops the robot in joint
# position control mode while CartesianStopMotion stops it in cartesian pose control mode. The
# difference becomes relevant when asynchronous move commands are being sent or reactions are
# being used(see below).
m_jp4 = JointStopMotion()

Joint Velocity Control

from franky import *

# Accelerate to the given joint velocity and hold it. After 1000ms, stop the robot again.
m_jv1 = JointVelocityMotion(
    [0.1, 0.3, -0.1, 0.0, 0.1, -0.2, 0.4], duration=Duration(1000)
)

# Joint velocity motions also support waypoints. Unlike in joint position control, a joint
# velocity waypoint is a target velocity to be reached. This particular example first
# accelerates the joints, holds the velocity for 1s, then reverses direction for 2s, reverses
# direction again for 1s, and finally stops. It is important not to forget to stop the robot
# at the end of such a sequence, as it will otherwise throw an error.
m_jv2 = JointVelocityWaypointMotion(
    [
        JointVelocityWaypoint(
            [0.1, 0.3, -0.1, 0.0, 0.1, -0.2, 0.4], hold_target_duration=Duration(1000)
        ),
        JointVelocityWaypoint(
            [-0.1, -0.3, 0.1, -0.0, -0.1, 0.2, -0.4],
            hold_target_duration=Duration(2000),
        ),
        JointVelocityWaypoint(
            [0.1, 0.3, -0.1, 0.0, 0.1, -0.2, 0.4], hold_target_duration=Duration(1000)
        ),
        JointVelocityWaypoint([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
    ]
)

# Stop the robot in joint velocity control mode.
m_jv3 = JointVelocityStopMotion()

Cartesian Position Control

import math
from scipy.spatial.transform import Rotation
from franky import *

# Move to the given target pose
quat = Rotation.from_euler("xyz", [0, 0, math.pi / 2]).as_quat()
m_cp1 = CartesianMotion(Affine([0.4, -0.2, 0.3], quat))

# With target elbow angle (otherwise, the Franka firmware will choose by itself)
m_cp2 = CartesianMotion(
    RobotPose(Affine([0.4, -0.2, 0.3], quat), elbow_state=ElbowState(0.3))
)

# A linear motion in Cartesian space relative to the initial position
# (Note that this motion is relative both in position and orientation. Hence, when the robot's
# end-effector is oriented differently, it will move in a different direction)
m_cp3 = CartesianMotion(Affine([0.2, 0.0, 0.0]), ReferenceType.Relative)

# Generalization of CartesianMotion that allows for multiple waypoints. The robot will stop at
# each of these waypoints. If you want the robot to move continuously, you have to specify a
# target velocity at every waypoint as shown in the example following this one.
m_cp4 = CartesianWaypointMotion(
    [
        CartesianWaypoint(
            RobotPose(Affine([0.4, -0.2, 0.3], quat), elbow_state=ElbowState(0.3))
        ),
        # The following waypoint is relative to the prior one and 50% slower
        CartesianWaypoint(
            Affine([0.2, 0.0, 0.0]),
            ReferenceType.Relative,
            RelativeDynamicsFactor(0.5, 1.0, 1.0),
        ),
    ]
)

# Cartesian waypoints permit specifying target velocities
m_cp5 = CartesianWaypointMotion(
    [
        CartesianWaypoint(Affine([0.5, -0.2, 0.3], quat)),
        CartesianWaypoint(
            CartesianState(
                pose=Affine([0.4, -0.1, 0.3], quat), velocity=Twist([-0.01, 0.01, 0.0])
            )
        ),
        CartesianWaypoint(Affine([0.3, 0.0, 0.3], quat)),
    ]
)

# Stop the robot in Cartesian position control mode.
m_cp6 = CartesianStopMotion()

Cartesian Velocity Control

from franky import *

# A Cartesian velocity motion with linear (first argument) and angular (second argument)
# components
m_cv1 = CartesianVelocityMotion(Twist([0.2, -0.1, 0.1], [0.1, -0.1, 0.2]))

# With target elbow velocity
m_cv2 = CartesianVelocityMotion(
    RobotVelocity(Twist([0.2, -0.1, 0.1], [0.1, -0.1, 0.2]), elbow_velocity=-0.2)
)

# Cartesian velocity motions also support multiple waypoints. Unlike in Cartesian position
# control, a Cartesian velocity waypoint is a target velocity to be reached. This particular
# example first accelerates the end-effector, holds the velocity for 1s, then reverses
# direction for 2s, reverses direction again for 1s, and finally stops. It is important not to
# forget to stop the robot at the end of such a sequence, as it will otherwise throw an error.
m_cv4 = CartesianVelocityWaypointMotion(
    [
        CartesianVelocityWaypoint(
            Twist([0.2, -0.1, 0.1], [0.1, -0.1, 0.2]),
            hold_target_duration=Duration(1000),
        ),
        CartesianVelocityWaypoint(
            Twist([-0.2, 0.1, -0.1], [-0.1, 0.1, -0.2]),
            hold_target_duration=Duration(2000),
        ),
        CartesianVelocityWaypoint(
            Twist([0.2, -0.1, 0.1], [0.1, -0.1, 0.2]),
            hold_target_duration=Duration(1000),
        ),
        CartesianVelocityWaypoint(Twist()),
    ]
)

# Stop the robot in Cartesian velocity control mode.
m_cv6 = CartesianVelocityStopMotion()

Relative Dynamics Factors

Every motion and waypoint type allows for adapting the dynamics (velocity, acceleration, and jerk) by setting the respective relative_dynamics_factor parameter. This parameter can also be set for the robot globally, as shown below, or in the robot.move command. Crucially, relative dynamics factors on different layers (robot, move command, and motion) do not override each other but rather get multiplied. Hence, a relative dynamics factor on a motion can only reduce the dynamics of the robot and never increase them.

There is one exception to this rule, and that is if any layer sets the relative dynamics factor to RelativeDynamicsFactor.MAX_DYNAMICS. This will cause the motion to be executed with maximum velocity, acceleration, and jerk limits, independently of the relative dynamics factors of the other layers. This feature should only be used to abruptly stop the robot in case of an unexpected environment contact, as executing Other motions with it are likely to lead to a discontinuity error and might be dangerous.

Executing Motions

The real robot can be moved by applying a motion to the robot using move:

# Before moving the robot, set an appropriate dynamics factor. We start small:
robot.relative_dynamics_factor = 0.05
# or alternatively, to control the scaling of velocity, acceleration, and jerk limits
# separately:
robot.relative_dynamics_factor = RelativeDynamicsFactor(0.05, 0.1, 0.15)
# If these values are set too high, you will see discontinuity errors

robot.move(m_jp1)

# We can also set a relative dynamics factor in the move command. It will be multiplied by
# the other relative dynamics factors (robot and motion if present).
robot.move(m_jp2, relative_dynamics_factor=0.8)

Motion Callbacks

All motions support callbacks, which will be invoked in every control step at 1kHz. Callbacks can be attached as follows:

def cb(
        robot_state: RobotState,
        time_step: Duration,
        rel_time: Duration,
        abs_time: Duration,
        control_signal: JointPositions,
):
    print(f"At time {abs_time}, the target joint positions were {control_signal.q}")


m_jp1.register_callback(cb)
robot.move(m_jp1)

Note that in Python, these callbacks are not executed in the control thread since they would otherwise block it. Instead, they are put in a queue and executed by another thread. While this scheme ensures that the control thread can always run, it cannot prevent the queue from growing indefinitely when the callbacks take more time to execute than it takes for new callbacks to be queued. Hence, callbacks might be executed significantly after they were queued if they take a long time to execute.

⚡ Real-Time Reactions

By adding reactions to the motion data, the robot can react to unforeseen events. In the Python API, you can define conditions by using a comparison between a robot's value and a given threshold. If the threshold is exceeded, the reaction fires.

from franky import CartesianMotion, Affine, ReferenceType, Measure, Reaction

motion = CartesianMotion(Affine([0.0, 0.0, 0.1]), ReferenceType.Relative)  # Move down 10cm

# It is important that the reaction motion uses the same control mode as the original motion.
# Hence, we cannot register a JointMotion as a reaction motion to a CartesianMotion.
# Move up by 1cm
reaction_motion = CartesianMotion(Affine([0.0, 0.0, -0.01]), ReferenceType.Relative)

# Trigger reaction if the Z force is greater than 30N
reaction = Reaction(Measure.FORCE_Z > 5.0, reaction_motion)
motion.add_reaction(reaction)

robot.move(motion)

Possible values to measure are

  • Measure.FORCE_X, Measure.FORCE_Y, Measure.FORCE_Z: Force in X, Y and Z direction
  • Measure.REL_TIME: Time in seconds since the current motion started
  • Measure.ABS_TIME: Time in seconds since the initial motion started

The difference between Measure.REL_TIME and Measure.ABS_TIME is that Measure.REL_TIME is reset to zero whenever a new motion starts (either by calling Robot.move or as a result of a triggered Reaction). Measure.ABS_TIME, on the other hand, is only reset to zero when a motion terminates regularly without being interrupted and the robot stops moving. Hence, Measure.ABS_TIME measures the total time in which the robot has moved without interruption.

Measure values support all classical arithmetic operations, like addition, subtraction, multiplication, division, and exponentiation (both as base and exponent).

normal_force = (Measure.FORCE_X ** 2 + Measure.FORCE_Y ** 2 + Measure.FORCE_Z ** 2) ** 0.5

With arithmetic comparisons, conditions can be generated.

normal_force_within_bounds = normal_force < 30.0
time_up = Measure.ABS_TIME > 10.0

Conditions support negation, conjunction (and), and disjunction (or):

abort = ~normal_force_within_bounds | time_up
fast_abort = ~normal_force_within_bounds | time_up

To check whether a reaction has fired, a callback can be attached:

from franky import RobotState


def reaction_callback(robot_state: RobotState, rel_time: float, abs_time: float):
    print(f"Reaction fired at {abs_time}.")


reaction.register_callback(reaction_callback)

Similar to the motion callbacks, in Python, reaction callbacks are not executed in real-time but in a regular thread with lower priority to ensure that the control thread does not get blocked. Thus, the callbacks might fire substantially after the reaction has fired, depending on the time it takes to execute them.

In C++, you can additionally use lambdas to define more complex behaviours:

auto motion = CartesianMotion(
  RobotPose(Affine({0.0, 0.0, 0.2}), 0.0), ReferenceType::Relative);

// Stop motion if force is over 10N
auto stop_motion = StopMotion<franka::CartesianPose>()

motion
  .addReaction(
    Reaction(
      Measure::ForceZ() > 10.0,  // [N],
      stop_motion))
  .addReaction(
    Reaction(
      Condition(
        [](const franka::RobotState& state, double rel_time, double abs_time) {
          // Lambda condition
          return state.current_errors.self_collision_avoidance_violation;
        }),
      [](const franka::RobotState& state, double rel_time, double abs_time) {
        // Lambda reaction motion generator
        // (we are just returning a stop motion, but there could be arbitrary
        // logic here for generating reaction motions)
        return StopMotion<franka::CartesianPose>();
      })
    ));

robot.move(motion)

⏱️ Real-Time Motions

By setting the asynchronous parameter of Robot.move to True, the function does not block until the motion finishes. Instead, it returns immediately and, thus, allows the main thread to set new motions asynchronously.

import time
from franky import Affine, CartesianMotion, Robot, ReferenceType

robot = Robot("10.90.90.1")
robot.relative_dynamics_factor = 0.05

motion1 = CartesianMotion(Affine([0.2, 0.0, 0.0]), ReferenceType.Relative)
robot.move(motion1, asynchronous=True)

time.sleep(0.5)
# Note that, similar to reactions, when preempting active motions with new motions, the
# control mode cannot change. Hence, we cannot use, e.g., a JointMotion here.
motion2 = CartesianMotion(Affine([0.2, 0.0, 0.0]), ReferenceType.Relative)
robot.move(motion2, asynchronous=True)

By calling Robot.join_motion, the main thread can be synchronized with the motion thread, as it will block until the robot finishes its motion.

robot.join_motion()

Note that when exceptions occur during the asynchronous execution of a motion, they will not be thrown immediately. Instead, the control thread stores the exception and terminates. The next time Robot.join_motion or Robot.move is called, they will throw the stored exception in the main thread. Hence, after an asynchronous motion has finished, make sure to call Robot.join_motion to ensure being notified of any exceptions that occurred during the motion.

👌 Gripper

In the franky::Gripper class, the default gripper force and gripper speed can be set. Then, in addition to the libfranka commands, the following helper methods can be used:

#include <franky.hpp>
#include <chrono>
#include <future>

auto gripper = franky::Gripper("10.90.90.1");

double speed = 0.02; // [m/s]
double force = 20.0; // [N]

// Move the fingers to a specific width (5cm)
bool success = gripper.move(0.05, speed);

// Grasp an object of unknown width
success &= gripper.grasp(0.0, speed, force, epsilon_outer=1.0);

// Get the width of the grasped object
double width = gripper.width();

// Release the object
gripper.open(speed);

// There are also asynchronous versions of the methods
std::future<bool> success_future = gripper.moveAsync(0.05, speed);

// Wait for 1s
if (!success_future.wait_for(std::chrono::seconds(1)) == std::future_status::ready) {
  // Get the result
  std::cout << "Success: " << success_future.get() << std::endl;
} else {
  gripper.stop();
  success_future.wait();
  std::cout << "Gripper motion timed out." << std::endl;
}

The Python API follows the C++ API closely:

import franky

gripper = franky.Gripper("10.90.90.1")

speed = 0.02  # [m/s]
force = 20.0  # [N]

# Move the fingers to a specific width (5cm)
success = gripper.move(0.05, speed)

# Grasp an object of unknown width
success &= gripper.grasp(0.0, speed, force, epsilon_outer=1.0)

# Get the width of the grasped object
width = gripper.width

# Release the object
gripper.open(speed)

# There are also asynchronous versions of the methods
success_future = gripper.move_async(0.05, speed)

# Wait for 1s
if success_future.wait(1):
    print(f"Success: {success_future.get()}")
else:
    gripper.stop()
    success_future.wait()
    print("Gripper motion timed out.")

Accessing the Web Interface API

For Franka robots, control happens via the Franka Control Interface (FCI), which has to be enabled through the Franka UI in the robot's web interface. The Franka UI also provides methods for locking and unlocking the brakes, setting the execution mode, and executing the safety self-test. However, sometimes you may want to access these methods programmatically, e.g., for automatically unlocking the brakes before starting a motion, or automatically executing the self-test after 24h of continuous execution.

For that reason, franky provides a RobotWebSession class that allows you to access the web interface API of the robot. Note that directly accessing the web interface API is not officially supported and documented by Franka. Hence, use this feature at your own risk.

A typical automated workflow could look like this:

import franky

with franky.RobotWebSession("10.90.90.1", "username", "password") as robot_web_session:
    # First take control
    try:
        # Try taking control. The session currently holding control has to release it in order
        # for this session to gain control. In the web interface, a notification will show
        # prompting the user to release control. If the other session is another
        # franky.RobotWebSession, then the `release_control` method can be called on the other
        # session to release control.
        robot_web_session.take_control(wait_timeout=10.0)
    except franky.TakeControlTimeoutError:
        # If nothing happens for 10s, we try to take control forcefully. This is particularly
        # useful if the session holding control is dead. Taking control by force requires the
        # user to manually push the blue button close to the robot's wrist.
        robot_web_session.take_control(wait_timeout=30.0, force=True)

    # Unlock the brakes
    robot_web_session.unlock_brakes()

    # Enable the FCI
    robot_web_session.enable_fci()

    # Create a franky.Robot instance and do whatever you want
    ...

    # Disable the FCI
    robot_web_session.disable_fci()

    # Lock brakes
    robot_web_session.lock_brakes()

In case you are running the robot for longer than 24h you will have noticed that you have to do a safety self-test every 24h. RobotWebSession allows to automate this task as well:

import time
import franky

with franky.RobotWebSession("10.90.90.1", "username", "password") as robot_web_session:
    # Execute self-test if the time until self-test is less than 5 minutes.
    if robot_web_session.get_system_status()["safety"]["timeToTd2"] < 300:
        robot_web_session.disable_fci()
        robot_web_session.lock_brakes()
        time.sleep(1.0)

        robot_web_session.execute_self_test()

        robot_web_session.unlock_brakes()
        robot_web_session.enable_fci()
        time.sleep(1.0)

        # Recreate your franky.Robot instance as the FCI has been disabled and re-enabled
        ...

robot_web_session.get_system_status() contains more information than just the time until self-test, such as the current execution mode, whether the brakes are locked, whether the FCI is enabled, and more.

If you want to call other API functions, you can use the RobotWebSession.send_api_request and RobotWebSession.send_control_api_request methods. See robot_web_session.py for an example of how to use these methods.

🛠️ Development

franky is currently tested against the following versions

  • libfranka 0.7.1, 0.8.0, 0.9.2, 0.10.0, 0.11.0, 0.12.1, 0.13.3, 0.14.2, 0.15.3, 0.16.1, 0.17.0, 0.18.0
  • Eigen 3.4.0
  • Pybind11 2.13.6
  • POCO 1.12.5p2
  • Pinocchio 3.4.0
  • Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
  • Catch2 2.13.8 (for testing only)

📜 License

For non-commercial applications, this software is licensed under the LGPL v3.0. If you want to use franky within commercial applications or under a different license, please contact us for individual agreements.

🔍 Differences to frankx

franky started originally as a fork of frankx, though both codebase and functionality differ substantially from frankx by now. Aside from bug fixes and general performance improvements, franky provides the following new features/improvements:

Contributing

If you wish to contribute to this project, you are welcome to create a pull request. Please run the pre-commit hooks before submitting your pull request. To install the pre-commit hooks, run:

  1. Install pre-commit
  2. Install the Git hooks by running pre-commit install or, alternatively, run pre-commit run --all-files manually.

Project details


Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

franky_control-1.1.3-cp314-cp314t-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp314-cp314t-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp314-cp314-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp314-cp314-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp313-cp313t-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.13tmanylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp313-cp313t-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.13tmanylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp313-cp313-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp313-cp313-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp312-cp312-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp311-cp311-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp310-cp310-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp310-cp310-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp39-cp39-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp39-cp39-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.28+ x86-64

franky_control-1.1.3-cp38-cp38-manylinux_2_34_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.8manylinux: glibc 2.34+ x86-64

franky_control-1.1.3-cp38-cp38-manylinux_2_28_x86_64.whl (5.2 MB view details)

Uploaded CPython 3.8manylinux: glibc 2.28+ x86-64

File details

Details for the file franky_control-1.1.3-cp314-cp314t-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp314-cp314t-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 1fc4449aa3b8df375fe193b138b71edf577660a64f178f4316bf925866624f92
MD5 576d785cf4ab33d57fded5b5edc22891
BLAKE2b-256 5ecf5486ce7db7beff45a63e7197c9b0451ff4cbadadfaa44e61e47d979649d6

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp314-cp314t-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp314-cp314t-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp314-cp314t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6119296e99f2c4abcdbd4ea64c36f3c4cf0fec560267b4507e491b4b4391b617
MD5 def2342dade17c9e882c365a01a9c3d6
BLAKE2b-256 545934d2c45c4b30d90604f8ec1a07d1590207607976bef11bdaccd234a5e472

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp314-cp314t-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp314-cp314-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp314-cp314-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 e942590e41c759f6b0cd657fafc09287f4df0fec191adb61807b0e3810ea26ff
MD5 2b6be77dd2a4be88423dc7709830d273
BLAKE2b-256 297c7fc81e95610a7f60087ab8d4c776c29646682f5c43cf0ddb6aea8da7dae7

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp314-cp314-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp314-cp314-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 eaed4ce0e92c8ae9ef8e40ac3e8d6a6395dae48f80dcdc8f9b45505d68503e4a
MD5 4b8c9b386741fdcb567f336132093d6c
BLAKE2b-256 e04caf0ec0588040402fde4d598a6e60ae8857ea14413e5c32227db01111be9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp314-cp314-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp313-cp313t-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp313-cp313t-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 861827ddf3af78713c9572c51833441f737e6426cd358658a2553d2758b6870d
MD5 d6439c096e2f5d9f3df7ab3ad8faba3f
BLAKE2b-256 7645c702b4f917f7bbf6aeef87af2440f6485dfa6025f4081ebd467a29fda091

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp313-cp313t-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp313-cp313t-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp313-cp313t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 48c346ce5a26fa50c39f7e8a88f88849c143504bd480f2ddb852daffc8330066
MD5 9fd95e6d2dc757fec75221523baa0ec4
BLAKE2b-256 a8135e90a7cce28bad480225719b47f396f5902f53e44363348c88c403345066

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp313-cp313t-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 7a94c9a55bb3cd494b277c3515772425f718249897f3841a13be8cc02819da7a
MD5 58296a4a3d066c70ed854004e6cf3016
BLAKE2b-256 5cad8d9bfd9065c3a79bdd8eeafb2620d979cf562cda25afe42dcce2ff1dbd4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp313-cp313-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 956fb37e1c12ad6af7cddf056bb4f25e87b32e8c47f8e42a3de9f2cbc5cd3de1
MD5 d085b1877ae55e1eb8c7dec110e03a46
BLAKE2b-256 ba3ea4800c4b3eabc5487ad08b8b2e421b6a3ef57ce1d8e1c3bd14a3f4eb1f47

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp313-cp313-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 d7ea5cb78357beb21ca4ff95dcf2c0984f58c037ca1768d87577934ed608edda
MD5 a3abf1f1699418556b7726a86a4e72ad
BLAKE2b-256 8ab08e7665260625801cca44ed265aa0a801955007c9a8223768fa0501eac5c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp312-cp312-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5c47f53abfd0740f43dc411fd2c5a189f70ad476526b4acfbfe6c042116fbfb5
MD5 2cc0604294f6971e0de3945aa645d9c3
BLAKE2b-256 1e1456d8a981f6a12c24fa37eac67a45d2b3db7219bbd78d3e25cddf3f73de90

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp312-cp312-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 b93caefa0910fbda61b4df603525695aadb1ae3f68ebe6660c5d34348c998201
MD5 91bd1651618e51384c70b6060f2d5f7d
BLAKE2b-256 ac4d936ebef8e6ea6a84c9ad7320c359664dd094d5f42a7cf9abed6ceeb4a25f

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp311-cp311-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ea74dd8f925d1b191de4b1bec304c5e5dfaf19dc90e7d447f3aa1b0ee226e600
MD5 8a9c84b2bf58a6c24d8f136bc5a96455
BLAKE2b-256 cc2087ad00c2e5dfa922ebff935690fc32b85dc6b2c505b235b8436213a38f9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp311-cp311-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 a3f39c0616f24168f98961db54a972c096b12d6388aad2e064bc4620f3579a1e
MD5 2b044a866a042d44cf8ee136edcb37a8
BLAKE2b-256 f6f224de0c4f628fae95842849e8e75ec523a30ce2ce6fbd910e652b719fec82

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp310-cp310-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 abcbac6680d1d262e0cb1f13c17b39b6946f5e54b2912c43ba9595c988d15d24
MD5 f70711b21b49481afb5521e7cc929868
BLAKE2b-256 8eb82b3c09c8977cbf6b27121c9f59058ff6db648ce9377d00274fa527812548

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp310-cp310-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp39-cp39-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp39-cp39-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 2aebf567a3bb7f3d1328eeae66ddb2537c3165dc01b8a901fe494d6bb3fd9a82
MD5 f6ddc40c96204862e599fc3086162fee
BLAKE2b-256 3d97a543c3ebb438b0463b97fa2b1077395595ed30eebb965c670063fadcc28c

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp39-cp39-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp39-cp39-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp39-cp39-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7d34656da66c5cfd20d2c6c8f52cf5d684217a7ed08a59ac4fedbba39285ed1f
MD5 1790d6a68aa29d041a57d4ab94fc7869
BLAKE2b-256 d16934725ca5c1f2b6a374bcadcf98c68dacaf3c1a29b9459b27cee79290cf51

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp39-cp39-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp38-cp38-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp38-cp38-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 6918d8a61924706ec4b219d867deda15a5606491e3c8507f663e05cf4d9d9472
MD5 da562579696c48e4bbb8e11c32852587
BLAKE2b-256 858ecabdd3b64380890a658a103a02d633fd145eb36f0c77c8c98d58fa7ec731

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp38-cp38-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file franky_control-1.1.3-cp38-cp38-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for franky_control-1.1.3-cp38-cp38-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 68ba19c59c222b95582852b4a0aae4682a6de63daa2b2778fff050ab20edc05e
MD5 864d30aacc795b42ebb6bf05d7f3bbdc
BLAKE2b-256 fa2aab3a9e7a86a1ff58703568bb17ee9c3294ee4d77a548b618bf43de7128e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for franky_control-1.1.3-cp38-cp38-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on TimSchneider42/franky

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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