Robot Systems and Components
A system is one robot arm plus everything attached to it. This guide covers
how a System is assembled, the component catalogue, custom tools, and
adding a system from a connected robot.
Anatomy of a system
world
└─ system base header: pose_in_parent in its parent
└─ external_robot_axis (ExternalAxis, optional) e.g. a linear rail
└─ manipulator_adapter (Adapter, optional)
└─ manipulator (Manipulator) the arm, e.g. "arm"
└─ wrist_sensor (WristSensor, optional)
└─ tool_adapter (ToolAdapter, optional)
└─ tool[] (Tool, optional)
└─ external_workpiece[] (ExternalWorkpiece, optional) e.g. a rotary table
The header places the system base at pose_in_parent in its parent. The
pose is converted to the world once, when the system is saved, so the system
does not follow its parent if the parent moves later. Get returns parent
world and the pose in the world. The parent cannot be the system itself,
one of its links, or a frame attached to them.
Field |
Type |
Description |
|---|---|---|
|
|
Required. System name, parent and the pose of the system base in the parent. |
|
|
Required. For example |
|
|
Required. |
|
|
Axis the arm is mounted on (rail, pan-tilt platform, …). Requires
|
|
|
Adapter on the external axis flange. |
|
|
Force/torque sensor. Requires |
|
|
Catalogue adapter. Requires |
|
|
Catalogue or custom tools ( |
|
|
Actuated devices that move workpieces independently of the arm. |
An Adapter is a rigid offset: xyz (m) and rpy (rad) give the pose of
the child base in the parent flange, with optional mass and inertia.
def create_system(name, arm_serial_number, control_box_serial_number, px, py, pz):
system = System(
header=make_header(name, px=px, py=py, pz=pz),
control_box_serial_number=control_box_serial_number,
)
system.manipulator_adapter.name = "manipulator_adapter"
system.manipulator_adapter.mass = 0.0
system.manipulator_adapter.inertia.extend([0.0] * 9)
system.manipulator_adapter.xyz.extend([0.0, 0.0, 0.0])
system.manipulator_adapter.rpy.extend([0.0, 0.0, 0.0])
system.manipulator.name = "arm"
system.manipulator.arm_serial_number = arm_serial_number
system.manipulator.lower_limit.extend([-2.76] * 7)
system.manipulator.upper_limit.extend([2.76] * 7)
system.manipulator.home_position.extend([0.0] * 7)
return system
Component catalogue
ComponentsService (client.system_config) lists the components the server
knows about. Each list call takes google.protobuf.Empty:
ListManipulatorsreturnsnames;ListToolAdapters,ListTools,ListCustomToolAdapters,ListCustomTools,ListExternalRobotAxes,ListExternalWorkpieceAxesreturnitemsofManufacturerModel(manufacturer, model).
Use a listed manufacturer/model pair in Tool, ToolAdapter or
ExternalAxis.
print("")
print("Listing tool adapters...")
try:
tool_adapters_response = client.system_config.stub.ListToolAdapters(empty_pb2.Empty())
if (tool_adapters_response.status.code == 0):
for item in tool_adapters_response.items:
print(f"Tool adapter manufacturer: {item.manufacturer}, model: {item.model}")
except grpc.RpcError as e:
print(f"ListToolAdapters failed: {e}")
Custom tools and adapters
Define your own end effector with CustomTool (tcp pose relative to the
tool base, plus optional inertial, collision and visual) or
CustomToolAdapter (one or more flange poses). Save it to the catalogue,
then reference it from a Tool by manufacturer and model:
print("")
print("Creating custom tool...")
dummy_shape = Shape(box=Box(size=[0.05, 0.05, 0.05]))
dummy_origin = SE3Pose(
position=Vector3(x=0.0, y=0.0, z=0.05),
rotation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)
)
custom_tool = CustomTool(
manufacturer="example_manufacturer",
model="example_model",
tcp=SE3Pose(
position=Vector3(x=0.0, y=0.0, z=0.1),
rotation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)
),
collision=[Collision(origin=dummy_origin, shape=dummy_shape)],
visual=[Visual(origin=dummy_origin, shape=dummy_shape)]
)
try:
save_tool_response = client.system_config.stub.SaveCustomTool(
SaveCustomToolRequest(custom_tool=custom_tool)
)
if save_tool_response.status.code == 0:
print("Custom tool saved successfully.")
else:
print(f"SaveCustomTool returned status: {save_tool_response.status}")
except grpc.RpcError as e:
print(f"SaveCustomTool failed: {e}")
# Helper to construct header
def make_header(name, parent="world", px=0.0, py=0.0, pz=0.0, rw=1.0, rx=0.0, ry=0.0, rz=0.0):
return ConfigHeader(
name=name,
parent=parent,
pose_in_parent=SE3Pose(
position=Vector3(x=px, y=py, z=pz),
rotation=Quaternion(x=rx, y=ry, z=rz, w=rw)
)
)
print("")
print("Saving robot system with custom tool to the scene...")
sys2 = System(
header=make_header("system_2", px=1.0, py=0.0, pz=0.0),
control_box_serial_number="CX01-01-T1-000002",
manipulator=Manipulator(
name="arm",
arm_serial_number="A02L-00-M3-000002"
),
tool=[Tool(
name="example_tool",
manufacturer="example_manufacturer",
model="example_model"
)]
)
try:
client.config.stub.Save(SaveConfigRequest(system=sys2))
except grpc.RpcError:
pass
RemoveCustomTool and RemoveCustomToolAdapter take a ManufacturerModel.
Adding a system from a real robot
SystemService.AddConnectedSystem connects to a physical robot (through RDK)
by serial number, reads its configuration, and saves it as a system in the
active workcell. Afterwards, read it back with ConfigService.Get.
print("Add a system by connecting to the RDK and saving its configuration to the PDK server")
system_name = "system_1"
serial_number = "Rizon4-062065"
try:
# Load the system from RDK
add_system_response = client.system.stub.AddConnectedSystem(AddConnectedSystemRequest(name=system_name, serial_number=serial_number))
if add_system_response.status.code == 0:
print(f"Successfully added system: {system_name} with serial number: {serial_number}")
else:
print(f"Failed to add system: {system_name} with serial number: {serial_number}")
except grpc.RpcError as e:
print(f"System operation failed: {e}")
# 4. List existing systems in the scene
try:
list_systems_response = client.config.stub.List(ListConfigRequest(type=ConfigType.CONFIG_TYPE_SYSTEM))
print("Existing systems in the scene:")
for sys in list_systems_response.names:
print(f"- {sys}")
except grpc.RpcError as e:
print(f"Failed to list systems: {e}")
Requirements: the robot is reachable and RDK remote mode is enabled. Change the
serial number to your robot’s. AddConnectedSystem takes the robot’s public
serial number (as used by RDK, e.g. "Rizon4-062065"), not the internal
arm_serial_number stored in Manipulator.
To reposition a connected system, Get it, edit header.pose_in_parent,
and Save it again:
print("Get system_1 and set its pose in the scene...")
try:
system_response = client.config.stub.Get(
TypeNameConfigRequest(type=ConfigType.CONFIG_TYPE_SYSTEM, name=system_name)
)
if system_response.status.code == 0:
system = system_response.system
system.header.pose_in_parent.position.x = 0.73
system.header.pose_in_parent.position.y = -1.7655
system.header.pose_in_parent.position.z = 0.5
# Save the updated system configuration to the PDK server
save_response = client.config.stub.Save(SaveConfigRequest(system=system))
if save_response.status.code == 0:
print(f"Successfully set pose for system: {system_name}")
else:
print(f"Failed to set pose for system: {system_name}")
else:
print(f"Failed to get system: {system_name}")
except grpc.RpcError as e:
print(f"System operation failed: {e}")
Other system queries
SystemService (client.system):
GetUrdf(GetUrdfRequest(name))URDF of one system.
GetArmSerialNumber(GetArmSerialNumberRequest(system_name))arm_serial_numberandarm_public_serial_number.GetCalibrationStatus(GetCalibrationStatusRequest(system_name))calibratedis true when a calibration exists and was applied.
Default versus current joint position
A system in the scene has two joint vectors. Neither is the real robot’s position; moving a real or simulated robot is done with the Flexiv RDK.
Default joint position:
Manipulator.home_position(rad) andExternalAxis.home_position(m or rad) in the savedSystem. Change it by editing the system and saving it withConfigService.Save.Current joint position:
StateService.GetJointPosition/SetJointPosition(client.system_state). The joint positions the system has in the scene right now, used by planning, collision checks and the system’s frames. A system starts at its default position when it is added or its configuration changes.SetJointPositiontakes effect immediately and is not saved.
GetJointPositionRequest and SetJointPositionRequest take
system_name and an optional joint_group: JOINT_GROUP_SYSTEM (the
default, all actuated joints), JOINT_GROUP_ARM,
JOINT_GROUP_EXTERNAL_ROBOT_AXIS or JOINT_GROUP_EXTERNAL_WORKPIECE_AXIS.
SetJointPositionRequest.joint_position has one value per joint in the group.
from flexiv.pdk.system.state_service_pb2 import SetJointPositionRequest
from flexiv.pdk.system.components.joint_group_pb2 import JointGroup
res = client.system_state.stub.SetJointPosition(SetJointPositionRequest(
system_name="system_1",
joint_group=JointGroup.JOINT_GROUP_ARM,
joint_position=[0.0, -0.7, 0.0, 1.57, 0.0, 0.7, 0.0],
))
if res.status.code != 0:
raise RuntimeError(res.status.message)