imetro_behavior.joint_behaviors
1# Copyright (c) 2026, United States Government, as represented by the 2# Administrator of the National Aeronautics and Space Administration. 3# 4# All rights reserved. 5# 6# This software is licensed under the Apache License, Version 2.0 7# (the "License"); you may not use this file except in compliance with the 8# License. You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15# License for the specific language governing permissions and limitations 16# under the License. 17 18import yaml 19from ament_index_python.packages import get_package_share_path 20from py_trees.common import Status 21from py_trees.ports import BehaviourWithPorts, PortInformation 22 23from imetro_behavior.helpers import set_ros_node 24 25 26class JointNamesAndPositionsFromYaml(BehaviourWithPorts): 27 """Loads joint names and positions from a YAML file. 28 29 Valid yaml configuration for a joint state: 30 31 state_name: 32 joint_names: 33 - joint_1 34 - joint_2 35 - joint_3 36 positions: 37 - 0.1 38 - 0.2 39 - 0.3 40 41 No validation is done for the respective lengths of the names and positions here, 42 as these may not match up exactly in the event of multi-DOF joints. 43 Downstream consumers should do their own validation based on what they support. 44 """ 45 46 INPUT_PORTS = { 47 "package_name": PortInformation(data_type=str, required=True), 48 "yaml_file": PortInformation(data_type=str, required=True), 49 "state_name": PortInformation(data_type=str, required=True), 50 } 51 52 OUTPUT_PORTS = { 53 "joint_names": PortInformation(data_type=list[str], required=True), 54 "joint_positions": PortInformation(data_type=list[float], required=True), 55 } 56 57 def setup(self, **kwargs): 58 """Get access to the node for error statements.""" 59 set_ros_node(self, **kwargs) 60 61 def update(self) -> Status: 62 """Load the YAML file and set the joint name and positions as an output port.""" 63 yaml_path = get_package_share_path(self.get_input("package_name")) / self.get_input("yaml_file") 64 if not yaml_path.is_file(): 65 self.logger.error(f"File at {yaml_path} could not be found or is not a file") 66 return Status.FAILURE 67 68 with open(yaml_path) as file: 69 data = yaml.safe_load(file) 70 state_name = self.get_input("state_name") 71 joint_dict = data.get(state_name) 72 if joint_dict is None: 73 self.logger.error(f"Failed to find joint configuration {state_name} in {yaml_path}") 74 return Status.FAILURE 75 76 joint_names = joint_dict.get("joint_names") 77 if joint_names is None: 78 self.logger.error(f"Joint configuration '{state_name}' does not have a 'joint_names' field.") 79 return Status.FAILURE 80 81 joint_positions = joint_dict.get("positions") 82 if joint_positions is None: 83 self.logger.error(f"Joint configuration '{state_name}' does not have a 'positions' field.") 84 return Status.FAILURE 85 86 self._set_output("joint_names", joint_names) 87 self._set_output("joint_positions", joint_positions) 88 return Status.SUCCESS
class
JointNamesAndPositionsFromYaml(py_trees.ports.BehaviourWithPorts):
27class JointNamesAndPositionsFromYaml(BehaviourWithPorts): 28 """Loads joint names and positions from a YAML file. 29 30 Valid yaml configuration for a joint state: 31 32 state_name: 33 joint_names: 34 - joint_1 35 - joint_2 36 - joint_3 37 positions: 38 - 0.1 39 - 0.2 40 - 0.3 41 42 No validation is done for the respective lengths of the names and positions here, 43 as these may not match up exactly in the event of multi-DOF joints. 44 Downstream consumers should do their own validation based on what they support. 45 """ 46 47 INPUT_PORTS = { 48 "package_name": PortInformation(data_type=str, required=True), 49 "yaml_file": PortInformation(data_type=str, required=True), 50 "state_name": PortInformation(data_type=str, required=True), 51 } 52 53 OUTPUT_PORTS = { 54 "joint_names": PortInformation(data_type=list[str], required=True), 55 "joint_positions": PortInformation(data_type=list[float], required=True), 56 } 57 58 def setup(self, **kwargs): 59 """Get access to the node for error statements.""" 60 set_ros_node(self, **kwargs) 61 62 def update(self) -> Status: 63 """Load the YAML file and set the joint name and positions as an output port.""" 64 yaml_path = get_package_share_path(self.get_input("package_name")) / self.get_input("yaml_file") 65 if not yaml_path.is_file(): 66 self.logger.error(f"File at {yaml_path} could not be found or is not a file") 67 return Status.FAILURE 68 69 with open(yaml_path) as file: 70 data = yaml.safe_load(file) 71 state_name = self.get_input("state_name") 72 joint_dict = data.get(state_name) 73 if joint_dict is None: 74 self.logger.error(f"Failed to find joint configuration {state_name} in {yaml_path}") 75 return Status.FAILURE 76 77 joint_names = joint_dict.get("joint_names") 78 if joint_names is None: 79 self.logger.error(f"Joint configuration '{state_name}' does not have a 'joint_names' field.") 80 return Status.FAILURE 81 82 joint_positions = joint_dict.get("positions") 83 if joint_positions is None: 84 self.logger.error(f"Joint configuration '{state_name}' does not have a 'positions' field.") 85 return Status.FAILURE 86 87 self._set_output("joint_names", joint_names) 88 self._set_output("joint_positions", joint_positions) 89 return Status.SUCCESS
Loads joint names and positions from a YAML file.
Valid yaml configuration for a joint state:
state_name: joint_names: - joint_1 - joint_2 - joint_3 positions: - 0.1 - 0.2 - 0.3
No validation is done for the respective lengths of the names and positions here, as these may not match up exactly in the event of multi-DOF joints. Downstream consumers should do their own validation based on what they support.
INPUT_PORTS =
{'package_name': PortInformation(data_type=<class 'str'>, required=True, description='', default_value=None), 'yaml_file': PortInformation(data_type=<class 'str'>, required=True, description='', default_value=None), 'state_name': PortInformation(data_type=<class 'str'>, required=True, description='', default_value=None)}
OUTPUT_PORTS =
{'joint_names': PortInformation(data_type=list[str], required=True, description='', default_value=None), 'joint_positions': PortInformation(data_type=list[float], required=True, description='', default_value=None)}
def
setup(self, **kwargs):
58 def setup(self, **kwargs): 59 """Get access to the node for error statements.""" 60 set_ros_node(self, **kwargs)
Get access to the node for error statements.
def
update(self) -> py_trees.common.Status:
62 def update(self) -> Status: 63 """Load the YAML file and set the joint name and positions as an output port.""" 64 yaml_path = get_package_share_path(self.get_input("package_name")) / self.get_input("yaml_file") 65 if not yaml_path.is_file(): 66 self.logger.error(f"File at {yaml_path} could not be found or is not a file") 67 return Status.FAILURE 68 69 with open(yaml_path) as file: 70 data = yaml.safe_load(file) 71 state_name = self.get_input("state_name") 72 joint_dict = data.get(state_name) 73 if joint_dict is None: 74 self.logger.error(f"Failed to find joint configuration {state_name} in {yaml_path}") 75 return Status.FAILURE 76 77 joint_names = joint_dict.get("joint_names") 78 if joint_names is None: 79 self.logger.error(f"Joint configuration '{state_name}' does not have a 'joint_names' field.") 80 return Status.FAILURE 81 82 joint_positions = joint_dict.get("positions") 83 if joint_positions is None: 84 self.logger.error(f"Joint configuration '{state_name}' does not have a 'positions' field.") 85 return Status.FAILURE 86 87 self._set_output("joint_names", joint_names) 88 self._set_output("joint_positions", joint_positions) 89 return Status.SUCCESS
Load the YAML file and set the joint name and positions as an output port.