ROS--6 Writing ROS Nodes

本是古典 何须时尚 2023-06-24 04:53 46阅读 0赞

6.0 add a script

step1. Adding the scripts directory

In order to create a new node in python, you must first create the scripts directory within the simple_arm package, as it does not yet exist.

  1. $ cd ~/catkin_ws/src/simple_arm/
  2. $ mkdir scripts

step2. Creating a new script

  1. $ cd scripts
  2. $ echo '#!/bin/bash' >> hello
  3. $ echo 'echo Hello World' >> hello

Step3:run the script

After setting the appropriate execution permissions on the file, rebuilding the workspace, and sourcing the newly created environment, you will be able to run the script.

  1. $ chmod u+x hello
  2. $ cd ~/catkin_ws
  3. $ catkin_make
  4. $ source devel/setup.bash
  5. $ rosrun simple_arm hello

6.1 Add a Publisher

Step1:Creating the empty simple_mover node script

  1. $ cd ~/catkin_ws/src/simple_arm
  2. $ cd scripts
  3. $ touch simple_mover
  4. $ chmod u+x simple_mover

Step2: Edit simple_mover file

Publishers allow a node to send messages to a topic

  1. pub1 = rospy.Publisher("/topic_name", message_type, queue_size=size)
  2. #!/usr/bin/env python
  3. import math
  4. import rospy
  5. from std_msgs.msg import Float64
  6. def mover():
  7. pub_j1 = rospy.Publisher('/simple_arm/joint_1_position_controller/command',
  8. Float64, queue_size=10)
  9. pub_j2 = rospy.Publisher('/simple_arm/joint_2_position_controller/command',
  10. Float64, queue_size=10)
  11. ‘’’
  12. Initializes a client node and registers it with the master. Here arm_mover is the name of the node. init_node() must be called before any other rospy package functions are called. The argument anonymous=True makes sure that you always have a unique name for your node
  13. ’’’
  14. rospy.init_node('arm_mover')
  15. rate = rospy.Rate(10)
  16. start_time = 0
  17. while not start_time:
  18. start_time = rospy.Time.now().to_sec()
  19. while not rospy.is_shutdown():
  20. elapsed = rospy.Time.now().to_sec() - start_time
  21. pub_j1.publish(math.sin(2*math.pi*0.1*elapsed)*(math.pi/2))
  22. pub_j2.publish(math.sin(2*math.pi*0.1*elapsed)*(math.pi/2))
  23. rate.sleep()
  24. if __name__ == '__main__':
  25. try:
  26. mover()
  27. except rospy.ROSInterruptException:
  28. pass

Step3 Running simple_mover

Assuming that your workspace has recently been built, and it’s setup.bash has been sourced, you can launch simple_arm as follows:

  1. $ cd ~/catkin_ws
  2. $ roslaunch simple_arm robot_spawn.launch

Once ROS Master, Gazebo, and all of our relevant nodes are up and running, we can finally launch simple_mover. To do so, open a new terminal and type the following commands:

  1. $ cd ~/catkin_ws
  2. $ source devel/setup.bash
  3. $ rosrun simple_arm simple_mover

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1dGVyZXNh_size_16_color_FFFFFF_t_70

6.2 ROS Services

6.2.1 Defining services

  1. service = rospy.Service('service_name', serviceClassName, handler)

each service has a definition provided in an .srv file; this is a text file that provides the proper message type for both requests and responses.

The handler is the name of the function or method that handles the incoming service message. This function is called each time the service is called, and the message from the service call is passed to the handler as an argument. The handler should return an appropriate service response message.

6.2.2 Using Services

Services can be called directly from the command line, and you will see an example of this in the upcoming arm_mover classroom concepts.

On the other hand, to use a ROS service from within another node, you will define a ServiceProxy, which provides the interface for sending messages to the service:

  1. service_proxy = rospy.ServiceProxy('service_name', serviceClassName)

One way the ServiceProxy can then be used to send requests is as follows:

  1. msg = serviceClassNameRequest()
  2. #update msg attributes here to have correct data
  3. response = service_proxy(msg)

http://wiki.ros.org/rospy/Overview/Services

6.3: add a service

Namely, we still need to cover:

  • Custom message generation
  • Services
  • Parameters
  • Launch Files
  • Subscribers
  • Logging

step1. Creating a new service definition

As you learned earlier, an interaction with a service consists of two messages being passed. A request passed to the service, and a response received from the service. The definitions of the request and response message type are contained within .srv files living in the srv directory under the package’s root.

  1. $ cd ~/catkin_ws/src/simple_arm/
  2. $ mkdir srv
  3. $ cd srv
  4. $ touch GoToPosition.srv

GoToPosition” 是服务类型名字

You should now edit GoToPosition.srv, so it contains the following:

  1. float64 joint_1
  2. float64 joint_2
  3. ---
  4. duration time_elapsed

Service definitions always contain two sections, separated by a ‘—-’ line. The first section is the definition of the request message. Here, a request consists of two float64 fields, one for each of simple_arm**’s joints. The second section contains is the service response. The response contains only a single field, time_elapsed. The time_elapsed** field is of type duration, and is responsible for indicating how long it took the arm to perform the movement.

Step2: Modifying CMakeLists.txt

In order for catkin to generate the python modules or C++ libraries which allow you to utilize messages in your code you must first modify simple_arm**’s** CMakeLists.txt (~/catkin_ws/src/simple_arm/CMakeLists.txt).

First, ensure that the find_package() macro lists std_msgs and message_generation as required packages. The find_package() macro should look as follows:

  1. find_package(catkin REQUIRED COMPONENTS
  2. std_msgs
  3. message_generation
  4. )

As the names might imply, the std_msgs package contains all of the basic message types, and message_generation is required to generate message libraries for all the supported languages (cpp, lisp, python, javascript).

Note: In your CMakeLists.txt, you may also see controller_manager listed as a required package. In actuality this package is not required. It was simply added as a means to demonstrate a build failure in the previous lesson. You may remove it from the list of REQUIRED COMPONENTS if you choose.

Next, uncomment the commented-out add_service_files() macro so it looks like this:

  1. ## Generate services in the 'srv' folder
  2. add_service_files(
  3. FILES
  4. GoToPosition.srv
  5. )

This tells catkin which files to generate code for.

Lastly, make sure that the generate_messages() macro is uncommented, as follows:

  1. generate_messages(
  2. DEPENDENCIES
  3. std_msgs # Or other packages containing msgs
  4. )

Step3: Modifying package.xml

package.xml is responsible for defining many of the package’s properties, such as the name of the package, version numbers, authors, maintainers, and dependencies.

When rosdep is searching for these dependencies, it’s the package.xml file that is being parsed. Let’s add the message_generation and message_runtimedependencies.

  1. Step3: Modifying package.xml
  2. package.xml is responsible for defining many of the packages properties, such as the name of the package, version numbers, authors, maintainers, and dependencies.
  3. When rosdep is searching for these dependencies, its the package.xml file that is being parsed. Lets add the message_generation and message_runtimedependencies.
  4. <buildtool_depend>catkin</buildtool_depend>
  5. <build_depend>message_generation</build_depend>
  6. <run_depend>controller_manager</run_depend>
  7. <run_depend>effort_controllers</run_depend>
  8. <run_depend>gazebo_plugins</run_depend>
  9. <run_depend>gazebo_ros</run_depend>
  10. <run_depend>gazebo_ros_control</run_depend>
  11. <run_depend>joint_state_controller</run_depend>
  12. <run_depend>joint_state_publisher</run_depend>
  13. <run_depend>robot_state_publisher</run_depend>
  14. <run_depend>message_runtime</run_depend>
  15. <run_depend>xacro</run_depend>

You are now ready to build the package! For more information about package.xml, check out the ROS Wiki.

http://wiki.ros.org/catkin/package.xml

step4: Building the package

If you build the workspace successfully, you should now find that a python package containing a module for the new service GoToPosition has been created deep down in the devel directory.

  1. $ cd ~/catkin_ws
  2. $ catkin_make
  3. $ cd devel/lib/python2.7/dist-packages
  4. $ ls
  5. $ env | grep PYTHONPATH

Step5: Creating the empty arm_mover node script

The steps you take to create the arm_mover node are exactly the same as the steps you took to create the simple_mover script, excepting the actual name of the script itself.

  1. $ cd ~/catkin_ws
  2. $ cd src/simple_arm/scripts
  3. $ touch arm_mover
  4. $ chmod u+x arm_mover

You can now edit the empty arm_mover script with your favorite text editor.

arm_mover

  1. #!/usr/bin/env python
  2. import math
  3. import rospy
  4. from std_msgs.msg import Float64
  5. from sensor_msgs.msg import JointState
  6. from simple_arm.srv import *
  7. def at_goal(pos_j1, goal_j1, pos_j2, goal_j2):
  8. tolerance = .05
  9. result = abs(pos_j1 - goal_j1) <= abs(tolerance)
  10. result = result and abs(pos_j2 - goal_j2) <= abs(tolerance)
  11. return result
  12. def clamp_at_boundaries(requested_j1, requested_j2):
  13. clamped_j1 = requested_j1
  14. clamped_j2 = requested_j2
  15. min_j1 = rospy.get_param('~min_joint_1_angle', 0)
  16. max_j1 = rospy.get_param('~max_joint_1_angle', 2*math.pi)
  17. min_j2 = rospy.get_param('~min_joint_2_angle', 0)
  18. max_j2 = rospy.get_param('~max_joint_2_angle', 2*math.pi)
  19. if not min_j1 <= requested_j1 <= max_j1:
  20. clamped_j1 = min(max(requested_j1, min_j1), max_j1)
  21. rospy.logwarn('j1 is out of bounds, valid range (%s,%s), clamping to: %s',
  22. min_j1, max_j1, clamped_j1)
  23. if not min_j2 <= requested_j2 <= max_j2:
  24. clamped_j2 = min(max(requested_j2, min_j2), max_j2)
  25. rospy.logwarn('j2 is out of bounds, valid range (%s,%s), clamping to: %s',
  26. min_j2, max_j2, clamped_j2)
  27. return clamped_j1, clamped_j2
  28. def move_arm(pos_j1, pos_j2):
  29. time_elapsed = rospy.Time.now()
  30. j1_publisher.publish(pos_j1)
  31. j2_publisher.publish(pos_j2)
  32. while True:
  33. joint_state = rospy.wait_for_message('/simple_arm/joint_states', JointState)
  34. if at_goal(joint_state.position[0], pos_j1, joint_state.position[1], pos_j2):
  35. time_elapsed = joint_state.header.stamp - time_elapsed
  36. break
  37. return time_elapsed
  38. def handle_safe_move_request(req):
  39. rospy.loginfo('GoToPositionRequest Received - j1:%s, j2:%s',
  40. req.joint_1, req.joint_2)
  41. clamp_j1, clamp_j2 = clamp_at_boundaries(req.joint_1, req.joint_2)
  42. time_elapsed = move_arm(clamp_j1, clamp_j2)
  43. return GoToPositionResponse(time_elapsed)
  44. def mover_service():
  45. rospy.init_node('arm_mover')
  46. service = rospy.Service('~safe_move', GoToPosition, handle_safe_move_request)
  47. rospy.spin()
  48. if __name__ == '__main__':
  49. j1_publisher = rospy.Publisher('/simple_arm/joint_1_position_controller/command',
  50. Float64, queue_size=10)
  51. j2_publisher = rospy.Publisher('/simple_arm/joint_2_position_controller/command',
  52. Float64, queue_size=10)
  53. try:
  54. mover_service()
  55. except rospy.ROSInterruptException:
  56. pass

step6: Launch and Interact

First, Launching the project with the new service

To get the arm_mover node, and accompanying safe_move service to launch along with all of the other nodes, you will modify robot_spawn.launch.

Launch files, when they exist, are located within the launch directory in the root of a catkin package. simple_arm’s launch file is located in ~/catkin_ws/src/simple_arm/launch

To get the arm_mover node to launch, simply add the following:

  1. <!-- The arm mover node -->
  2. <node name="arm_mover" type="arm_mover" pkg="simple_arm">
  3. <rosparam>
  4. min_joint_1_angle: 0
  5. max_joint_1_angle: 1.57
  6. min_joint_2_angle: 0
  7. max_joint_2_angle: 1.0
  8. </rosparam>
  9. </node>

More information on the format of the launch file can be found

http://wiki.ros.org/roslaunch/XML

step7 Testing the new service

Now that you’ve modified the launch file, you are ready to test everything out.

First. To do so, launch the simple_arm, verify that the arm_mover node is running, and that the safe_moveservice is listed:

Note: You will need to make sure that you’ve exited out of your previous roslaunch session before re-launching.

  1. $ cd ~/catkin_ws
  2. $ catkin_make
  3. $ source devel/setup.bash
  4. $ roslaunch simple_arm robot_spawn.launch

Then, in a new terminal, verify that the node and service have indeed launched.

  1. $ rosnode list
  2. $ rosservice list

To view the camera image stream, you can use the command rqt_image_view (you can learn more about rqt and the associated tools here):

http://wiki.ros.org/rqt

  1. $ rqt_image_view /rgb_camera/image_raw

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1dGVyZXNh_size_16_color_FFFFFF_t_70 1

Adjusting the view

The camera is displaying a gray image. This is as to be expected, given that it is straight up, towards the gray sky of our gazebo world.

To point the camera towards the numbered blocks on the counter top, we would need to rotate both joint 1 and joint 2 by approximately pi/2 radians. Let’s give it a try:

  1. $ cd ~/catkin_ws/
  2. $ source devel/setup.bash
  3. $ rosservice call /arm_mover/safe_move "joint_1: 1.57
  4. joint_2: 1.57"

Note: rosservice call can tab-complete the request message, so that you don’t have to worry about writing it out by hand. Also, be sure to include a line break between the two joint parameters.

What was not expected is the resulting position of the arm. Looking at the roscore console, we can very clearly see what the problem was. The requested angle for joint 2 was out of the safe bounds. We requested 1.57 radians, but the maximum joint angle was set to 1.0 radians.

By setting the max_joint_2_angle on the parameter server, we should be able to bring the blocks into view the next time a service call is made. To increase joint 2’s maximum angle, you can use the command rosparam

  1. $ rosparam set /arm_mover/max_joint_2_angle 1.57

Now we should be able to move the arm such that all of the blocks are within the field of view of the camera:

  1. rosservice call /arm_mover/safe_move "joint_1: 1.57
  2. joint_2: 1.57"

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1dGVyZXNh_size_16_color_FFFFFF_t_70 2

And there you have it. All of the blocks are within the field of view!

6.4 add Subscribers

添加一个订阅者节点,根据从主题获取的数据,做相应处理,当出现机械臂指向天空时,发送一个service消息,把机械臂移动到指定位置。

也包括一个发送服务消息

A Subscriber enables your node to read messages from a topic, allowing useful data to be streamed into the node.

  1. sub1 = rospy.Subscriber("/topic_name", message_type, callback_function)

The "/topic_name" indicates which topic the Subscriber should listen to.

The message_type is the type of message being published on "/topic_name".

The callback_function is the name of the function that should be called with each incoming message. Each time a message is received, it is passed as an argument to callback_function. Typically, this function is defined in your node to perform a useful action with the incoming data. Note that unlike service handler functions, the callback_function is not required to return anything.

More in http://docs.ros.org/api/rospy/html/rospy.topics.Subscriber-class.html

创建一个look_away节点, look_away 节点订阅/rgb_camera/image_raw 主题,该主题可以从robotic arm获取摄像头数据。

Step1: Creating the empty look_away node scrip

  1. $ cd ~/catkin_ws
  2. $ cd src/simple_arm/scripts
  3. $ touch look_away
  4. $ chmod u+x look_away

Note: If look_away starts before the system has fully initialized, then look_away hangs in the call to safe_move.

解决办法:wait_for_message

  1. def __init__(self):
  2. rospy.init_node('look_away')
  3. self.last_position = None
  4. self.arm_moving = False
  5. rospy.wait_for_message('/simple_arm/joint_states', JointState)
  6. rospy.wait_for_message('/rgb_camera/image_raw', Image)
  7. self.sub1 = rospy.Subscriber('/simple_arm/joint_states',
  8. JointState, self.joint_states_callback)
  9. self.sub2 = rospy.Subscriber('/rgb_camera/image_raw',
  10. Image, self.look_away_callback)
  11. self.safe_move = rospy.ServiceProxy('/arm_mover/safe_move',
  12. GoToPosition)
  13. rospy.spin()

step2: Updating the launch file

  1. <!-- The look away node -->
  2. <node name="look_away" type="look_away" pkg="simple_arm"/>

修改默认值

  1. <!-- The arm mover node -->
  2. <node name="arm_mover" type="arm_mover" pkg="simple_arm">
  3. <rosparam>
  4. min_joint_1_angle: 0
  5. max_joint_1_angle: 1.57
  6. min_joint_2_angle: 0
  7. max_joint_2_angle: 1.57
  8. </rosparam>
  9. </node>

step3: Look_up

  1. #!/usr/bin/env python
  2. import math
  3. import rospy
  4. from sensor_msgs.msg import Image, JointState
  5. from simple_arm.srv import *
  6. class LookAway(object):
  7. def __init__(self):
  8. rospy.init_node('look_away')
  9. self.sub1 = rospy.Subscriber('/simple_arm/joint_states',
  10. JointState, self.joint_states_callback)
  11. self.sub2 = rospy.Subscriber("rgb_camera/image_raw",
  12. Image, self.look_away_callback)
  13. self.safe_move = rospy.ServiceProxy('/arm_mover/safe_move',
  14. GoToPosition)
  15. self.last_position = None
  16. self.arm_moving = False
  17. rospy.spin()
  18. def uniform_image(self, image):
  19. return all(value == image[0] for value in image)
  20. def coord_equal(self, coord_1, coord_2):
  21. if coord_1 is None or coord_2 is None:
  22. return False
  23. tolerance = .0005
  24. result = abs(coord_1[0] - coord_2[0]) <= abs(tolerance)
  25. result = result and abs(coord_1[1] - coord_2[1]) <= abs(tolerance)
  26. return result
  27. def joint_states_callback(self, data):
  28. if self.coord_equal(data.position, self.last_position):
  29. self.arm_moving = False
  30. else:
  31. self.last_position = data.position
  32. self.arm_moving = True
  33. def look_away_callback(self, data):
  34. if not self.arm_moving and self.uniform_image(data.data):
  35. try:
  36. rospy.wait_for_service('/arm_mover/safe_move')
  37. msg = GoToPositionRequest()
  38. msg.joint_1 = 1.57
  39. msg.joint_2 = 1.57
  40. response = self.safe_move(msg)
  41. rospy.logwarn("Camera detecting uniform image. \
  42. Elapsed time to look at something nicer:\n%s",
  43. response)
  44. except rospy.ServiceException, e:
  45. rospy.logwarn("Service call failed: %s", e)
  46. if __name__ == '__main__':
  47. try:
  48. LookAway()
  49. except rospy.ROSInterruptException:
  50. pass

step4: Launch and Interact

  1. $ cd ~/catkin_ws
  2. $ catkin_make
  3. $ source devel/setup.bash
  4. $ roslaunch simple_arm robot_spawn.launch

After launching, the arm should move away from the grey sky and look towards the blocks. To view the camera image stream, you can use the same command as before:

观察图片数据

  1. $ rqt_image_view /rgb_camera/image_raw

发送消息操作simple_arm

  1. rosservice call /arm_mover/safe_move "joint_1: 0
  2. joint_2: 0"

Note:

1.对safe_move发送一个service消息,移动simple_arm,移动的相关信息会发布在主题“joint_states”和“rgb_camera/image_raw”;

2.look_away节点作为订阅者节点,从这两个主题中获取位置和图像信息,当发现摄像头朝向天空时,发送一个service消息,将simple_arm移动到指定位置。

发表评论

表情:
评论列表 (有 0 条评论,46人围观)

还没有评论,来说两句吧...

相关阅读