克隆下来必须带上正在用的视觉权重和本机 site.yaml,否则感知只能指到一台机器上的路径,别人起不来。

顶视迁臂改走厂家 MoveL,D405 框心对准后再对孔;夹爪停在 caught 时 open 不再把相同目标当成已经张开,init 会真的回零。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
arm_dev
2026-10-12 17:08:43 +08:00
parent b903161113
commit f882c4309c
187 changed files with 22898 additions and 104 deletions
@@ -0,0 +1,409 @@
#!/usr/bin/env python
"""AGENT_REFERENCE_primitives_only.py
Comprehensive primitive-only reference script for code-generation agents.
Primitives demonstrated (in order):
1. Home - move robot to the platform-defined home posture.
2. ZeroFTSensor - zero force/torque sensors for all active single-arm groups.
3. MoveJ - joint-space move with waypoints and joint-velocity scaling.
4. MoveL - Cartesian linear move in WORLD frame with velocity, blending (zoneRadius).
5. MoveL - multi-waypoint sweep in WORLD frame.
6. Home - return to home before TCP-frame relative move.
7. MoveL - relative move in TCP frame (TRAJ::START), orientation-only rotation.
8. MoveJ - return to upright posture (all joints 0).
9. Home - return to home.
Key patterns shown:
- Fault clear, enable, wait operational, mode switch (essential setup).
- exec_prim() wrapper accepts group-indexed PrimitiveArgs for per-group primitives.
- Transition keys must be looked up per primitive from Flexiv primitive documentation:
https://www.flexiv.com/software/primitives
- Reading tcp_pose from each joint group to extract live orientation.
- Converting quaternion to Euler ZYX degrees using utility.quat2eulerZYX for MoveL orientation.
- ZeroFTSensor is executed for all selected groups with explicit completion checks.
- MoveL with optional velocity, zoneRadius blending, and waypoints.
- MoveJ with optional waypoints list and jntVelScale.
- All JPos calls use 7-element arm-joint list; second (external-axis) argument is optional.
- All Coord calls use positional args: (position, orientation_eulerZYX_deg, ref_frame_list).
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import argparse
import time
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
import utility
# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────
def normalize_euler_deg(euler_deg):
"""Normalize one angle or a 3-element Euler list to the robot's [-180, 180] convention."""
def normalize_angle(angle_deg):
normalized = (angle_deg + 180.0) % 360.0 - 180.0
if normalized == -180.0 and angle_deg > 0:
return 180.0
return normalized
if isinstance(euler_deg, (int, float)):
return normalize_angle(euler_deg)
return [normalize_angle(angle) for angle in euler_deg]
def wait_until_operational(robot, dt=1.0, timeout_s=120.0):
"""Block until robot.operational() is True, or raise TimeoutError."""
t0 = time.time()
while not robot.operational():
if time.time() - t0 > timeout_s:
raise TimeoutError("Timed out waiting for robot to become operational")
time.sleep(dt)
def wait_primitive_transition(robot, transition_keys_by_group, dt=0.2, timeout_s=60.0):
"""Block until primitive_states() for all groups satisfy their transition key.
Use the primitive's default transition key unless a custom transition condition
is intended. Check primitive docs to confirm available primitive state keys.
"""
t0 = time.time()
while True:
if utility.primitive_state_true_for_groups(
robot.primitive_states(),
transition_keys_by_group,
):
return
if time.time() - t0 > timeout_s:
raise TimeoutError(
"Timed out waiting for primitive transition states: "
f"{ {flexivrdk.kJointGroupNames[g]: k for g, k in transition_keys_by_group.items()} }"
)
time.sleep(dt)
def exec_prim(robot, primitive_args_by_group, transition_keys_by_group):
"""Execute primitive command(s) and block until all transitions are satisfied.
Args:
robot: flexivrdk.Robot instance.
primitive_args_by_group:
Dict[JointGroup, flexivrdk.PrimitiveArgs] for per-group primitive commands.
transition_keys_by_group:
Dict[JointGroup, str] where each value is the transition key for that group.
This can be the primitive's default key or another primitive state key.
Look up primitive states from:
https://www.flexiv.com/software/primitives
Note:
This helper waits on primitive_states() only. If transition depends on
robot states, implement a custom wait loop at call site.
"""
if not primitive_args_by_group:
raise ValueError("primitive_args_by_group cannot be empty")
if set(primitive_args_by_group.keys()) != set(transition_keys_by_group.keys()):
raise ValueError(
"primitive_args_by_group and transition_keys_by_group must have identical groups"
)
robot.ExecutePrimitive(primitive_args_by_group)
wait_primitive_transition(robot, transition_keys_by_group)
def prepare_robot(robot_sn, logger):
"""Connect, clear faults, enable robot, and switch to primitive execution mode."""
robot = flexivrdk.Robot(robot_sn)
if robot.fault():
logger.warn("Fault detected on connected robot, trying to clear")
if not robot.ClearFault():
raise RuntimeError("Failed to clear robot fault")
logger.info("Fault cleared")
logger.info("Enabling robot")
robot.ServoOn()
wait_until_operational(robot)
logger.info("Robot is operational")
robot.SwitchMode(flexivrdk.Mode.NRT_PRIMITIVE_EXECUTION)
return robot
def current_euler_zyx_deg(robot, group):
"""Return live TCP orientation of a specific joint group as Euler ZYX [x,y,z] degrees.
tcp_pose layout: [x, y, z, q_w, q_x, q_y, q_z] (SI units, quaternion).
utility.quat2eulerZYX() expects [w, x, y, z] order.
The returned Euler values are normalized to the robot's [-180, 180] convention.
"""
all_states = robot.states()
if group not in all_states:
raise ValueError(
f"Requested joint group has no available robot state: "
f"{flexivrdk.kJointGroupNames[group]}"
)
pose = all_states[group].tcp_pose
return utility.quat2eulerZYX([pose[3], pose[4], pose[5], pose[6]], degree=True)
# ──────────────────────────────────────────────────────────────────────────────
# Main sequence
# ──────────────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove spaces, e.g. Enlight-L-123456",
)
args = parser.parse_args()
logger = spdlog.ConsoleLogger("AgentReference")
try:
robot = prepare_robot(args.robot_sn, logger)
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# ── 1) Move to home pose ──────────────────────────────────────────────
logger.info("Step 1: Move to home pose")
robot.Home()
# ── 2) ZeroFTSensor ───────────────────────────────────────────────────
# Use this primitive's documented default transition condition.
# The robot must not be in contact with anything during zeroing.
logger.info("Step 2: ZeroFTSensor")
logger.warn("Zeroing F/T sensors - ensure nothing contacts the robot")
exec_prim(
robot,
{
group: flexivrdk.PrimitiveArgs(
"ZeroFTSensor",
{
"dataCollectTime": 0.2,
"enableStaticCheck": False,
"calibExtraPayload": False,
},
)
for group in single_arm_groups
},
{group: "terminated" for group in single_arm_groups},
)
logger.info("F/T sensor zeroing complete")
# ── 3) MoveJ with waypoints and velocity scaling ──────────────────────
# target / waypoints: flexivrdk.JPos(q_m) where q_m is 7-element degrees list.
# jntVelScale: percentage of max joint speed [1-100].
logger.info("Step 3: MoveJ with waypoints")
exec_prim(
robot,
{
group: flexivrdk.PrimitiveArgs(
"MoveJ",
{
"target": flexivrdk.JPos([30, -45, 0, 90, 0, 40, 30]),
"waypoints": [
flexivrdk.JPos([10, -30, 10, 30, 10, 15, 10]),
flexivrdk.JPos([20, -60, -10, 60, -10, 30, 20]),
],
"jntVelScale": 50, # 50% of max joint speed
"zoneRadius": "Z50",
"targetTolerLevel": 1,
"enableRelativeMove": False,
"jntAccMultiplier": 1,
},
)
for group in single_arm_groups
},
{group: "reachedTarget" for group in single_arm_groups},
)
# ── 4) MoveL in WORLD frame with velocity and blending ────────────────
# target / waypoints: flexivrdk.Coord(position_m, orientation_eulerZYX_deg, ref_frame).
# ref_frame is a 2-element list: [frame_type, frame_origin].
# vel: TCP linear speed in m/s.
# zoneRadius: blending zone size string, e.g. "Z50". Use "Z0" for exact stops.
step4_primitive_args_by_group = {}
for group in single_arm_groups:
live_euler = current_euler_zyx_deg(robot, group)
wp1_euler = normalize_euler_deg(
[live_euler[0] + 20.0, live_euler[1] - 20.0, live_euler[2] + 10.0]
)
wp2_euler = normalize_euler_deg(
[live_euler[0] - 30.0, live_euler[1] + 30.0, live_euler[2] - 30.0]
)
target_euler = normalize_euler_deg(
[live_euler[0] + 40.0, live_euler[1] - 20.0, live_euler[2] + 20.0]
)
logger.info(
f"Step 4 [{flexivrdk.kJointGroupNames[group]}] live Euler ZYX (deg): {live_euler}"
)
logger.info(
f"Step 4 [{flexivrdk.kJointGroupNames[group]}] waypoint1 Euler ZYX (deg): {wp1_euler}"
)
logger.info(
f"Step 4 [{flexivrdk.kJointGroupNames[group]}] waypoint2 Euler ZYX (deg): {wp2_euler}"
)
logger.info(
f"Step 4 [{flexivrdk.kJointGroupNames[group]}] target Euler ZYX (deg): {target_euler}"
)
step4_primitive_args_by_group[group] = flexivrdk.PrimitiveArgs(
"MoveL",
{
"target": flexivrdk.Coord(
[0.65, -0.3, 0.3], target_euler, ["WORLD", "WORLD_ORIGIN"]
),
"waypoints": [
flexivrdk.Coord(
[0.45, 0.1, 0.3], wp1_euler, ["WORLD", "WORLD_ORIGIN"]
),
flexivrdk.Coord(
[0.45, -0.3, 0.3], wp2_euler, ["WORLD", "WORLD_ORIGIN"]
),
],
"vel": 0.3, # m/s TCP linear speed
"zoneRadius": "Z50", # blended corners; use "Z0" for hard stops
"targetTolerLevel": 3,
"acc": 1.5,
"angVel": 150,
"enableFixRefJntPos": False,
"refJntPos": flexivrdk.JPos([0, -40, 0, 90, 0, 40, 0]),
"jerk": 50,
"configOptObj": [0, 0, 0.5],
"enableSixAxisJntCtrl": False,
"enableNullspaceTraj": False,
},
)
logger.info("Step 4: MoveL in WORLD frame with speed and blending")
exec_prim(
robot,
step4_primitive_args_by_group,
{group: "reachedTarget" for group in single_arm_groups},
)
# ── 5) MoveL multi-point sweep, exact stops ───────────────────────────
logger.info("Step 5: MoveL sweep with exact stops")
for target_pos in [
[0.5, 0.2, 0.4],
[0.5, -0.2, 0.4],
[0.5, -0.2, 0.2],
[0.5, 0.2, 0.2],
]:
exec_prim(
robot,
{
group: flexivrdk.PrimitiveArgs(
"MoveL",
{
"target": flexivrdk.Coord(
target_pos, [180, 0, 180], ["WORLD", "WORLD_ORIGIN"]
),
"vel": 0.2,
"zoneRadius": "Z0", # exact stop at each corner
"targetTolerLevel": 3,
"acc": 1.5,
"angVel": 150,
"enableFixRefJntPos": False,
"refJntPos": flexivrdk.JPos([0, -40, 0, 90, 0, 40, 0]),
"jerk": 50,
"configOptObj": [0, 0, 0.5],
"enableSixAxisJntCtrl": False,
"enableNullspaceTraj": False,
},
)
for group in single_arm_groups
},
{group: "reachedTarget" for group in single_arm_groups},
)
# ── 6) Home before TCP-frame relative move ────────────────────────────
logger.info("Step 6: Move to home pose")
robot.Home()
# ── 7) MoveL in TCP frame (TRAJ::START) - relative orientation change ─
# TRAJ::START means both translation and orientation targets are relative
# to the current TCP pose at primitive start.
# Setting position to [0, 0, 0] keeps the TCP at the same location.
# Orientation [20, 0, 0] means a +20 deg relative rotation around X.
logger.info("Step 7: MoveL relative orientation-only rotation in TCP frame")
logger.info("Relative target Euler ZYX (deg): [20.0, 0.0, 0.0]")
exec_prim(
robot,
{
group: flexivrdk.PrimitiveArgs(
"MoveL",
{
# Zero position delta: stay in place. Apply a 20-degree rotation around X.
"target": flexivrdk.Coord(
[0.0, 0.0, 0.0],
[20.0, 0.0, 0.0],
["TRAJ", "START"],
),
"vel": 0.1,
"zoneRadius": "Z0",
"targetTolerLevel": 3,
"acc": 1.0,
"angVel": 120,
"enableFixRefJntPos": False,
"refJntPos": flexivrdk.JPos([0, -40, 0, 90, 0, 40, 0]),
"jerk": 50,
"configOptObj": [0, 0, 0.5],
"enableSixAxisJntCtrl": False,
"enableNullspaceTraj": False,
},
)
for group in single_arm_groups
},
{group: "reachedTarget" for group in single_arm_groups},
)
# ── 8) MoveJ to upright posture ───────────────────────────────────────
# Upright convention: all arm joints at 0 degrees.
logger.info("Step 8: MoveJ to upright posture")
exec_prim(
robot,
{
group: flexivrdk.PrimitiveArgs(
"MoveJ",
{
"target": flexivrdk.JPos([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
"jntVelScale": 40,
"zoneRadius": "Z50",
"targetTolerLevel": 1,
"enableRelativeMove": False,
"jntAccMultiplier": 1,
},
)
for group in single_arm_groups
},
{group: "reachedTarget" for group in single_arm_groups},
)
# ── 9) Home ───────────────────────────────────────────────────────────
logger.info("Step 9: Move to home pose")
robot.Home()
logger.info("Reference sequence completed successfully")
robot.Stop()
except Exception as e:
logger.error(f"Unhandled exception: {e}")
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,156 @@
#!/usr/bin/env python
"""basics1_display_robot_states.py
This tutorial does the very first thing: check connection with the robot server and print
received robot states.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import argparse
import threading
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def print_robot_states(robot, logger, stop_event):
"""
Print robot states data @ 1Hz.
"""
while not stop_event.is_set():
# Print available joint groups
joint_groups_str = " ".join(
[f"[{name}]" for name in robot.info().all_groups.values()]
)
logger.info(f"Available joint groups: {joint_groups_str}")
# Print all robot states in JSON format using the built-in __str__ overloading
for group, states in robot.states().items():
logger.info(f"[{flexivrdk.kJointGroupNames[group]}] robot states:")
# fmt: off
print("{")
print(f"timestamp: [{states.timestamp[0]}, {states.timestamp[1]}]")
print(f"q: {['%.3f' % i for i in states.q]}")
print(f"theta: {['%.3f' % i for i in states.theta]}")
print(f"dq: {['%.3f' % i for i in states.dq]}")
print(f"dtheta: {['%.3f' % i for i in states.dtheta]}")
print(f"tau: {['%.3f' % i for i in states.tau]}")
print(f"tau_dot: {['%.3f' % i for i in states.tau_dot]}")
print(f"tau_ext: {['%.3f' % i for i in states.tau_ext]}")
print(f"tau_interact: {['%.3f' % i for i in states.tau_interact]}")
print(f"temperature: {['%.3f' % i for i in states.temperature]}")
print(f"flange_pose: {['%.3f' % i for i in states.flange_pose]}")
print(f"tcp_pose: {['%.3f' % i for i in states.tcp_pose]}")
print(f"tcp_twist: {['%.3f' % i for i in states.tcp_twist]}")
print(f"tcp_wrench: {['%.3f' % i for i in states.tcp_wrench]}")
print(f"tcp_wrench_local: {['%.3f' % i for i in states.tcp_wrench_local]}")
print(f"raw_tcp_wrench: {['%.3f' % i for i in states.raw_tcp_wrench]}")
print(f"raw_tcp_wrench_local: {['%.3f' % i for i in states.raw_tcp_wrench_local]}")
print(f"raw_ft_sensor: {['%.3f' % i for i in states.raw_ft_sensor]}")
print("}", flush=True)
# fmt: on
# Print all robot actions in JSON format using the built-in __str__ overloading
for group, actions in robot.actions().items():
logger.info(f"[{flexivrdk.kJointGroupNames[group]}] robot actions:")
# fmt: off
print("{")
print(f"timestamp: [{actions.timestamp[0]}, {actions.timestamp[1]}]")
print(f"q_d: {['%.3f' % i for i in actions.q_d]}")
print(f"dq_d: {['%.3f' % i for i in actions.dq_d]}")
print(f"tau_d: {['%.3f' % i for i in actions.tau_d]}")
print(f"tcp_pose_d: {['%.3f' % i for i in actions.tcp_pose_d]}")
print(f"tcp_twist_d: {['%.3f' % i for i in actions.tcp_twist_d]}")
print(f"tcp_wrench_d: {['%.3f' % i for i in actions.tcp_wrench_d]}")
print("}", flush=True)
# fmt: on
# Print digital inputs and outputs
logger.info("Digital inputs:")
print(robot.digital_inputs())
logger.info("Digital outputs:")
print(robot.digital_outputs())
time.sleep(1)
def main():
# Create an event to signal the thread to stop
stop_event = threading.Event()
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial does the very first thing: check connection "
"with the robot server and print received robot states.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
# Print States
# =============================================================================
# Thread for printing robot states
print_thread = threading.Thread(
target=print_robot_states, args=[robot, logger, stop_event]
)
print_thread.start()
# Use main thread to catch keyboard interrupt and exit thread
try:
while not stop_event.is_set():
time.sleep(0.1)
except KeyboardInterrupt:
# Send signal to exit thread
logger.info("Stopping print thread")
stop_event.set()
# Wait for thread to exit
print_thread.join()
logger.info("Print thread exited")
if __name__ == "__main__":
main()
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python
"""basics2_clear_fault.py
This tutorial clears minor or critical faults, if any, of the connected robot.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial clears minor or critical faults, if any, of "
"the connected robot.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Fault Clearing
# ==========================================================================================
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
else:
logger.info("No fault on the connected robot")
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,223 @@
#!/usr/bin/env python
"""basics3_primitive_execution.py
This tutorial executes several basic robot primitives (unit skills). For detailed documentation
on all available primitives, please see [Flexiv Primitives](https://www.flexiv.com/primitives/).
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
import utility
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial executes several basic robot primitives (unit "
"skills). For detailed documentation on all available primitives, please see [Flexiv "
"Primitives](https://www.flexiv.com/primitives/).\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Execute Primitives
# ==========================================================================================
# Primitives can only be executed on single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# (1) Move robot to home pose
# ------------------------------------------------------------------------------------------
logger.info("Moving to home pose")
robot.Home()
# Switch to primitive execution mode
robot.SwitchMode(mode.NRT_PRIMITIVE_EXECUTION)
# (2) Move robot joints to target positions
# ------------------------------------------------------------------------------------------
# Required parameters:
# target: final joint positions, unit: degrees
# {arm joint positions}, {external axis joint positions, optional}
# Optional parameters:
# waypoints: waypoints to pass before reaching the target
# (same format as above, but can be more than one)
# vel: TCP linear velocity, unit: m/s
logger.info("Executing primitive: MoveJ")
# Send command to robot
robot.ExecutePrimitive(
{
group: flexivrdk.PrimitiveArgs(
"MoveJ",
{
"target": flexivrdk.JPos(
[30, -45, 0, 90, 0, 40, 30], [-50, 30, 0, 0, 0, 0]
),
"waypoints": [
flexivrdk.JPos(
[10, -30, 10, 30, 10, 15, 10], [-15, 10, 0, 0, 0, 0]
),
flexivrdk.JPos(
[20, -60, -10, 60, -10, 30, 20], [-30, 20, 0, 0, 0, 0]
),
],
},
)
for group in single_arm_groups
}
)
# Most primitives won't exit by themselves and require users to explicitly trigger
# transitions based on specific primitive states. Here we check if the primitive state
# [reachedTarget] becomes true and trigger the transition manually by sending a new
# primitive command.
while True:
primitive_states = robot.primitive_states()
if utility.primitive_state_true_for_groups(
primitive_states, "reachedTarget"
):
break
# Print current primitive states
logger.info("Current primitive states:")
for group, pt_states in primitive_states.items():
print(f"{flexivrdk.kJointGroupNames[group]}:")
print(f"primitiveName: {pt_states.pt_name}")
for name, value in pt_states.names_and_values.items():
print(f"{name}: {value}")
time.sleep(1)
# (3) Move robot TCP to a target pose in world (base) frame
# ------------------------------------------------------------------------------------------
# Required parameters:
# target: final TCP pose, unit: m and degrees
# {pos_x, pos_y, pos_z}, {rot_x, rot_y, rot_z}, {ref_frame, ref_point}
# Optional parameters:
# waypoints: waypoints to pass before reaching the target
# (same format as above, but can be more than one)
# vel: TCP linear velocity, unit: m/s
# NOTE: The rotations use Euler ZYX convention, rot_x means Euler ZYX angle around X axis
logger.info("Executing primitive: MoveL")
# Send command to robot
robot.ExecutePrimitive(
{
group: flexivrdk.PrimitiveArgs(
"MoveL",
{
"target": flexivrdk.Coord(
[0.3, -0.1, 0.2], [160, 20, 180], ["WORLD", "WORLD_ORIGIN"]
),
"waypoints": [
flexivrdk.Coord(
[0.1, 0.1, 0.1],
[-160, -20, 180],
["WORLD", "WORLD_ORIGIN"],
),
flexivrdk.Coord(
[0.3, 0.2, 0.1],
[180, 0, 180],
["WORLD", "WORLD_ORIGIN"],
),
],
"vel": 0.6,
"zoneRadius": "Z50",
},
)
for group in single_arm_groups
}
)
# Wait for reached target
while not utility.primitive_state_true_for_groups(
robot.primitive_states(), "reachedTarget"
):
time.sleep(1)
# (4) Another MoveL that uses TCP frame
# ------------------------------------------------------------------------------------------
# In this example the reference frame is changed from WORLD::WORLD_ORIGIN to TRAJ::START,
# which represents the current TCP frame
logger.info("Executing primitive: MoveL")
# Example to convert target quaternion [w,x,y,z] to Euler ZYX using scipy package's 'xyz'
# extrinsic rotation
target_quat = [0.9185587, 0.1767767, 0.3061862, 0.1767767]
# ZYX = [30, 30, 30] degrees
eulerZYX_deg = utility.quat2eulerZYX(target_quat, degree=True)
# Send command to robot. This motion will hold current TCP position and only do rotation
robot.ExecutePrimitive(
{
group: flexivrdk.PrimitiveArgs(
"MoveL",
{
"target": flexivrdk.Coord(
[0.0, 0.0, 0.0], eulerZYX_deg, ["TRAJ", "START"]
),
"vel": 0.2,
},
)
for group in single_arm_groups
}
)
# Wait for reached target
while not utility.primitive_state_true_for_groups(
robot.primitive_states(), "reachedTarget"
):
time.sleep(1)
# All done, stop robot and put into IDLE mode
robot.Stop()
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,147 @@
#!/usr/bin/env python
"""basics4_plan_execution.py
This tutorial executes a plan selected by the user from a list of available plans. A plan is a
pre-written script to execute a series of robot primitives with pre-defined transition conditions
between 2 adjacent primitives. Users can use Flexiv Elements to compose their own plan and assign
to the robot, which will appear in the plan list.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial executes a plan selected by the user from a "
"list of available plans. A plan is a pre-written script to execute a series of robot "
"primitives with pre-defined transition conditions between 2 adjacent primitives. Users "
"can use Flexiv Elements to compose their own plan and assign to the robot, which "
"will appear in the plan list.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Execute Plans
# ==========================================================================================
# Switch to plan execution mode
robot.SwitchMode(mode.NRT_PLAN_EXECUTION)
while True:
# Monitor fault on the connected robot
if robot.fault():
raise Exception("Fault occurred on the connected robot, exiting ...")
# Get user input
logger.info("Choose an action:")
print("[1] Show available plans")
print("[2] Execute a plan by index")
print("[3] Execute a plan by name")
user_input = int(input())
# Get and show plan list
if user_input == 1:
plan_list = robot.plan_list()
for i in range(len(plan_list)):
print(f"[{i}] {plan_list[i]}")
print("")
# Execute plan by index
elif user_input == 2:
index = int(input("Enter plan index to execute:\n"))
# Allow the plan to continue its execution even if the RDK program is closed or
# the connection is lost
robot.ExecutePlan(index, True)
# Print plan info while the current plan is running
while robot.busy():
plan_info = robot.plan_info()
logger.info("Current plan info:")
print(f"assigned_plan_name: {plan_info.assigned_plan_name}")
print(f"pt_name: {plan_info.pt_name}")
print(f"node_name: {plan_info.node_name}")
print(f"node_path: {plan_info.node_path}")
print(f"node_path_time_period: {plan_info.node_path_time_period}")
print(f"node_path_number: {plan_info.node_path_number}")
print(f"velocity_scale: {plan_info.velocity_scale}")
print(f"waiting_for_step: {plan_info.waiting_for_step}")
print("", flush=True)
time.sleep(1)
# Execute plan by name
elif user_input == 3:
name = str(input("Enter plan name to execute:\n"))
# Allow the plan to continue its execution even if the RDK program is closed or
# the connection is lost
robot.ExecutePlan(name, True)
# Print plan info while the current plan is running
while robot.busy():
plan_info = robot.plan_info()
logger.info("Current plan info:")
print(f"assigned_plan_name: {plan_info.assigned_plan_name}")
print(f"pt_name: {plan_info.pt_name}")
print(f"node_name: {plan_info.node_name}")
print(f"node_path: {plan_info.node_path}")
print(f"node_path_time_period: {plan_info.node_path_time_period}")
print(f"node_path_number: {plan_info.node_path_number}")
print(f"velocity_scale: {plan_info.velocity_scale}")
print(f"waiting_for_step: {plan_info.waiting_for_step}")
print("", flush=True)
time.sleep(1)
else:
logger.warn("Invalid input")
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,112 @@
#!/usr/bin/env python
"""basics5_zero_force_torque_sensors.py
This tutorial zeros the robot's force and torque sensors, which is a recommended (but not
mandatory) step before any operations that require accurate force/torque measurement.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
import utility
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial zeros the robot's force and torque sensors, "
"which is a recommended (but not mandatory) step before any operations that require "
"accurate force/torque measurement.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Zero Sensors
# ==========================================================================================
# Get and print the current TCP force/moment readings
for group, states in robot.states().items():
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] TCP force and moment reading in world frame BEFORE sensor zeroing: {states.tcp_wrench} N-Nm"
)
# Primitives can only be executed on single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# Run the "ZeroFTSensor" primitive to automatically zero force and torque sensors
robot.SwitchMode(mode.NRT_PRIMITIVE_EXECUTION)
robot.ExecutePrimitive(
{
group: flexivrdk.PrimitiveArgs("ZeroFTSensor", dict())
for group in single_arm_groups
}
)
# WARNING: during the process, the robot must not contact anything, otherwise the result
# will be inaccurate and affect following operations
logger.warn(
"Zeroing force/torque sensors, make sure nothing is in contact with the robot"
)
# Wait for primitive to finish
while not utility.primitive_state_true_for_groups(robot.primitive_states(), "terminated"):
time.sleep(1)
logger.info("Sensor zeroing complete")
# Get and print the current TCP force/moment readings
for group, states in robot.states().items():
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] TCP force and moment reading in world frame AFTER sensor zeroing: {states.tcp_wrench} N-Nm"
)
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,230 @@
#!/usr/bin/env python
"""basics6_gripper_control.py
This tutorial does position and force (if available) control of grippers supported by Flexiv.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import sys
import argparse
import threading
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def print_gripper_states(gripper, logger, stop_event):
"""
Print gripper states data @ 1Hz.
"""
while not stop_event.is_set():
# Print all gripper states, round all float values to 2 decimals
logger.info("Current gripper states:")
gripper_states = gripper.states()
for group, states in gripper_states.items():
print(f"[{flexivrdk.kJointGroupNames[group]}]")
print(f"width: {round(states.width, 2)}")
print(f"force: {round(states.force, 2)}")
print(f"is_moving: {states.is_moving}")
print("", flush=True)
time.sleep(1)
def main():
# Create an event to signal the thread to stop
stop_event = threading.Event()
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
argparser.add_argument(
"gripper_device_name",
help="Full name of the device representing the gripper, can be found in Flexiv Elements->Settings->Device",
)
argparser.add_argument(
"gripper_tool_name",
help="Full name of the tool representing the gripper, can be found in Flexiv Elements->Settings->Tool",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial does position and force (if available) "
"control of grippers supported by Flexiv.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Gripper Control
# ==========================================================================================
# Instantiate gripper control interface
gripper = flexivrdk.Gripper(robot)
# Instantiate tool interface. Gripper is categorized as both a device and a tool. The
# device attribute allows a gripper to be interactively controlled by the user; whereas the
# tool attribute tells the robot to account for its mass properties and TCP location.
tool = flexivrdk.Tool(robot)
# Grippers can only be assigned to single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# Enable the specified gripper as a device. This is equivalent to enabling the specified
# gripper in Flexiv Elements -> Settings -> Device
logger.info(
f"Enabling gripper device [{args.gripper_device_name}] for all available single-arm joint groups"
)
for group in single_arm_groups:
gripper.Enable(group, args.gripper_device_name)
# Print parameters of the enabled gripper
logger.info("Gripper params:")
gripper_params = gripper.params()
for group, params in gripper_params.items():
print(f"[{flexivrdk.kJointGroupNames[group]}]")
print(f"name: {params.name}")
print(f"min_width: {round(params.min_width, 2)}")
print(f"max_width: {round(params.max_width, 2)}")
print(f"min_force: {round(params.min_force, 2)}")
print(f"max_force: {round(params.max_force, 2)}")
print(f"min_vel: {round(params.min_vel, 2)}")
print(f"max_vel: {round(params.max_vel, 2)}")
print("", flush=True)
# Switch robot tool to gripper so the gravity compensation and TCP location is updated
logger.info(
f"Switching robot tool to [{args.gripper_tool_name}] for all available single-arm joint groups"
)
for group in single_arm_groups:
tool.Switch(group, args.gripper_tool_name)
# User needs to determine if this gripper requires manual initialization
logger.info(
"Manually trigger initialization for the gripper now? Choose Yes if it's a 48v Grav "
"gripper"
)
print("[1] No, it has already initialized automatically when power on")
print("[2] Yes, it does not initialize itself when power on")
choice = int(input(""))
# Trigger manual initialization based on input
if choice == 1:
logger.info("Skipped manual initialization")
elif choice == 2:
for group in single_arm_groups:
gripper.Init(group)
# User determines if the manual initialization is finished
logger.info(
"Triggered manual initialization, press Enter when the initialization is finished to continue"
)
input()
else:
logger.error("Invalid choice")
return 1
# Start a separate thread to print gripper states
print_thread = threading.Thread(
target=print_gripper_states, args=[gripper, logger, stop_event]
)
print_thread.start()
# Position control
logger.info("Closing gripper")
for group, params in gripper_params.items():
gripper.Move(group, params.min_width, params.max_vel, 0.25 * params.max_force)
time.sleep(2)
logger.info("Opening gripper")
for group, params in gripper_params.items():
gripper.Move(group, params.max_width, params.max_vel, 0.25 * params.max_force)
time.sleep(2)
# Stop
logger.info("Closing gripper")
for group, params in gripper_params.items():
gripper.Move(group, params.min_width, params.max_vel, 0.25 * params.max_force)
time.sleep(0.5)
logger.info("Stopping gripper")
for group in gripper_params:
gripper.Stop(group)
time.sleep(2)
logger.info("Closing gripper")
for group, params in gripper_params.items():
gripper.Move(group, params.min_width, params.max_vel, 0.25 * params.max_force)
time.sleep(2)
logger.info("Opening gripper")
for group, params in gripper_params.items():
gripper.Move(group, params.max_width, params.max_vel, 0.25 * params.max_force)
time.sleep(0.5)
logger.info("Stopping gripper")
for group in gripper_params:
gripper.Stop(group)
time.sleep(2)
# Force control, if available (sensed force is not zero)
gripper_states = gripper.states()
has_force_control = any(
abs(states.force) > sys.float_info.epsilon
for states in gripper_states.values()
)
if has_force_control:
logger.info("Gripper running zero force control")
for group in gripper_states:
gripper.Grasp(group, 0)
# Exit after 10 seconds
time.sleep(10)
# Finished
for group in gripper_states:
gripper.Stop(group)
# Stop all threads
logger.info("Stopping print thread")
stop_event.set()
print_thread.join()
logger.info("Print thread exited")
logger.info("Program finished")
except Exception as e:
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,159 @@
#!/usr/bin/env python
"""basics7_update_robot_tool.py
This tutorial shows how to online update and interact with the robot tools. All changes made to
the robot tool system will take effect immediately without needing to reboot. However, the robot
must be put into IDLE mode when making these changes.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial shows how to online update and interact with "
"the robot tools. All changes made to the robot tool system will take effect immediately "
"without needing to reboot. However, the robot must be put into IDLE mode when making "
"these changes.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Update Robot Tool
# ==========================================================================================
# Make sure the robot is in IDLE mode
robot.SwitchMode(mode.IDLE)
# Instantiate tool interface
tool = flexivrdk.Tool(robot)
# Tools can only be assigned to single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# Get and print a list of already configured tools currently in the robot's tools pool
logger.info("All configured tools:")
tool_list = tool.list()
for i in range(len(tool_list)):
print(f"[{i}] {tool_list[i]}")
print()
# Get and print the current active tool
for group in single_arm_groups:
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Current active tool: [{tool.name(group)}]"
)
# Set name and parameters for a new tool
new_tool_name = "ExampleTool1"
new_tool_params = flexivrdk.ToolParams()
new_tool_params.mass = 0.9
new_tool_params.CoM = [0.0, 0.0, 0.057]
new_tool_params.inertia = [2.768e-03, 3.149e-03, 5.64e-04, 0.0, 0.0, 0.0]
new_tool_params.tcp_location = [
0.0,
-0.207,
0.09,
0.7071068,
0.7071068,
0.0,
0.0,
]
# If there's already a tool with the same name in the robot's tools pool, then remove it
# first, because duplicate tool names are not allowed
if tool.exist(new_tool_name):
logger.warn(
f"Tool with the same name [{new_tool_name}] already exists, removing it now"
)
# Switch to other tool or no tool (Flange) before removing the current tool
for group in single_arm_groups:
tool.Switch(group, "Flange")
tool.Remove(new_tool_name)
# Add the new tool
logger.info(f"Adding new tool [{new_tool_name}] to the robot")
tool.Add(new_tool_name, new_tool_params)
# Get and print the tools list again, the new tool should appear at the end
logger.info("All configured tools:")
tool_list = tool.list()
for i in range(len(tool_list)):
print(f"[{i}] {tool_list[i]}")
print()
# Switch to the newly added tool, i.e. set it as the active tool
logger.info(f"Switching to tool [{new_tool_name}]")
for group in single_arm_groups:
tool.Switch(group, new_tool_name)
# Get and print the current active tool again, should be the new tool
for group in single_arm_groups:
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Current active tool: [{tool.name(group)}]"
)
# Switch to other tool or no tool (Flange) before removing the current tool
for group in single_arm_groups:
tool.Switch(group, "Flange")
# Clean up by removing the new tool
time.sleep(2)
logger.info(f"Removing tool [{new_tool_name}]")
tool.Remove(new_tool_name)
logger.info("Program finished")
except Exception as e:
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,129 @@
#!/usr/bin/env python
"""basics8_global_variables.py
This tutorial shows how to get and set global variables.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial shows how to get and set global variables.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Get existing global variables
# =========================================================================================
global_vars = robot.global_variables()
if not global_vars:
logger.warn("No global variables available")
return 1
else:
logger.info("Existing global variables and their original values:")
for key, value in global_vars.items():
print(f"{key}: {value}")
# Set global variables
# =========================================================================================
# WARNING: These specified global variables need to be created first using Flexiv Elements
logger.info("Setting new values to existing global variables")
robot.SetGlobalVariables(
{
"test_bool": 1,
"test_int": 100,
"test_double": 100.123,
"test_string": "Flexiv",
"test_int_vec": [1, 2, 3],
"test_double_vec": [1.1, 2.2, 3.3],
"test_string_vec": ["Go", "Flexiv", "Go!"],
"test_pose": [0.1, -0.2, 0.3, -90, -45, 120],
"test_coord": flexivrdk.Coord(
[0.1, -0.2, 0.3],
[-90, -45, 120],
["WORLD", "WORLD_ORIGIN"],
[1, 2, 3, 4, 5, 6, 7],
[10, 20, 0, 0, 0, 0],
),
"test_coord_array": [
flexivrdk.Coord([1, 2, 3], [4, 5, 6], ["WORK", "WorkCoord0"]),
flexivrdk.Coord(
[10, 20, 30],
[40, 50, 60],
["WORLD", "WORLD_ORIGIN"],
[1, 2, 3, 4, 5, 6, 7],
[10, 20, 0, 0, 0, 0],
),
flexivrdk.Coord(
[3, 2, 1], [180, 0, 180], ["WORLD", "WORLD_ORIGIN"]
),
],
}
)
# Get updated global variables
# =========================================================================================
global_vars = robot.global_variables()
if not global_vars:
logger.warn("No global variables available")
return 1
else:
logger.info("Updated global variables:")
for key, value in global_vars.items():
print(f"{key}: {value}")
logger.info("Program finished")
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,154 @@
#!/usr/bin/env python
"""intermediate1_non_realtime_joint_position_control.py
This tutorial runs non-real-time joint position control to hold or sine-sweep all robot joints.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import math
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
# Required arguments
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
argparser.add_argument(
"frequency", help="Command frequency, 1 to 100 [Hz]", type=int
)
# Optional arguments
argparser.add_argument(
"--hold",
action="store_true",
help="Robot holds current joint positions, otherwise do a sine-sweep",
)
args = argparser.parse_args()
# Check if arguments are valid
frequency = args.frequency
assert frequency >= 1 and frequency <= 100, "Invalid <frequency> input"
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial runs non-real-time joint position control to "
"hold or sine-sweep all robot joints.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Move robot to home pose
logger.info("Moving to home pose")
robot.Home()
# Non-real-time Joint Position Control
# ==========================================================================================
# Direct joint control can be executed by single-arm joint groups and the external axis
exe_groups = robot.info().single_arm_groups
if not exe_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# The external axis joint group (if it exists) also supports direct joint control
if flexivrdk.JointGroup.EXT_AXIS in robot.info().all_groups:
exe_groups[flexivrdk.JointGroup.EXT_AXIS] = robot.info().all_groups[
flexivrdk.JointGroup.EXT_AXIS
]
# Switch to non-real-time joint position control mode
robot.SwitchMode(mode.NRT_JOINT_POSITION)
period = 1.0 / frequency
loop_time = 0
logger.info(
f"Sending command to robot at {frequency} Hz, or {period} seconds interval"
)
# Use current robot joint positions as initial positions
all_init_pos = {}
robot_states = robot.states()
for group in exe_groups:
all_init_pos[group] = robot_states[group].q.copy()
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Initial joint positions: {all_init_pos[group]}"
)
# Joint sine-sweep amplitude [rad]
SWING_AMP = 0.1
# TCP sine-sweep frequency [Hz]
SWING_FREQ = 0.3
# Send command periodically at user-specified frequency
while True:
# Use sleep to control loop period
time.sleep(period)
# Monitor fault on the connected robot
if robot.fault():
raise Exception("Fault occurred on the connected robot, exiting ...")
cmds = {}
sine_offset = SWING_AMP * math.sin(2 * math.pi * SWING_FREQ * loop_time)
for group, init_pos in all_init_pos.items():
target_pos = init_pos.copy()
if not args.hold:
for i in range(len(target_pos)):
target_pos[i] += sine_offset
zero_vel = [0.0] * len(init_pos)
max_vel = [2.0] * len(init_pos)
max_acc = [3.0] * len(init_pos)
cmds[group] = flexivrdk.NrtJointPositionCmd(
target_pos, zero_vel, max_vel, max_acc
)
robot.SendJointPosition(cmds)
# Increment loop time
loop_time += period
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,177 @@
#!/usr/bin/env python
"""intermediate2_non_realtime_joint_impedance_control.py
This tutorial runs non-real-time joint impedance control to hold or sine-sweep all robot joints.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import math
import argparse
import spdlog # pip install spdlog
import numpy as np # pip install numpy
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
# Required arguments
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
argparser.add_argument(
"frequency", help="Command frequency, 1 to 100 [Hz]", type=int
)
# Optional arguments
argparser.add_argument(
"--hold",
action="store_true",
help="Robot holds current joint positions, otherwise do a sine-sweep",
)
args = argparser.parse_args()
# Check if arguments are valid
frequency = args.frequency
assert frequency >= 1 and frequency <= 100, "Invalid <frequency> input"
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial runs non-real-time joint impedance control to "
"hold or sine-sweep all robot joints.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Move robot to home pose
logger.info("Moving to home pose")
robot.Home()
# Non-real-time Joint Impedance Control
# ==========================================================================================
# Switch to non-real-time joint impedance control mode
robot.SwitchMode(mode.NRT_JOINT_IMPEDANCE)
# Direct joint control can be executed by single-arm joint groups and the external axis
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
# The external axis joint group (if it exists) also supports direct joint control
exe_groups = dict(single_arm_groups)
if flexivrdk.JointGroup.EXT_AXIS in robot.info().all_groups:
exe_groups[flexivrdk.JointGroup.EXT_AXIS] = robot.info().all_groups[
flexivrdk.JointGroup.EXT_AXIS
]
period = 1.0 / frequency
loop_counter = 0
logger.info(
f"Sending command to robot at {frequency} Hz, or {period} seconds interval"
)
# Use current robot joint positions as initial positions
all_init_pos = {}
robot_states = robot.states()
for group in exe_groups:
all_init_pos[group] = robot_states[group].q.copy()
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Initial joint positions: {all_init_pos[group]}"
)
# Joint sine-sweep amplitude [rad]
SWING_AMP = 0.1
# TCP sine-sweep frequency [Hz]
SWING_FREQ = 0.3
# Send command periodically at user-specified frequency
while True:
# Use sleep to control loop period
time.sleep(period)
# Monitor fault on the connected robot
if robot.fault():
raise Exception("Fault occurred on the connected robot, exiting ...")
# Reduce stiffness to half of nominal values after 5 seconds
if loop_counter == 5 / period:
for group in single_arm_groups:
new_Kq = np.multiply(robot.info().K_q_nom[group], 0.5).tolist()
robot.SetJointImpedance(group, new_Kq)
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Joint stiffness set to: {new_Kq}"
)
# Reset stiffness to nominal values after another 5 seconds
if loop_counter == 10 / period:
for group in single_arm_groups:
nominal_Kq = robot.info().K_q_nom[group]
robot.SetJointImpedance(group, nominal_Kq)
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Joint stiffness reset to nominal: {nominal_Kq}"
)
# Send commands
cmds = {}
sine_offset = SWING_AMP * math.sin(
2 * math.pi * SWING_FREQ * loop_counter * period
)
for group, init_pos in all_init_pos.items():
target_pos = init_pos.copy()
if not args.hold:
for i in range(len(target_pos)):
target_pos[i] += sine_offset
zero_vel = [0.0] * len(init_pos)
max_vel = [2.0] * len(init_pos)
max_acc = [3.0] * len(init_pos)
cmds[group] = flexivrdk.NrtJointPositionCmd(
target_pos, zero_vel, max_vel, max_acc
)
robot.SendJointPosition(cmds)
# Increment loop counter
loop_counter += 1
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,280 @@
#!/usr/bin/env python
"""intermediate3_non_realtime_cartesian_pure_motion_control.py
This tutorial runs non-real-time Cartesian-space pure motion control to hold or sine-sweep the robot
TCP. A simple collision detection is also included.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import math
import argparse
import spdlog # pip install spdlog
import numpy as np # pip install numpy
import flexivrdk # pip install flexivrdk
import utility
# Global constants
# ==================================================================================================
# TCP sine-sweep amplitude [m]
SWING_AMP = 0.1
# TCP sine-sweep frequency [Hz]
SWING_FREQ = 0.3
# External TCP force threshold for collision detection, value is only for demo purpose [N]
EXT_FORCE_THRESHOLD = 10.0
# External joint torque threshold for collision detection, value is only for demo purpose [Nm]
EXT_TORQUE_THRESHOLD = 5.0
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
# Required arguments
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
argparser.add_argument(
"frequency", help="Command frequency, 1 to 100 [Hz]", type=int
)
# Optional arguments
argparser.add_argument(
"--hold",
action="store_true",
help="Robot holds current TCP pose, otherwise do a sine-sweep",
)
argparser.add_argument(
"--collision",
action="store_true",
help="Enable collision detection, robot will stop upon collision",
)
args = argparser.parse_args()
# Check if arguments are valid
frequency = args.frequency
assert frequency >= 1 and frequency <= 100, "Invalid <frequency> input"
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial runs non-real-time Cartesian-space pure "
"motion control to hold or sine-sweep the robot TCP. A simple collision detection is also "
"included.\n"
)
# Print based on arguments
if args.hold:
logger.info("Robot holding current TCP pose")
else:
logger.info("Robot running TCP sine-sweep")
if args.collision:
logger.info("Collision detection enabled")
else:
logger.info("Collision detection disabled")
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Move robot to home pose
logger.info("Moving to home pose")
robot.Home()
# Zero Force-torque Sensor
# =========================================================================================
# Direct Cartesian control can only be executed by single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
robot.SwitchMode(mode.NRT_PRIMITIVE_EXECUTION)
# IMPORTANT: must zero force/torque sensor offset for accurate force/torque measurement
robot.ExecutePrimitive(
{
group: flexivrdk.PrimitiveArgs("ZeroFTSensor", dict())
for group in single_arm_groups
}
)
# WARNING: during the process, the robot must not contact anything, otherwise the result
# will be inaccurate and affect following operations
logger.warn(
"Zeroing force/torque sensors, make sure nothing is in contact with the robot"
)
# Wait for primitive to finish
while not utility.primitive_state_true_for_groups(robot.primitive_states(), "terminated"):
time.sleep(1)
logger.info("Sensor zeroing complete")
# Configure Motion Control
# =========================================================================================
# The Cartesian motion force modes do pure motion control out of the box, thus nothing
# needs to be explicitly configured
# NOTE: motion control always uses robot world frame, while force control can use
# either world or TCP frame as reference frame
# Start Pure Motion Control
# =========================================================================================
# Switch to non-real-time mode for discrete motion control
robot.SwitchMode(mode.NRT_CARTESIAN_MOTION_FORCE)
# Set all Cartesian axis(s) to motion control
for group in single_arm_groups:
robot.SetForceControlAxis(group, [False, False, False, False, False, False])
# Save initial poses and joint positions
all_init_pose = {}
all_init_q = {}
robot_states = robot.states()
for group in single_arm_groups:
all_init_pose[group] = robot_states[group].tcp_pose.copy()
all_init_q[group] = robot_states[group].q.copy()
# Periodic Task
# =========================================================================================
# Set loop period
period = 1.0 / frequency
loop_counter = 0
logger.info(
f"Sending command to robot at {frequency} Hz, or {period} seconds interval"
)
# Send command periodically at user-specified frequency
while True:
# Use sleep to control loop period
time.sleep(period)
# Monitor fault on the connected robot
if robot.fault():
raise Exception("Fault occurred on the connected robot, exiting ...")
cmds = {}
sine_offset = SWING_AMP * math.sin(
2 * math.pi * SWING_FREQ * loop_counter * period
)
for group, init_pose in all_init_pose.items():
target_pose = init_pose.copy()
# Sine-sweep TCP along Y axis
if not args.hold:
target_pose[1] += sine_offset
cmds[group] = flexivrdk.NrtCartesianCmd(target_pose)
# Send command. Calling this method with only target pose input results
# in pure motion control
robot.SendCartesianMotionForce(cmds)
# Do the following operations in sequence for every 20 seconds
time_elapsed = loop_counter * period
# Online change reference joint positions at 3 seconds
if time_elapsed % 20.0 == 3.0:
ref_q = [0.938, -1.108, -1.254, 1.464, 1.073, 0.278, -0.658]
for group in single_arm_groups:
robot.SetNullSpacePosture(group, ref_q)
logger.info(f"Reference joint positions set to: {ref_q}")
# Online change stiffness to half of nominal at 6 seconds
elif time_elapsed % 20.0 == 6.0:
for group in single_arm_groups:
new_K = np.multiply(robot.info().K_x_nom[group], 0.5).tolist()
robot.SetCartesianImpedance(group, new_K)
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Cartesian stiffness set to: {new_K}"
)
# Online change to another reference joint positions at 9 seconds
elif time_elapsed % 20.0 == 9.0:
ref_q = [-0.938, -1.108, 1.254, 1.464, -1.073, 0.278, 0.658]
for group in single_arm_groups:
robot.SetNullSpacePosture(group, ref_q)
logger.info(f"Reference joint positions set to: {ref_q}")
# Online reset stiffness to nominal at 12 seconds
elif time_elapsed % 20.0 == 12.0:
for group in single_arm_groups:
nominal_K = robot.info().K_x_nom[group]
robot.SetCartesianImpedance(group, nominal_K)
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Cartesian stiffness reset to nominal: {nominal_K}"
)
# Online reset reference joint positions to nominal at 14 seconds
elif time_elapsed % 20.0 == 14.0:
for group, init_q in all_init_q.items():
robot.SetNullSpacePosture(group, init_q)
logger.info("Reference joint positions are reset")
# Online enable max contact wrench regulation at 16 seconds
elif time_elapsed % 20.0 == 16.0:
max_wrench = [10.0, 10.0, 10.0, 2.0, 2.0, 2.0]
for group in single_arm_groups:
robot.SetMaxContactWrench(group, max_wrench)
logger.info(f"Max contact wrench set to: {max_wrench}")
# Disable max contact wrench regulation at 19 seconds
elif time_elapsed % 20.0 == 19.0:
for group in single_arm_groups:
robot.SetMaxContactWrench(group, [float("inf")] * 6)
logger.info("Max contact wrench regulation is disabled")
# Simple collision detection: stop robot if collision is detected at
# end-effector
if args.collision:
for group, states in robot.states().items():
collision_detected = False
ext_force = np.array(states.tcp_wrench[:3])
if np.linalg.norm(ext_force) > EXT_FORCE_THRESHOLD:
collision_detected = True
for v in states.tau_ext:
if abs(v) > EXT_TORQUE_THRESHOLD:
collision_detected = True
if collision_detected:
robot.Stop()
logger.warn(
f"[{flexivrdk.kJointGroupNames[group]}] Collision detected, stopping robot and exit program ..."
)
return
# Increment loop counter
loop_counter += 1
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,300 @@
#!/usr/bin/env python
"""intermediate4_non_realtime_cartesian_motion_force_control.py
This tutorial runs non-real-time Cartesian-space unified motion-force control. The Z axis of the
chosen reference frame will be activated for explicit force control, while the rest axes in the
same reference frame will stay motion controlled.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import math
import argparse
import spdlog # pip install spdlog
import numpy as np # pip install numpy
import flexivrdk # pip install flexivrdk
import utility
# Global constants
# ==================================================================================================
# TCP sine-sweep amplitude [m]
SWING_AMP = 0.1
# TCP sine-sweep frequency [Hz]
SWING_FREQ = 0.3
# Pressing force to apply during the unified motion-force control [N]
PRESSING_FORCE = 5.0
# Cartesian linear velocity used to search for contact [m/s]
SEARCH_VELOCITY = 0.02
# Maximum distance to travel when searching for contact [m]
SEARCH_DISTANCE = 1.0
# Maximum contact wrench during contact search for soft contact
MAX_WRENCH_FOR_CONTACT_SEARCH = [10.0, 10.0, 10.0, 3.0, 3.0, 3.0]
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
# Required arguments
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
argparser.add_argument(
"frequency", help="Command frequency, 1 to 100 [Hz]", type=int
)
# Optional arguments
argparser.add_argument(
"--TCP",
action="store_true",
help="Use TCP frame as reference frame for force control, otherwise use world frame",
)
argparser.add_argument(
"--polish",
action="store_true",
help="Run a simple polish motion along XY plane in world frame, otherwise hold robot motion in non-force-control axes",
)
args = argparser.parse_args()
# Check if arguments are valid
frequency = args.frequency
assert frequency >= 1 and frequency <= 100, "Invalid <frequency> input"
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial runs non-real-time Cartesian-space unified "
"motion-force control. The Z axis of the chosen reference frame will be activated for "
"explicit force control, while the rest axes in the same reference frame will stay motion "
"controlled.\n"
)
# The reference frame to use, see Robot::SendCartesianMotionForce() for more details
force_ctrl_frame = flexivrdk.CoordType.WORLD
if args.TCP:
logger.info("Reference frame used for force control: robot TCP frame")
force_ctrl_frame = flexivrdk.CoordType.TCP
else:
logger.info("Reference frame used for force control: robot world frame")
# Whether to enable polish motion
if args.polish:
logger.info(
"Robot will run a polish motion along XY plane in robot world frame"
)
else:
logger.info("Robot will hold its motion in all non-force-controlled axes")
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Move robot to home pose
logger.info("Moving to home pose")
robot.Home()
# Zero Force-torque Sensor
# =========================================================================================
# Direct Cartesian control can only be executed by single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
robot.SwitchMode(mode.NRT_PRIMITIVE_EXECUTION)
# IMPORTANT: must zero force/torque sensor offset for accurate force/torque measurement
robot.ExecutePrimitive(
{
group: flexivrdk.PrimitiveArgs("ZeroFTSensor", dict())
for group in single_arm_groups
}
)
# WARNING: during the process, the robot must not contact anything, otherwise the result
# will be inaccurate and affect following operations
logger.warn(
"Zeroing force/torque sensors, make sure nothing is in contact with the robot"
)
# Wait for primitive to finish
while not utility.primitive_state_true_for_groups(
robot.primitive_states(), "terminated"
):
time.sleep(1)
logger.info("Sensor zeroing complete")
# Search for Contact
# =========================================================================================
# NOTE: there are several ways to do contact search, such as using primitives, or real-time
# and non-real-time direct motion controls, etc. Here we use non-real-time direct Cartesian
# control for example.
logger.info("Searching for contact ...")
# Set initial poses to current TCP poses
all_init_pose = {}
for group in single_arm_groups:
all_init_pose[group] = robot.states()[group].tcp_pose.copy()
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Initial TCP pose [position 3x1, rotation (quaternion) 4x1]: {all_init_pose[group]}"
)
# Use non-real-time mode to make the robot go to a set point with its own motion generator
robot.SwitchMode(mode.NRT_CARTESIAN_MOTION_FORCE)
# Search for contact with max contact wrench set to a small value for making soft contact
for group in single_arm_groups:
robot.SetMaxContactWrench(group, MAX_WRENCH_FOR_CONTACT_SEARCH)
# Set target point along -Z direction and expect contact to happen during the travel
cmds = {}
for group, init_pose in all_init_pose.items():
target_pose = init_pose.copy()
target_pose[2] -= SEARCH_DISTANCE
cmds[group] = flexivrdk.NrtCartesianCmd(
target_pose, [0.0] * 6, [0.0] * 6, SEARCH_VELOCITY
)
# Send target point to robot to start searching for contact and limit the velocity. Keep
# target wrench 0 at this stage since we are not doing force control yet
robot.SendCartesianMotionForce(cmds)
# Use a while loop to poll robot states and check if a contact is made
is_contacted = False
while not is_contacted:
# Compute norm of sensed external force applied on robot TCP
for group, states in robot.states().items():
ext_force = np.array(states.tcp_wrench[:3])
# Contact is considered to be made if sensed TCP force exceeds the threshold
if np.linalg.norm(ext_force) > PRESSING_FORCE:
is_contacted = True
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Contact detected at robot TCP"
)
break
# Check at 1ms interval
time.sleep(0.001)
# Configure Force Control
# =========================================================================================
# Switch to non-real-time mode for discrete motion force control
robot.SwitchMode(mode.NRT_CARTESIAN_MOTION_FORCE)
# Set force control reference frame based on program argument. See function doc for more
# details
for group in single_arm_groups:
robot.SetForceControlFrame(group, force_ctrl_frame)
# Set which Cartesian axis(s) to activate for force control. See function doc for more
# details. Here we only active Z axis
for group in single_arm_groups:
robot.SetForceControlAxis(group, [False, False, True, False, False, False])
# Uncomment the following line to enable passive force control, otherwise active force
# control is used by default. See function doc for more details
# for group in single_arm_groups:
# robot.SetPassiveForceControl(group, True)
# NOTE: motion control always uses robot world frame, while force control can use
# either world or TCP frame as reference frame
# Start Unified Motion Force Control
# =========================================================================================
# Disable max contact wrench regulation. Need to do this AFTER the force control in Z axis
# is activated (i.e. motion control disabled in Z axis) and the motion force control mode
# is entered, this way the contact force along Z axis is explicitly regulated and will not
# spike after the max contact wrench regulation for motion control is disabled
for group in single_arm_groups:
robot.SetMaxContactWrench(group, [float("inf")] * 6)
# Update initial poses to current TCP poses
for group in single_arm_groups:
all_init_pose[group] = robot.states()[group].tcp_pose.copy()
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Initial TCP pose [position 3x1, rotation (quaternion) 4x1]: {all_init_pose[group]}"
)
# Periodic Task
# =========================================================================================
# Set loop period
period = 1.0 / frequency
logger.info(
f"Sending command to robot at {frequency} Hz, or {period} seconds interval"
)
# Periodic loop counter
loop_counter = 0
# Send command periodically at user-specified frequency
while True:
# Use sleep to control loop period
time.sleep(period)
# Monitor fault on the connected robot
if robot.fault():
raise Exception("Fault occurred on the connected robot, exiting ...")
# Set Fz according to reference frame to achieve a "pressing down" behavior
Fz = 0.0
if force_ctrl_frame == flexivrdk.CoordType.WORLD:
Fz = PRESSING_FORCE
elif force_ctrl_frame == flexivrdk.CoordType.TCP:
Fz = -PRESSING_FORCE
target_wrench = [0.0, 0.0, Fz, 0.0, 0.0, 0.0]
cmds = {}
for group, init_pose in all_init_pose.items():
target_pose = init_pose.copy()
if args.polish:
target_pose[1] += SWING_AMP * math.sin(
2 * math.pi * SWING_FREQ * loop_counter * period
)
cmds[group] = flexivrdk.NrtCartesianCmd(target_pose, target_wrench)
robot.SendCartesianMotionForce(cmds)
# Increment loop counter
loop_counter += 1
except Exception as e:
# Print exception error message
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
@@ -0,0 +1,132 @@
#!/usr/bin/env python
"""intermediate5_robot_dynamics.py
This tutorial runs the integrated dynamics engine to obtain robot Jacobian, mass matrix, and
gravity torques. Also checks reachability of a Cartesian pose.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import time
import argparse
import spdlog # pip install spdlog
import flexivrdk # pip install flexivrdk
def main():
# Program Setup
# ==============================================================================================
# Parse arguments
argparser = argparse.ArgumentParser()
argparser.add_argument(
"robot_sn",
help="Serial number of the robot to connect. Remove any space, e.g. Enlight-L-123456",
)
args = argparser.parse_args()
# Define alias
logger = spdlog.ConsoleLogger("Example")
mode = flexivrdk.Mode
# Print description
logger.info(
">>> Tutorial description <<<\nThis tutorial runs the integrated dynamics engine to obtain "
"robot Jacobian, mass matrix, and gravity torques. Also checks reachability of a Cartesian "
"pose.\n"
)
try:
# RDK Initialization
# ==========================================================================================
# Instantiate robot interface
robot = flexivrdk.Robot(args.robot_sn)
# Clear fault on the connected robot if any
if robot.fault():
logger.warn("Fault occurred on the connected robot, trying to clear ...")
# Try to clear the fault
if not robot.ClearFault():
logger.error("Fault cannot be cleared, exiting ...")
return 1
logger.info("Fault on the connected robot is cleared")
# Servo on the robot, make sure the E-stop is released
logger.info("Servo on the robot ...")
robot.ServoOn()
# Wait for the robot to become operational
while not robot.operational():
time.sleep(1)
logger.info("Robot is now operational")
# Move robot to home pose
logger.info("Moving to home pose")
robot.Home()
# Robot Dynamics
# ==========================================================================================
# Initialize dynamics engine
model = flexivrdk.Model(robot)
# Step dynamics engine 5 times
for i in range(5):
# Update robot model in dynamics engine
robot_states = robot.states()
model.Update(
robot_states[flexivrdk.JointGroup.ALL].q,
robot_states[flexivrdk.JointGroup.ALL].dq,
)
# Compute gravity vector
g = model.g()
# Compute mass matrix
M = model.M()
# Compute Jacobian
J = model.J("flange")
# Print result
logger.info("g = ")
print(g, flush=True)
logger.info("M = ")
print(M, flush=True)
logger.info("J = ")
print(J, flush=True)
print()
# Check IK feasibility for a nearby Cartesian pose on all available single-arm joint groups
single_arm_groups = robot.info().single_arm_groups
if not single_arm_groups:
raise RuntimeError("No single-arm joint group found on the connected robot")
robot_states = robot.states()
ik_params_by_group = {}
for group in single_arm_groups:
pose_to_check = robot_states[group].tcp_pose.copy()
pose_to_check[0] += 0.1
logger.info(
f"[{flexivrdk.kJointGroupNames[group]}] Checking IK feasibility of Cartesian pose {pose_to_check}"
)
ik_params = flexivrdk.IKParams()
ik_params.cartesian_pose = pose_to_check
ik_params.seed_q = robot_states[group].q.copy()
ik_params.free_orientation = False
ik_params_by_group[group] = ik_params
# Print result
result = model.SolveConstrainedIK(ik_params_by_group)
logger.info(f"IK result success = {result.success}")
for group, q in result.solved_q.items():
logger.info(f"[{flexivrdk.kJointGroupNames[group]}] solved_q = {q}")
except Exception as e:
logger.error(str(e))
return 1
if __name__ == "__main__":
main()
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env python
"""utility.py
Utility methods.
Import this module as `import utility` and use functions through the module namespace,
for example `utility.quat2eulerZYX(...)` and `utility.primitive_state_true_for_groups(...)`.
"""
__copyright__ = "Copyright (C) 2016-2026 Flexiv Ltd. All Rights Reserved."
__author__ = "Flexiv"
import math
# pip install scipy
from scipy.spatial.transform import Rotation as R
def quat2eulerZYX(quat, degree=False):
"""
Convert quaternion to Euler angles with ZYX axis rotations.
Parameters
----------
quat : float list
Quaternion input in [w,x,y,z] order.
degree : bool
Return values in degrees, otherwise in radians.
Returns
----------
float list
Euler angles in [x,y,z] order, radian by default unless specified otherwise.
Returned angles are normalized by scipy to a principal range. In degree mode,
this is typically within [-180, 180].
"""
# Convert target quaternion to Euler ZYX using scipy package's 'xyz' extrinsic rotation
# NOTE: scipy uses [x,y,z,w] order to represent quaternion. The returned Euler values
# are a normalized representation of the same physical orientation.
eulerZYX = (
R.from_quat([quat[1], quat[2], quat[3], quat[0]])
.as_euler("xyz", degrees=degree)
.tolist()
)
return eulerZYX
def primitive_state_true_for_groups(primitive_states, state_name):
"""
Check whether primitive states are true for all groups included in primitive_states.
Parameters
----------
primitive_states : dict
Primitive states keyed by joint group.
state_name : str or dict
Primitive state name to check for all groups, or per-group state names keyed by joint
group, e.g. "reachedTarget" or {group: "terminated"}.
Returns
----------
bool
True if all included groups contain the requested primitive state and its integer value is
non-zero.
"""
def primitive_state_true(primitive_state, requested_state_name):
if requested_state_name not in primitive_state.names_and_values:
return False
value = primitive_state.names_and_values[requested_state_name]
return isinstance(value, int) and value != 0
if isinstance(state_name, dict):
return all(
group in state_name
and primitive_state_true(primitive_state, state_name[group])
for group, primitive_state in primitive_states.items()
)
return all(
primitive_state_true(primitive_state, state_name)
for primitive_state in primitive_states.values()
)
def list2str(ls):
"""
Convert a list to a string.
Parameters
----------
ls : list
Source list of any size.
Returns
----------
str
A string with format "ls[0] ls[1] ... ls[n] ", i.e. each value
followed by a space, including the last one.
"""
ret_str = ""
for i in ls:
ret_str += str(i) + " "
return ret_str