Examples
This page provides complete, working examples for common use cases, with Robot and
Controller. Example 2 shows the same motion for asyncio code, with NativeFrankaController.
Example 1: Simple Motion
Move the robot through positions:
import time
import aiofranka
robot = aiofranka.Robot("172.16.0.2")
with aiofranka.Controller(robot) as controller: # start() here, stop() at the end
home = [0, 0, 0, -1.57079, 0, 1.57079, -0.7853]
pose1 = [0, -0.785, 0, -2.356, 0, 1.571, 0.785]
for pose in [home, pose1, home]:
print(f"Moving to: {pose}")
controller.move(pose)
time.sleep(1.0)
Example 2: Simple Motion with asyncio
Same motion in an asyncio program:
import asyncio
from aiofranka import RobotInterface, NativeFrankaController
async def simple_motion():
controller = NativeFrankaController(RobotInterface("172.16.0.2"))
await controller.start()
try:
home = [0, 0, 0, -1.57079, 0, 1.57079, -0.7853]
pose1 = [0, -0.785, 0, -2.356, 0, 1.571, 0.785]
for pose in [home, pose1, home]:
print(f"Moving to: {pose}")
await controller.move(pose)
await asyncio.sleep(1.0)
finally:
await controller.stop()
asyncio.run(simple_motion())
Example 3: Impedance Control
Compliant joint-space control with sinusoidal motion:
import numpy as np
import aiofranka
robot = aiofranka.Robot("172.16.0.2")
with aiofranka.Controller(robot) as controller:
# Move to start position
controller.move()
# Configure impedance control
controller.switch("impedance")
controller.kp = np.ones(7) * 80.0
controller.kd = np.ones(7) * 4.0
controller.set_freq(50)
# Execute smooth sinusoidal motion
for i in range(200): # 4 seconds at 50 Hz
delta = np.sin(i / 50.0 * np.pi) * 0.1
controller.set("q_desired", controller.initial_qpos + delta)
Example 4: Operational Space Control
Control end-effector position in Cartesian space:
import numpy as np
import aiofranka
robot = aiofranka.Robot("172.16.0.2")
with aiofranka.Controller(robot) as controller:
controller.move()
# Configure OSC
controller.switch("osc")
controller.ee_kp = np.array([300, 300, 300, 1000, 1000, 1000])
controller.ee_kd = np.ones(6) * 10.0
controller.set_freq(50)
# Circular motion in XY plane
for i in range(200):
angle = i / 50.0 * np.pi
radius = 0.05
desired_ee = controller.initial_ee.copy()
desired_ee[0, 3] += radius * np.cos(angle)
desired_ee[1, 3] += radius * np.sin(angle)
controller.set("ee_desired", desired_ee)
Example 5: Data Collection
Record every 1 kHz cycle during operation, with record() (see Native Control Loop):
import numpy as np
import aiofranka
robot = aiofranka.Robot("172.16.0.2")
with aiofranka.Controller(robot) as controller:
controller.move()
controller.switch("impedance")
controller.kp = np.ones(7) * 80.0
controller.kd = np.ones(7) * 4.0
controller.set_freq(50)
# The state of each cycle, the target it used and the torque it sent
fields = ["time", "q", "dq", "ee", "q_desired", "tau"]
with controller.record(fields, path="robot_data.npz") as recording:
for i in range(200):
delta = np.sin(i / 50.0 * np.pi) * 0.1
controller.set("q_desired", controller.initial_qpos + delta)
print(f"Saved {recording.rows} cycles")
Example 6: Gain Tuning
Systematically test different controller gains:
import os
import time
import numpy as np
import aiofranka
os.makedirs("sysid_data", exist_ok=True)
base = np.array([1, 1, 1, 1, 0.6, 0.6, 0.6])
kps = [16, 32, 64, 128, 256]
kds = [1, 2, 4, 8, 16]
robot = aiofranka.Robot("172.16.0.2")
with aiofranka.Controller(robot) as controller:
for kp in kps:
for kd in kds:
# Move to start
controller.kp = base * 80
controller.kd = base * 4
controller.move()
time.sleep(1.0)
print(f"Testing kp={kp}, kd={kd}")
controller.switch("impedance")
controller.kp = base * kp
controller.kd = base * kd
controller.set_freq(50)
with controller.record(["time", "q", "q_desired"], path=f"sysid_data/K{kp}_D{kd}.npz"):
for cnt in range(200):
delta = np.sin(cnt / 50.0 * np.pi) * 0.1
controller.set("q_desired", controller.initial_qpos + delta)
Example 7: End-Effector Configuration
Set end-effector mass and center of mass for accurate gravity compensation:
import aiofranka
aiofranka.unlock()
# Set end-effector parameters (mass in kg, CoM in meters)
aiofranka.set_configuration(mass=1.0, com=[0, 0, 0.057])
# Now start your control script...
# The robot will use the updated parameters for gravity compensation
aiofranka.lock()
If you do not know the parameters, identify the tool with aiofranka tool identify gripper, which
saves it as an end-effector profile in Desk. Activate a saved profile whenever its tool is mounted
(see Payload Identification):
aiofranka.load_tool("gripper")
Example 8: Gripper Control
Control a Robotiq gripper alongside the robot arm:
import asyncio
from aiofranka import GripperController
async def gripper_demo():
gripper = GripperController("/dev/ttyUSB1")
await gripper.start()
# Set speed and force (like kp/kd for the arm)
gripper.speed = 128
gripper.force = 200
# Open and close
gripper.q_desired = 0 # Open
await gripper.wait_until_reached()
gripper.q_desired = 255 # Close
await gripper.wait_until_reached()
# Or use convenience methods
gripper.open()
await gripper.wait_until_reached()
gripper.close()
await gripper.wait_until_reached()
await gripper.stop()
asyncio.run(gripper_demo())
Note
Gripper support requires: pip install "aiofranka[robotiq]"
Example 9: Simulation Testing
Test your controller in simulation before deploying to real robot:
import numpy as np
import aiofranka
def test_algorithm(robot_ip=None):
mode = "SIMULATION" if robot_ip is None else "REAL"
print(f"Testing in {mode} mode")
robot = aiofranka.Robot(robot_ip)
with aiofranka.Controller(robot) as controller:
controller.move()
controller.switch("impedance")
controller.kp = np.ones(7) * 80.0
controller.kd = np.ones(7) * 4.0
controller.set_freq(50)
for i in range(100):
delta = np.sin(i / 50.0 * np.pi) * 0.1
controller.set("q_desired", controller.initial_qpos + delta)
print("Test successful!")
if __name__ == "__main__":
# First test in simulation (on macOS, run with mjpython for MuJoCo's viewer)
test_algorithm(None)
# Then deploy to real robot
test_algorithm("172.16.0.2")
More Examples
The examples/ directory in the repository has minimal scripts. Each takes the robot IP as an
optional argument and runs in MuJoCo without it:
00_move.py: Move one joint (--joint,--delta)01_joint_impedance.py: Hold the current joint positions with joint impedance control02_osc_hold.py: Hold the current end-effector pose with operational space control03_zero_torque.py: Stream zero torque, so the robot only compensates gravity. With a new tool on the flange, the arm should stay still; if it drifts, identify the tool withaiofranka tool identify NAME(see Payload Identification).04_collect_joint_sysid.py: Play steps, multisines and slow ramps with joint impedance at three poses, about 1.5 minutes, with a controller configuration (--activate configs/joint_impedance.yaml, see Controller Configuration Files). It sets the targets at the configuration’s policy rate, as a policy would, and records every 1 kHz cycle withController.record(). What it shares with06is insysid.py.05_fit_joint_sysid.py: Fit kp, kd, damping and friction loss of each joint to the configuration’s latest recording with CMA-ES, simulating at your physics step (--activate,--physics_dt;--trajfor another recording), and add the fit to the configuration’ssimsection. The armature stays at fr3.xml’s, like the link inertias;--fixedchooses which parameters stay at their start values. Needspip install mjbatch.06_collect_osc_sysid.py: The same with operational space control (--activate configs/osc.yaml), about 2 minutes07_fit_osc_sysid.py: Fit ee_kp, ee_kd, null_kp, null_kd and each joint’s damping and friction loss to an OSC recording, weighing the TCP response and the joints08_native_custom_law.py: Hold the arm with a PID law written in Python, which the native loop runs compiled with Numba (see Native Control Loop)
Research scripts (system identification, SpaceMouse teleoperation, Robotiq gripper) are on the research-scripts branch.
Next Steps
Review Controllers for detailed controller documentation
Check CLI Reference for robot setup commands
Explore the Async Mode Guide guide if using
NativeFrankaControllerwith asyncio