1010The `mode` parameter selects the teleoperation scenario and which topics are
1111published:
1212
13- - controller_teleop (default): ee_poses (from controller aim pose), root_twist, root_pose,
14- finger_joints (retargeted TriHand angles), and TF transforms for
15- left/right wrists
13+ - controller_teleop (default): ee_poses (from controller aim pose), root_twist,
14+ root_pose, finger_joints (retargeted TriHand angles),
15+ controller_data, and TF transforms for left/right wrists
1616 - hand_teleop: ee_poses (from hand tracking wrist), hand (finger joint poses),
17- root_twist, root_pose, and TF transforms for left/right wrists
17+ root_twist/root_pose (from foot pedal locomotion), and TF
18+ transforms for left/right wrists
1819 - controller_raw: controller_data only
1920 - full_body: full_body and controller_data
2021
5960from isaacteleop .retargeting_engine .deviceio_source_nodes import (
6061 ControllersSource ,
6162 FullBodySource ,
63+ Generic3AxisPedalSource ,
6264 HandsSource ,
6365)
66+ from isaacteleop .retargeting_engine .deviceio_source_nodes .pedals_source import (
67+ DEFAULT_PEDAL_COLLECTION_ID ,
68+ )
6469from isaacteleop .retargeting_engine .interface import OptionalTensorGroup , OutputCombiner
6570from isaacteleop .retargeters import (
71+ FootPedalRootCmdRetargeter ,
72+ FootPedalRootCmdRetargeterConfig ,
6673 LocomotionRootCmdRetargeter ,
6774 LocomotionRootCmdRetargeterConfig ,
6875 TriHandMotionControllerRetargeter ,
@@ -161,6 +168,30 @@ def _apply_transform_to_pose(
161168 return result
162169
163170
171+ def _build_plugins (
172+ use_mock_operators : bool ,
173+ * ,
174+ include_synthetic_hands : bool ,
175+ search_start : Path ,
176+ ) -> List [PluginConfig ]:
177+ if not use_mock_operators or not include_synthetic_hands :
178+ return []
179+
180+ plugin_paths = []
181+ env_paths = os .environ .get ("ISAAC_TELEOP_PLUGIN_PATH" )
182+ if env_paths :
183+ plugin_paths .extend ([Path (p ) for p in env_paths .split (os .pathsep ) if p ])
184+ plugin_paths .extend (_find_plugins_dirs (search_start ))
185+
186+ return [
187+ PluginConfig (
188+ plugin_name = "controller_synthetic_hands" ,
189+ plugin_root_id = "synthetic_hands" ,
190+ search_paths = plugin_paths ,
191+ )
192+ ]
193+
194+
164195def _find_plugins_dirs (start : Path ) -> List [Path ]:
165196 candidates : List [Path ] = []
166197 for parent in [start , * start .parents ]:
@@ -446,6 +477,16 @@ def __init__(self) -> None:
446477 self .declare_parameter ("mode" , "controller_teleop" )
447478 self .declare_parameter ("rate_hz" , 60.0 )
448479 self .declare_parameter ("use_mock_operators" , value = False )
480+ self .declare_parameter (
481+ "pedal_collection_id" ,
482+ DEFAULT_PEDAL_COLLECTION_ID ,
483+ ParameterDescriptor (
484+ description = (
485+ "Tensor collection ID used for hand_teleop foot pedal locomotion. "
486+ "Must match the pedal pusher or reader collection_id."
487+ )
488+ ),
489+ )
449490
450491 self .declare_parameter (
451492 "transform_translation" ,
@@ -534,6 +575,12 @@ def __init__(self) -> None:
534575 self .get_logger ().info (f"Mode: { mode } " )
535576 self ._mode : str = mode
536577
578+ self ._pedal_collection_id : str = (
579+ self .get_parameter ("pedal_collection_id" ).get_parameter_value ().string_value
580+ )
581+ if not self ._pedal_collection_id :
582+ raise ValueError ("Parameter 'pedal_collection_id' must not be empty" )
583+
537584 self ._world_frame : str = (
538585 self .get_parameter ("world_frame" ).get_parameter_value ().string_value
539586 )
@@ -631,9 +678,37 @@ def __init__(self) -> None:
631678 JointState , "xr_teleop/finger_joints" , 10
632679 )
633680
634- hands = HandsSource (name = "hands" )
681+ self ._config = self ._build_session_config (
682+ self ._mode ,
683+ left_finger_joint_names ,
684+ right_finger_joint_names ,
685+ )
686+
687+ def _build_controller_raw_config (self ) -> TeleopSessionConfig :
688+ controllers = ControllersSource (name = "controllers" )
689+ pipeline = OutputCombiner (
690+ {
691+ "controller_left" : controllers .output (ControllersSource .LEFT ),
692+ "controller_right" : controllers .output (ControllersSource .RIGHT ),
693+ }
694+ )
695+
696+ return TeleopSessionConfig (
697+ app_name = "TeleopRos2Publisher" ,
698+ pipeline = pipeline ,
699+ plugins = _build_plugins (
700+ self ._use_mock_operators ,
701+ include_synthetic_hands = False ,
702+ search_start = Path (__file__ ).resolve (),
703+ ),
704+ )
705+
706+ def _build_controller_teleop_config (
707+ self ,
708+ left_finger_joint_names : Sequence [str ],
709+ right_finger_joint_names : Sequence [str ],
710+ ) -> TeleopSessionConfig :
635711 controllers = ControllersSource (name = "controllers" )
636- full_body = FullBodySource (name = "full_body" )
637712 locomotion = LocomotionRootCmdRetargeter (
638713 LocomotionRootCmdRetargeterConfig (), name = "locomotion"
639714 )
@@ -665,37 +740,92 @@ def __init__(self) -> None:
665740
666741 pipeline = OutputCombiner (
667742 {
668- "hand_left" : hands .output (HandsSource .LEFT ),
669- "hand_right" : hands .output (HandsSource .RIGHT ),
670743 "controller_left" : controllers .output (ControllersSource .LEFT ),
671744 "controller_right" : controllers .output (ControllersSource .RIGHT ),
672745 "root_command" : locomotion_connected .output ("root_command" ),
673- "full_body" : full_body .output (FullBodySource .FULL_BODY ),
674746 "finger_joints_left" : left_hand_connected .output ("hand_joints" ),
675747 "finger_joints_right" : right_hand_connected .output ("hand_joints" ),
676748 }
677749 )
678750
679- plugins : List [PluginConfig ] = []
680- if self ._use_mock_operators :
681- plugin_paths = []
682- env_paths = os .environ .get ("ISAAC_TELEOP_PLUGIN_PATH" )
683- if env_paths :
684- plugin_paths .extend ([Path (p ) for p in env_paths .split (os .pathsep ) if p ])
685- plugin_paths .extend (_find_plugins_dirs (Path (__file__ ).resolve ()))
686- plugins .append (
687- PluginConfig (
688- plugin_name = "controller_synthetic_hands" ,
689- plugin_root_id = "synthetic_hands" ,
690- search_paths = plugin_paths ,
691- )
692- )
751+ return TeleopSessionConfig (
752+ app_name = "TeleopRos2Publisher" ,
753+ pipeline = pipeline ,
754+ plugins = _build_plugins (
755+ self ._use_mock_operators ,
756+ include_synthetic_hands = False ,
757+ search_start = Path (__file__ ).resolve (),
758+ ),
759+ )
760+
761+ def _build_full_body_config (self ) -> TeleopSessionConfig :
762+ controllers = ControllersSource (name = "controllers" )
763+ full_body = FullBodySource (name = "full_body" )
764+ pipeline = OutputCombiner (
765+ {
766+ "controller_left" : controllers .output (ControllersSource .LEFT ),
767+ "controller_right" : controllers .output (ControllersSource .RIGHT ),
768+ "full_body" : full_body .output (FullBodySource .FULL_BODY ),
769+ }
770+ )
693771
694- self . _config = TeleopSessionConfig (
772+ return TeleopSessionConfig (
695773 app_name = "TeleopRos2Publisher" ,
696774 pipeline = pipeline ,
697- plugins = plugins ,
775+ plugins = _build_plugins (
776+ self ._use_mock_operators ,
777+ include_synthetic_hands = False ,
778+ search_start = Path (__file__ ).resolve (),
779+ ),
780+ )
781+
782+ def _build_hand_teleop_config (self ) -> TeleopSessionConfig :
783+ hands = HandsSource (name = "hands" )
784+ pedals = Generic3AxisPedalSource (
785+ name = "pedals" , collection_id = self ._pedal_collection_id
698786 )
787+ locomotion = FootPedalRootCmdRetargeter (
788+ FootPedalRootCmdRetargeterConfig (),
789+ name = "foot_pedal" ,
790+ )
791+ locomotion_connected = locomotion .connect ({"pedals" : pedals .output ("pedals" )})
792+
793+ pipeline = OutputCombiner (
794+ {
795+ "hand_left" : hands .output (HandsSource .LEFT ),
796+ "hand_right" : hands .output (HandsSource .RIGHT ),
797+ "root_command" : locomotion_connected .output ("root_command" ),
798+ }
799+ )
800+
801+ return TeleopSessionConfig (
802+ app_name = "TeleopRos2Publisher" ,
803+ pipeline = pipeline ,
804+ plugins = _build_plugins (
805+ self ._use_mock_operators ,
806+ include_synthetic_hands = True ,
807+ search_start = Path (__file__ ).resolve (),
808+ ),
809+ )
810+
811+ def _build_session_config (
812+ self ,
813+ mode : str ,
814+ left_finger_joint_names : Sequence [str ],
815+ right_finger_joint_names : Sequence [str ],
816+ ) -> TeleopSessionConfig :
817+ if mode == "controller_teleop" :
818+ return self ._build_controller_teleop_config (
819+ left_finger_joint_names ,
820+ right_finger_joint_names ,
821+ )
822+ if mode == "hand_teleop" :
823+ return self ._build_hand_teleop_config ()
824+ if mode == "controller_raw" :
825+ return self ._build_controller_raw_config ()
826+ if mode == "full_body" :
827+ return self ._build_full_body_config ()
828+ raise ValueError (f"Unsupported mode { mode !r} " )
699829
700830 def _build_wrist_tfs (
701831 self ,
@@ -749,8 +879,6 @@ def run(self) -> int:
749879 result = session .step ()
750880
751881 now = self .get_clock ().now ().to_msg ()
752- left_ctrl = result ["controller_left" ]
753- right_ctrl = result ["controller_right" ]
754882
755883 if self ._mode == "hand_teleop" :
756884 left_hand = result ["hand_left" ]
@@ -786,6 +914,8 @@ def run(self) -> int:
786914 if wrist_tfs :
787915 self ._tf_broadcaster .sendTransform (wrist_tfs )
788916 elif self ._mode == "controller_teleop" :
917+ left_ctrl = result ["controller_left" ]
918+ right_ctrl = result ["controller_right" ]
789919 # Build EE poses from controllers
790920 ee_msg = _build_ee_msg_from_controllers (
791921 left_ctrl ,
@@ -807,7 +937,7 @@ def run(self) -> int:
807937 self ._tf_broadcaster .sendTransform (wrist_tfs )
808938
809939 if self ._mode in ("hand_teleop" , "controller_teleop" ):
810- root_command = result . get ( "root_command" )
940+ root_command = result [ "root_command" ]
811941 if not root_command .is_none :
812942 cmd = np .asarray (root_command [0 ])
813943 twist_msg = TwistStamped ()
@@ -827,6 +957,8 @@ def run(self) -> int:
827957 self ._pub_root_pose .publish (pose_msg )
828958
829959 if self ._mode == "controller_teleop" :
960+ left_ctrl = result ["controller_left" ]
961+ right_ctrl = result ["controller_right" ]
830962 left_joints = result ["finger_joints_left" ]
831963 right_joints = result ["finger_joints_right" ]
832964 if not left_joints .is_none or not right_joints .is_none :
@@ -862,6 +994,8 @@ def run(self) -> int:
862994 "controller_teleop" ,
863995 "full_body" ,
864996 ):
997+ left_ctrl = result ["controller_left" ]
998+ right_ctrl = result ["controller_right" ]
865999 if not left_ctrl .is_none or not right_ctrl .is_none :
8661000 controller_payload = _build_controller_payload (
8671001 left_ctrl , right_ctrl
0 commit comments