Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ Copyright 2025 Enactic, Inc.

All participation in the OpenArm project is governed by our
[Code of Conduct](CODE_OF_CONDUCT.md).

Control settings live directly under each component's `control` mapping. Set `hardware_plugin`, `can_interface` and `can_fd` in the preset; `use_fake_hardware:=true` selects `mock_components/GenericSystem`. The optional `system_name` defaults to `<component_name>_hardware_interface`. Explicit control parameters override generated defaults and are emitted once. Components declare their generated topology, names and mimic relationships in `describe_component`; assembly and control consume that shared description.
59 changes: 24 additions & 35 deletions assets/common/assembly.xacro
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,21 @@ limitations under the License.

<xacro:include filename="$(find openarm_description)/assets/common/components.xacro" />

<xacro:macro name="openarm_collect_collapsible_children"
params="names robot_preset component_api options out_name index:=0 candidates:=${dict()}">
<xacro:macro name="openarm_collect_component_descriptions"
params="names robot_preset component_api options out_name index:=0 descriptions:=${dict()}">
<xacro:if value="${index &lt; len(names)}">
<xacro:property name="name" value="${names[index]}" />
<xacro:call macro="${component_api['describe']}" component_name="${name}" component_cfg="${robot_preset[name]}"
options="${options}" out_name="description" />
<xacro:if value="${description.get('supports_frame_shift', False)}">
<xacro:property name="candidates" value="${dict(list(candidates.items()) + [(name, description)])}" lazy_eval="false" />
</xacro:if>
<xacro:openarm_collect_collapsible_children names="${names}" robot_preset="${robot_preset}"
component_api="${component_api}" options="${options}" index="${index + 1}" candidates="${candidates}" out_name="candidates" />
<xacro:property name="descriptions" value="${dict(list(descriptions.items()) + [(name, description)])}" lazy_eval="false" />
<xacro:openarm_collect_component_descriptions names="${names}" robot_preset="${robot_preset}"
component_api="${component_api}" options="${options}" index="${index + 1}" descriptions="${descriptions}" out_name="descriptions" />
</xacro:if>
<xacro:property name="${out_name}" value="${candidates}" scope="parent" lazy_eval="false" />
<xacro:property name="${out_name}" value="${descriptions}" scope="parent" lazy_eval="false" />
</xacro:macro>

<xacro:macro name="openarm_resolve_component_runtime"
params="component_api component_name components robot_preset config_root root_frame component_cfg component_desc parent_desc:=none emit_grasp_frame:=false out_name">
params="component_descriptions component_name components robot_preset config_root root_frame component_cfg component_desc parent_desc:=none emit_grasp_frame:=false out_name">

<xacro:property name="none_value" value="${'none'}" lazy_eval="false" />
<xacro:property name="zero_origin" value="${dict(x=0.0, y=0.0, z=0.0, roll=0.0, pitch=0.0, yaw=0.0)}" lazy_eval="false" />
Expand Down Expand Up @@ -134,8 +132,9 @@ limitations under the License.
value="${[name for name in attached_children if component_terminal_link_empty and 'parent_reference_point' in robot_preset[name]['connection'] and robot_preset[name]['connection']['parent_reference_point'] != None and ('parent' not in robot_preset[name]['connection'] or robot_preset[name]['connection']['parent'] == None) and robot_preset[name]['connection']['parent_reference_point'] in component_desc['reference_points'] and component_desc['reference_points'][robot_preset[name]['connection']['parent_reference_point']]['parent'] == component_terminal_link]}"
lazy_eval="false" />

<xacro:openarm_collect_collapsible_children names="${collapse_candidate_names}" robot_preset="${robot_preset}"
component_api="${component_api}" options="${dict(emit_grasp_frame=emit_grasp_frame)}" out_name="candidate_descriptions" />
<xacro:property name="candidate_descriptions"
value="${dict([(name, component_descriptions[name]) for name in collapse_candidate_names if component_descriptions[name].get('supports_frame_shift', False)])}"
lazy_eval="false" />
<xacro:openarm_require condition="${len(candidate_descriptions) &lt;= 1}"
message="${'Invalid robot preset: component %s has multiple children competing to collapse the same terminal empty link.' % component_name}" />
<xacro:if value="${len(candidate_descriptions) == 1}">
Expand Down Expand Up @@ -170,20 +169,12 @@ limitations under the License.
</xacro:macro>

<xacro:macro name="openarm_emit_component_tree"
params="component_api component_name components robot_preset config_root root_frame parent_desc:=none preloaded_desc:=none emit_grasp_frame:=false">
params="component_api component_descriptions component_name components robot_preset config_root root_frame parent_desc:=none emit_grasp_frame:=false">

<xacro:property name="none_value" value="${'none'}" lazy_eval="false" />
<xacro:property name="component_cfg" value="${robot_preset[component_name]}" lazy_eval="false" />
<xacro:property name="component_desc" value="${preloaded_desc}" lazy_eval="false" />
<xacro:property name="component_desc" value="${component_descriptions[component_name]}" lazy_eval="false" />

<xacro:if value="${component_desc == none_value}">
<xacro:call macro="${component_api['describe']}"
component_name="${component_name}"
component_cfg="${component_cfg}"
out_name="component_desc" options="${dict(emit_grasp_frame=emit_grasp_frame)}" />
</xacro:if>

<xacro:openarm_resolve_component_runtime component_api="${component_api}"
<xacro:openarm_resolve_component_runtime component_descriptions="${component_descriptions}"
component_name="${component_name}"
components="${components}"
robot_preset="${robot_preset}"
Expand All @@ -204,21 +195,19 @@ limitations under the License.
component_runtime="${component_runtime}"
options="${dict(emit_grasp_frame=emit_grasp_frame)}" />

<xacro:openarm_emit_attached_components component_api="${component_api}"
<xacro:openarm_emit_attached_components component_api="${component_api}" component_descriptions="${component_descriptions}"
index="0"
components="${components}"
parent_component_name="${component_name}"
robot_preset="${robot_preset}"
config_root="${config_root}"
root_frame="${root_frame}"
parent_desc="${component_desc}"
preloaded_component_name="${component_runtime['collapse_child_name']}"
preloaded_component_desc="${component_runtime['collapse_child_desc']}"
emit_grasp_frame="${emit_grasp_frame}" />
</xacro:macro>

<xacro:macro name="openarm_emit_attached_components"
params="component_api index components parent_component_name robot_preset config_root root_frame parent_desc:=none preloaded_component_name:=none preloaded_component_desc:=none emit_grasp_frame:=false">
params="component_api component_descriptions index components parent_component_name robot_preset config_root root_frame parent_desc:=none emit_grasp_frame:=false">

<xacro:if value="${index &lt; len(components)}">
<xacro:property name="none_value" value="${'none'}" lazy_eval="false" />
Expand All @@ -229,37 +218,32 @@ limitations under the License.
<xacro:property name="attach_to" value="${connection['attach_to'] if 'attach_to' in connection else None}" lazy_eval="false" />
<xacro:property name="is_root_component" value="${parent_component_name == '__root__' and attach_to == None}" lazy_eval="false" />
<xacro:property name="is_attached_child" value="${attach_to == parent_component_name}" lazy_eval="false" />
<!-- Reuse a preloaded descriptor when collapse analysis already loaded it for this child. -->
<xacro:property name="component_desc_override" value="${preloaded_component_desc if component_name == preloaded_component_name else none_value}" lazy_eval="false" />

<xacro:if value="${component_enabled and (is_root_component or is_attached_child)}">
<xacro:openarm_emit_component_tree component_api="${component_api}"
<xacro:openarm_emit_component_tree component_api="${component_api}" component_descriptions="${component_descriptions}"
component_name="${component_name}"
components="${components}"
robot_preset="${robot_preset}"
config_root="${config_root}"
root_frame="${root_frame}"
parent_desc="${none_value if is_root_component else parent_desc}"
preloaded_desc="${component_desc_override}"
emit_grasp_frame="${emit_grasp_frame}" />
</xacro:if>

<xacro:openarm_emit_attached_components component_api="${component_api}"
<xacro:openarm_emit_attached_components component_api="${component_api}" component_descriptions="${component_descriptions}"
index="${index + 1}"
components="${components}"
parent_component_name="${parent_component_name}"
robot_preset="${robot_preset}"
config_root="${config_root}"
root_frame="${root_frame}"
parent_desc="${parent_desc}"
preloaded_component_name="${preloaded_component_name}"
preloaded_component_desc="${preloaded_component_desc}"
emit_grasp_frame="${emit_grasp_frame}" />
</xacro:if>
</xacro:macro>

<xacro:macro name="openarm_assemble_robot"
params="component_api config_root robot_preset emit_grasp_frame:=false">
params="component_api config_root robot_preset emit_grasp_frame:=false out_name:=component_descriptions">

<xacro:property name="robot_cfg" value="${robot_preset['robot']}" lazy_eval="false" />
<xacro:property name="root_frame" value="${robot_cfg['root_frame']}" lazy_eval="false" />
Expand All @@ -269,9 +253,14 @@ limitations under the License.
components="${components}"
robot_preset="${robot_preset}" />

<xacro:property name="enabled_components" value="${[name for name in components if robot_preset[name]['enabled']]}" lazy_eval="false" />
<xacro:openarm_collect_component_descriptions names="${enabled_components}" robot_preset="${robot_preset}"
component_api="${component_api}" options="${dict(emit_grasp_frame=emit_grasp_frame)}" out_name="component_descriptions" />
<xacro:property name="${out_name}" value="${component_descriptions}" scope="parent" lazy_eval="false" />

<link name="${root_frame}" />

<xacro:openarm_emit_attached_components component_api="${component_api}"
<xacro:openarm_emit_attached_components component_api="${component_api}" component_descriptions="${component_descriptions}"
index="0"
components="${components}"
parent_component_name="__root__"
Expand Down
119 changes: 119 additions & 0 deletions assets/common/ros2_control.xacro
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?xml version="1.0"?>
<!--
Copyright 2026 Enactic, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find openarm_description)/assets/common/validation.xacro" />

<xacro:macro name="openarm_control_parameters" params="values index:=0">
<xacro:property name="keys" value="${list(values.keys())}" />
<xacro:if value="${index &lt; len(keys)}">
<param name="${keys[index]}">${values[keys[index]]}</param>
<xacro:openarm_control_parameters values="${values}" index="${index + 1}" />
</xacro:if>
</xacro:macro>

<xacro:macro name="openarm_control_joint" params="joint_name">
<joint name="${joint_name}">
<command_interface name="position" />
<command_interface name="velocity" />
<command_interface name="effort" />
<state_interface name="position"><param name="initial_value">0.0</param></state_interface>
<state_interface name="velocity"><param name="initial_value">0.0</param></state_interface>
<state_interface name="effort"><param name="initial_value">0.0</param></state_interface>
</joint>
</xacro:macro>

<xacro:macro name="openarm_control_joints" params="joint_names index:=0">
<xacro:if value="${index &lt; len(joint_names)}">
<xacro:openarm_control_joint joint_name="${joint_names[index]}" />
<xacro:openarm_control_joints joint_names="${joint_names}" index="${index + 1}" />
</xacro:if>
</xacro:macro>

<xacro:macro name="openarm_control_joint_names" params="description out_name">
<xacro:property name="${out_name}"
value="${[description['prefix'] + name for name, joint in description['topology']['joints'].items() if joint['type'] != 'fixed' and name not in description['joint_mimics']]}"
scope="parent" lazy_eval="false" />
</xacro:macro>

<xacro:macro name="openarm_arm_control_parameters" params="description ee_type arm_type out_name">
<xacro:property name="gains" value="${xacro.load_yaml(description['config_root'] + '/control/control_gains.yaml')}" lazy_eval="false" />
<xacro:property name="prefix" value="${description['prefix']}" />
<xacro:property name="parameters"
value="${dict(arm_type=arm_type, arm_prefix=prefix[8:] if prefix.startswith('openarm_') else prefix, hand=ee_type != '', ee_type=ee_type)}" lazy_eval="false" />
<xacro:property name="gain_parameters"
value="${dict([(kind + str(index), gains['joint' + str(index)][kind]) for kind in ['kp', 'kd'] for index in [1, 2, 3, 4, 5, 6, 7]])}" lazy_eval="false" />
<xacro:property name="parameters" value="${dict(parameters, **gain_parameters)}" lazy_eval="false" />
<xacro:if value="${ee_type != '' and 'hand' in gains}">
<xacro:property name="parameters" value="${dict(parameters, kp_hand=gains['hand']['kp'], kd_hand=gains['hand']['kd'])}" lazy_eval="false" />
</xacro:if>
<xacro:property name="${out_name}" value="${parameters}" scope="parent" lazy_eval="false" />
</xacro:macro>

<xacro:macro name="openarm_ros2_control"
params="robot_preset component_descriptions arm_type:=v20 use_fake_hardware:=true fake_sensor_commands:=false index:=0">
<xacro:property name="components" value="${list(component_descriptions)}" lazy_eval="false" />
<xacro:if value="${index &lt; len(components)}">
<xacro:property name="name" value="${components[index]}" lazy_eval="false" />
<xacro:property name="component" value="${robot_preset[name]}" lazy_eval="false" />
<xacro:if value="${'control' in component}">
<xacro:property name="control" value="${component['control']}" lazy_eval="false" />
<xacro:property name="description" value="${component_descriptions[name]}" lazy_eval="false" />
<xacro:openarm_require condition="${'side' not in control and 'parameters' not in control}"
message="${'Component `%s` requires flat control settings, including can_interface for OpenArm hardware.' % name}" />
<xacro:property name="system_name" value="${control.get('system_name', name + '_hardware_interface')}" lazy_eval="false" />
<xacro:property name="controlled_components" value="${[key for key in components if 'control' in robot_preset[key]]}" lazy_eval="false" />
<xacro:openarm_require condition="${len([key for key in controlled_components if robot_preset[key]['control'].get('system_name', key + '_hardware_interface') == system_name]) == 1}"
message="${'Duplicate control system name `%s`.' % system_name}" />
<xacro:openarm_control_joint_names description="${description}" out_name="joint_names" />
<xacro:property name="ee_type" value="${''}" lazy_eval="false" />
<xacro:if value="${description['component_type'] == 'arm'}">
<xacro:property name="grippers"
value="${[key for key in components if component_descriptions[key]['component_type'] == 'end_effector' and robot_preset[key].get('connection', dict()).get('attach_to') == name and 'control' not in robot_preset[key]]}" lazy_eval="false" />
<xacro:openarm_require condition="${len(grippers) &lt;= 1}" message="${'Arm `%s` supports one attached gripper in its control system.' % name}" />
<xacro:if value="${grippers}">
<xacro:property name="gripper" value="${component_descriptions[grippers[0]]}" lazy_eval="false" />
<xacro:openarm_control_joint_names description="${gripper}" out_name="gripper_joint_names" />
<xacro:property name="joint_names" value="${joint_names + gripper_joint_names}" lazy_eval="false" />
<xacro:property name="ee_type" value="${gripper['product']}" lazy_eval="false" />
</xacro:if>
</xacro:if>
<xacro:property name="parameters" value="${dict()}" lazy_eval="false" />
<xacro:property name="plugin" value="${control.get('hardware_plugin', '')}" lazy_eval="false" />
<xacro:if value="${use_fake_hardware}">
<xacro:property name="plugin" value="mock_components/GenericSystem" />
<xacro:property name="parameters" value="${dict(mock_sensor_commands=fake_sensor_commands, position_state_following_offset=0.0)}" lazy_eval="false" />
</xacro:if>
<xacro:unless value="${use_fake_hardware}">
<xacro:openarm_require condition="${plugin != ''}" message="${'Configure control.hardware_plugin on `%s` for real hardware.' % name}" />
<xacro:if value="${plugin == 'openarm_hardware/OpenArmHW'}">
<xacro:openarm_require condition="${'can_interface' in control}" message="${'Configure control.can_interface on `%s`.' % name}" />
<xacro:openarm_arm_control_parameters description="${description}" ee_type="${ee_type}" arm_type="${arm_type}" out_name="parameters" />
</xacro:if>
</xacro:unless>
<xacro:property name="configured_parameters" value="${dict([(key, value) for key, value in control.items() if key not in ['system_name', 'hardware_plugin']])}" lazy_eval="false" />
<xacro:property name="parameters" value="${dict(parameters, **configured_parameters)}" lazy_eval="false" />
<ros2_control name="${system_name}" type="system">
<hardware>
<plugin>${plugin}</plugin>
<xacro:openarm_control_parameters values="${parameters}" />
</hardware>
<xacro:openarm_control_joints joint_names="${joint_names}" />
</ros2_control>
</xacro:if>
<xacro:openarm_ros2_control robot_preset="${robot_preset}" component_descriptions="${component_descriptions}"
arm_type="${arm_type}" use_fake_hardware="${use_fake_hardware}" fake_sensor_commands="${fake_sensor_commands}" index="${index + 1}" />
</xacro:if>
</xacro:macro>
</robot>
Loading
Loading