Skip to content

Commit a1ad886

Browse files
committed
Merge branch 'feat/pylibfranka_async'
2 parents 138d4be + 6ce8b75 commit a1ad886

File tree

12 files changed

+460
-365
lines changed

12 files changed

+460
-365
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@ All notable changes to libfranka in this file.
77
#### Changed
88
- Hotfix to avoid torques discontinuity false positives due to robot state float precision change.
99

10+
### pylibfranka - Python
11+
#### Added
12+
- Async control python bindings
13+
- Async joint positions control example from C++.
14+
1015
## [0.19.0]
1116
### libfranka - C++
1217
#### Changed
1318
- To support franka_ros2, we added an option for the async position control to base the `getFeedback` function on a robot state received via `franka_hardware` instead of querying the robot directly.
1419
- Format libfranka debian package to inlclude ubuntu code name and arch: libfranka_VERSION_CODENAME_ARCH.deb
1520
- Added build containers to support Ubuntu 22.04 and 24.04
1621

22+
1723
## [0.18.2]
1824
Requires Franka Research 3 System Version >= 5.9.0
1925

pylibfranka/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ find_package(pinocchio REQUIRED)
2020
# Create Python module
2121
pybind11_add_module(_pylibfranka
2222
src/pylibfranka.cpp
23+
src/async_control.cpp
24+
src/gripper.cpp
25+
# add more source files as needed for next splits
2326
)
2427

2528
# Include directories

pylibfranka/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,14 @@ The gripper example:
161161
- Attempts to grasp an object with specified parameters
162162
- Verifies successful grasping
163163
- Releases the object
164+
165+
### Async Joint Position Control Example
166+
167+
You can also use the async API to control the robot in a low-rate fashion, e.g. 50Hz.
168+
This allows you to specify joint position setpoints without the need for a blocking loop.
169+
170+
```bash
171+
cd examples
172+
python3 async_position_control.py --ip <robot_ip>
173+
```
174+

pylibfranka/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
RobotMode,
2929
RobotState,
3030
Torques,
31+
AsyncPositionControlHandler
3132
)
3233
from ._version import __version__
3334

@@ -55,4 +56,5 @@
5556
"RobotMode",
5657
"RobotState",
5758
"Torques",
59+
"AsyncPositionControlHandler"
5860
]
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright (c) 2026 Franka Robotics GmbH
2+
# Apache-2.0
3+
4+
# This example demonstrates asynchronous position control of a Franka robot using the
5+
# pylibfranka library. It connects to the robot, sets up an asynchronous position control
6+
# handler, and continuously updates joint position targets in a loop until interrupted, leveraging
7+
# the latest low-rate control API.
8+
9+
import signal
10+
import sys
11+
import time
12+
import math
13+
import argparse
14+
import threading
15+
from datetime import timedelta
16+
17+
import pylibfranka as franka
18+
from example_common import setDefaultBehaviour
19+
20+
kDefaultMaximumVelocities = [0.655, 0.655, 0.655, 0.655, 1.315, 1.315, 1.315]
21+
kDefaultGoalTolerance = 10.0
22+
23+
motion_finished = False
24+
25+
26+
def signal_handler(sig, frame):
27+
global motion_finished
28+
if sig == signal.SIGINT:
29+
motion_finished = True
30+
31+
32+
def main():
33+
parser = argparse.ArgumentParser()
34+
parser.add_argument("--ip", type=str, default="localhost", help="Robot IP address")
35+
args = parser.parse_args()
36+
37+
signal.signal(signal.SIGINT, signal_handler)
38+
39+
try:
40+
robot = franka.Robot(args.ip, franka.RealtimeConfig.kIgnore)
41+
except Exception as e:
42+
print(f"Could not connect to robot: {e}")
43+
sys.exit(-1)
44+
45+
setDefaultBehaviour(robot)
46+
47+
initial_position = [0,
48+
-math.pi / 4,
49+
0,
50+
-3 * math.pi / 4,
51+
0,
52+
math.pi / 2,
53+
math.pi / 4]
54+
55+
time_elapsed = 0.0
56+
direction = 1.0
57+
time_since_last_log = 0.0
58+
59+
def calculate_joint_position_target(period_sec):
60+
nonlocal time_elapsed, direction, time_since_last_log
61+
62+
time_elapsed += period_sec
63+
64+
target_positions = [
65+
initial_position[i] + direction * 0.25
66+
for i in range(7)
67+
]
68+
69+
time_since_last_log += period_sec
70+
if time_since_last_log >= 1.0:
71+
direction *= -1.0
72+
time_since_last_log = 0.0
73+
74+
return franka.AsyncPositionControlHandler.JointPositionTarget(
75+
joint_positions=target_positions
76+
)
77+
78+
joint_position_control_configuration = \
79+
franka.AsyncPositionControlHandler.Configuration(
80+
maximum_joint_velocities=kDefaultMaximumVelocities,
81+
goal_tolerance=kDefaultGoalTolerance
82+
)
83+
84+
result = franka.AsyncPositionControlHandler.configure(robot,
85+
joint_position_control_configuration)
86+
87+
if result.error_message is not None:
88+
print(result.error_message)
89+
sys.exit(-1)
90+
91+
position_control_handler = result.handler
92+
target_feedback = position_control_handler.get_target_feedback()
93+
94+
time_step = 0.020 # 20 ms, 50 Hz
95+
96+
global motion_finished
97+
while not motion_finished:
98+
loop_start = time.monotonic()
99+
100+
target_feedback = position_control_handler.get_target_feedback()
101+
if target_feedback.error_message is not None:
102+
print(target_feedback.error_message)
103+
sys.exit(-1)
104+
105+
next_target = calculate_joint_position_target(time_step)
106+
command_result = position_control_handler.set_joint_position_target(next_target)
107+
108+
if command_result.error_message is not None:
109+
print(command_result.error_message)
110+
sys.exit(-1)
111+
112+
if time_elapsed > 10.0:
113+
position_control_handler.stop_control()
114+
motion_finished = True
115+
print("Control finished")
116+
break
117+
118+
sleep_time = time_step - (time.monotonic() - loop_start)
119+
if sleep_time > 0:
120+
time.sleep(sleep_time)
121+
122+
123+
if __name__ == "__main__":
124+
main()

pylibfranka/include/pygripper.h

Lines changed: 0 additions & 46 deletions
This file was deleted.

pylibfranka/include/pylibfranka.h

Lines changed: 0 additions & 104 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2026 Franka Robotics GmbH
2+
// Use of this source code is governed by the Apache-2.0 license, see LICENSE
3+
4+
// Expose async position control handler python bindings
5+
6+
#pragma once
7+
#include <pybind11/pybind11.h>
8+
9+
namespace py = pybind11;
10+
11+
namespace pylibfranka {
12+
13+
void bind_async_control(py::module& m);
14+
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2026 Franka Robotics GmbH
2+
// Use of this source code is governed by the Apache-2.0 license, see LICENSE
3+
4+
// Expose gripper-related python bindings
5+
6+
#pragma once
7+
#include <pybind11/pybind11.h>
8+
9+
namespace py = pybind11;
10+
11+
namespace pylibfranka {
12+
13+
void bind_gripper(py::module& m);
14+
15+
}

0 commit comments

Comments
 (0)