# Introduction to HelloNode HelloNode is a convenience class for creating a ROS 2 node for Stretch. The most common way to use this class is to extend it. In your extending class, the main funcion would call `HelloNode`'s main function. This would look like: ```python import hello_helpers.hello_misc as hm class MyNode(hm.HelloNode): def __init__(self): hm.HelloNode.__init__(self) def main(self): hm.HelloNode.main(self, 'my_node', 'my_node', wait_for_first_pointcloud=False) # my_node's main logic goes here node = MyNode() node.main() ``` There is also a one-liner class method for instantiating a `HelloNode` for easy prototyping. One example where this is handy in sending pose commands from iPython: ```python # roslaunch the stretch launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.move_to_pose({'joint_lift': 0.4}) ``` #### Attributes ##### `dryrun` This attribute allows you to control whether the robot actually moves when calling `move_to_pose()`, `home_the_robot()`, `stow_the_robot()`, or other motion methods in this class. When `dryrun` is set to True, these motion methods return immediately. This attribute is helpful when you want to run just the perception/planning part of your node without actually moving the robot. For example, you could replace the following verbose snippet: ```python # launch the stretch driver launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') actually_move = False [...] if actually_move: temp.move_to_pose({'translate_mobile_base': 1.0}) ``` to be more consise: ```python # launch the stretch driver launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') [...] temp.dryrun = True temp.move_to_pose({'translate_mobile_base': 1.0}) ``` #### Methods ##### `move_to_pose(pose, blocking=False, custom_contact_thresholds=False, duration=2.0)` This method takes in a dictionary that describes a desired pose for the robot and communicates with [stretch_driver](../stretch_core/README.md#stretchdrivernodesstretchdriver) to execute it. The basic format of this dictionary is string/number key/value pairs, where the keys are joint names and the values are desired position goals. For example, `{'joint_lift': 0.5}` would put the lift at 0.5m in its joint range. A full list of command-able joints is published to the `/stretch/joint_states` topic. Used within a node extending `HelloNode`, calling this method would look like: ```python self.move_to_pose({'joint_lift': 0.5}) ``` Internally, this dictionary is converted into a [JointTrajectory](https://docs.ros2.org/latest/api/trajectory_msgs/msg/JointTrajectory.html) message that is sent to a [FollowJointTrajectory action](http://docs.ros.org/en/noetic/api/control_msgs/html/action/FollowJointTrajectory.html) server in stretch_driver. This method waits by default for the server to report that the goal has completed executing. However, you can return before the goal has completed by setting the `blocking` argument to False. This can be useful for preempting goals. When the robot is in `position` mode, if you set `custom_contact_thresholds` to True, this method expects a different format dictionary: string/tuple key/value pairs, where the keys are still joint names, but the values are `(position_goal, effort_threshold)`. The addition of a effort threshold enables you to detect when a joint has made contact with something in the environment, which is useful for manipulation or safe movements. For example, `{'joint_arm': (0.5, 20)}` commands the telescoping arm fully out (the arm is nearly fully extended at 0.5 meters) but with a low enough effort threshold (20% of the arm motor's max effort) that the motor will stop when the end of arm has made contact with something. Again, in a node, this would look like: ```python self.move_to_pose({'joint_arm': (0.5, 40)}, custom_contact_thresholds=True) ``` When the robot is in `trajectory` mode, if you set argument `duration` as `ts`, this method will ensure that the target joint positions are achieved over `ts` seconds. For example, the below would put the lift at 0.5m from its current position in `5.0` seconds: ```python self.move_to_pose({'joint_lift': 0.5}, duration=5.0) ``` ##### `home_the_robot()` This is a convenience method to interact with the driver's [`/home_the_robot` service](../stretch_core/README.md#home_the_robot-std_srvstrigger). ##### `stow_the_robot()` This is a convenience method to interact with the driver's [`/stow_the_robot` service](../stretch_core/README.md#stow_the_robot-std_srvstrigger). ##### `stop_the_robot()` This is a convenience method to interact with the driver's [`/stop_the_robot` service](../stretch_core/README.md#stop_the_robot-std_srvstrigger). ##### `get_tf(from_frame, to_frame)` Use this method to get the transform ([geometry_msgs/TransformStamped](https://docs.ros2.org/latest/api/geometry_msgs/msg/TransformStamped.html)) between two frames. This method is blocking. For example, this method can do forward kinematics from the base_link to the link between the gripper fingers, link_grasp_center, using: ```python # launch the stretch driver launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') t = temp.get_tf('base_link', 'link_grasp_center') print(t.transform.translation) ``` ##### `get_robot_floor_pose_xya(floor_frame='odom')` Returns the current estimated x, y position and angle of the robot on the floor. This is typically called with respect to the odom frame or the map frame. x and y are in meters and the angle is in radians. !!! note To get the robot pose with respect to the odom frame we need to launch stretch_driver along with the broadcast_odom_tf parameter set to True. To do this execute the command: `ros2 launch stretch_core stretch_driver.launch.py broadcast_odom_tf:=True` ```python # launch the stretch driver launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') t = temp.get_robot_floor_pose_xya(floor_frame='odom') print(t) ``` ##### `main(node_name, node_topic_namespace, wait_for_first_pointcloud=True)` When extending the `HelloNode` class, call this method at the very beginning of your `main()` method. This method handles setting up a few ROS components, including registering the node with the ROS server, creating a TF listener, creating a [FollowJointTrajectory](http://docs.ros.org/en/noetic/api/control_msgs/html/action/FollowJointTrajectory.html) client for the [`move_to_pose()`](#movetoposepose-returnbeforedonefalse-customcontactthresholdsfalse-customfullgoalfalse) method, subscribing to depth camera point cloud topic, and connecting to the quick-stop service. Since it takes up to 30 seconds for the head camera to start streaming data, the `wait_for_first_pointcloud` argument will get the node to wait until it has seen camera data, which is helpful if your node is processing camera data. ##### `quick_create(name, wait_for_first_pointcloud=False)` A class level method for quick testing. This allows you to avoid having to extend `HelloNode` to use it. ```python # launch the stretch driver launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.move_to_pose({'joint_lift': 0.4}) ``` #### Subscribed Topics ##### /camera/depth/color/points ([sensor_msgs/PointCloud2](https://docs.ros2.org/latest/api/sensor_msgs/msg/PointCloud2.html)) Provides a point cloud as currently seen by the Realsense depth camera in Stretch's head. Accessible from the `self.point_cloud` attribute. ```python # launch the stretch driver launch file beforehand import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp', wait_for_first_pointcloud=True) print(temp.point_cloud) ``` ##### /stretch/joint_states ([sensor_msgs/JointState](https://docs.ros2.org/latest/api/sensor_msgs/msg/JointState.html)) Provides the current state of robot joints that includes joint names, positions, velocities, efforts. Accessible from the `self.joint_state` attribute. ```python print(temp.joint_state) ``` ##### /mode ([std_msgs/String](https://docs.ros2.org/latest/api/std_msgs/msg/String.html)) Provides the mode the stretch driver is currently in. Possible values include `position`, `trajectory`, `navigation`, `homing`, `stowing`. ```python print(temp.mode) ``` ##### /tool ([std_msgs/String](https://docs.ros2.org/latest/api/std_msgs/msg/String.html)) Provides the end of arm tool attached to the robot. ```python print(temp.tool) ``` #### Subscribed Services ##### /stop_the_robot ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html)) Provides a service to quickly stop any motion currently executing on the robot. ```python # launch the stretch driver launch file beforehand from std_srvs.srv import Trigger import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.stop_the_robot_service.call_async(Trigger.Request()) ``` ##### /stow_the_robot ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html)) Provides a service to stow the robot arm. ```python # launch the stretch driver launch file beforehand from std_srvs.srv import Trigger import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.stow_the_robot_service.call_async(Trigger.Request()) ``` ##### /home_the_robot ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html)) Provides a service to home the robot joints. ```python # launch the stretch driver launch file beforehand from std_srvs.srv import Trigger import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.home_the_robot_service.call_async(Trigger.Request()) ``` ##### /switch_to_trajectory_mode ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html)) Provides a service to quickly stop any motion currently executing on the robot. ```python # launch the stretch driver launch file beforehand from std_srvs.srv import Trigger import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.switch_to_trajectory_mode_service.call_async(Trigger.Request()) ``` ##### /switch_to_position_mode ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html)) Provides a service to quickly stop any motion currently executing on the robot. ```python # launch the stretch driver launch file beforehand from std_srvs.srv import Trigger import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.switch_to_position_mode_service.call_async(Trigger.Request()) ``` ##### /switch_to_navigation_mode ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html)) Provides a service to quickly stop any motion currently executing on the robot. ```python # launch the stretch driver launch file beforehand from std_srvs.srv import Trigger import hello_helpers.hello_misc as hm temp = hm.HelloNode.quick_create('temp') temp.switch_to_navigation_mode_service.call_async(Trigger.Request()) ```