Lab 3
Lab 3
TurtleSim Programming
Execute in Terminal #1
ros2 run turtlesim turtlesim_node
Execute in Terminal #2
ros2 run turtlesim turtle_teleop_key
Execute in Terminal #3
ros2 service list
ros2 service type /clear
ros2 interface show std_srvs/srv/Empty
ros2 service call /clear std_srvs/srv/Empty
ros2 service type /spawn
ros2 interface show turtlesim/srv/Spawn
ros2 service call /spawn turtlesim/srv/Spawn
ros2 service call /spawn turtlesim/srv/Spawn "{x: 5.0, y: 5.0, theta: 0.0, name: "my_turtle"}"
ROS2 interfaces:
https://github.com/ros2/example_interfaces
https://github.com/ros2/common_interfaces
• A custom node to control the turtle (named “turtle1”) which is already existing in the
turtlesim_node. This node can be called turtle_controller.
• A custom node to spawn turtles on the window. This node can be called
turtle_spawner.
Execute in Terminal #1
cd ~/ros2_ws/src/my_package/my_package
Execute in Terminal #2
touch turtle_controller.py
chmod + turtle_controller.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
from geometry_msgs.msg import Twist
import math
class TurtleControllerNode(Node):
def __init__(self):
super().__init__("turtle_controller")
self.target_x = 8.0
self.target_y = 4.0
self.pose_ = None
self.cmd_vel_publisher_ = self.create_publisher(Twist, "turtle1/cmd_vel", 10)
self.pose_subscriber_ = self.create_subscription(Pose, "turtle1/pose",
self.callback_turtle_pose, 10)
self.control_loop_timer_ = self.create_timer(0.01, self.control_loop)
def callback_turtle_pose(self,msg):
self.pose_ = msg
def control_loop(self):
if self.pose_ == None:
return
2
dist_x = self.target_x - self.pose_.x
dist_y = self.target_y - self.pose_.y
distance = math.sqrt(dist_x * dist_x + dist_y * dist_y)
msg = Twist()
if distance > 0.5:
msg.linear.x = distance
goal_theta = math.atan2(dist_y, dist_x)
diff = goal_theta - self.pose_.theta
diff -= 2*math.pi
elif diff < -math.pi:
diff += 2*math.pi
msg.angular.z = diff
else:
msg.linear.x = 0.0
msg.angular.z = 0.0
self.cmd_vel_publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = TurtleControllerNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
entry_points={
'console_scripts': [
3
'sample = my_package.sample:main',
'robot_publisher = my_package.robot_publisher:main',
'robot_subscriber = my_package.robot_subscriber:main',
'turtlesim_controller = my_package.turtle_controller:main'
],
},
)
Modify he package.xml as
<depend>rclpy</depend>
<depend>example_interfaces</depend>
<depend>my_robot_interface</depend>
<depend>turtlesim</depend>
Execute in Terminal #1
4
5
Execute in Terminal #2
6
colcon build --packages-select my_package --symlink-install
Execute in Terminal #3
Execute in Terminal #4
ros2 service list
ros2 service type /spawn
ros2 interface show turtlesim/srv/Spawn
Services:
Execute in Terminal #1
ros2 interface show example_interfaces/srv/AddTwoInts
Execute in Terminal #1
cd ros2_ws/src/my_package/my_package
touch add_two_ints_server.py
chmod +x add_two_ints_server.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddTwoIntsServerNode(Node):
def __init__(self):
super().__init__("add_two_ints_server")
self.server_ = self.create_service(AddTwoInts, "add_two_ints",
self.callback_add_two_ints)
self.get_logger().info("Add two ints server has been started")
def main(args=None):
rclpy.init(args=args)
7
node = AddTwoIntsServerNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Execute in Terminal #1
colcon
colcon build –packages-scdelect my_package
Execute in Terminal #2
Execute in Terminal #3
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 4}"
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
def main(args=None):
rclpy.init(args=args)
node = Node("add_two_ints_no_oop") # MODIFY NAME
8
client = node.create_client(AddTwoInts, "add_two_ints")
while not client.wait_for_service(1.0):
node.get_logger().warn("Waiting for Server Add Two Ints")
request = AddTwoInts.Request()
request.a = 3
request.b = 4
future = client.call_async(request)
rclpy.spin_until_future_complete(node, future)
try:
response = future.result()
node.get_logger().info(str(request.a)+ " + " + str(request.b) + " = " +
str(response.sum))
except Exception as e:
node.get_logger().error("Service call failed %r" % (e,))
rclpy.shutdown()
if __name__ == "__main__":
main()
],
Execute in Terminal #1
Execute in Terminal #2
Execute in Terminal #3
• Call the /spawn service to create a new turtle (choose random coordinates between
0.0 and 11.0 for both x and y), and call the /kill service to remove a turtle from the
screen. Both those services are already advertised by the turtlesim_node.
• Handle a service server to “catch” a turtle, which means to call the /kill service and
remove the turtle.
• Run a control loop (for example using a timer with a high rate) to reach a given tar-
get point. The first turtle on the screen “turtle1” will be the “master” turtle to control.
To control the turtle you can subscribe to /turtle1/pose and publish to /tur-
tle1/cmd_vel.
• The control loop will use a simplified Proportional controller.
• Subscribe to get turtle’s coordinates. From that info, select a turtle to target (to
catch).
• When a turtle has been caught by the master turtle, call the service /kill_turtle adver-
tised by the turtle_spawner node.
10
Optional Lab:
This lab is completely optional. You can practice them if you want to be a robotic software
engineer.
Recap:
Execute in Terminal #1
ros2 interface show example_interfaces/srv/AddTwoInts
Execute in Terminal #1
cd ros2_ws/src/my_package/my_package
touch add_two_ints_server.py
chmod +x add_two_ints_server.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddTwoIntsServerNode(Node):
def __init__(self):
super().__init__("add_two_ints_server")
self.server_ = self.create_service(AddTwoInts, "add_two_ints",
self.callback_add_two_ints)
self.get_logger().info("Add two ints server has been started")
def main(args=None):
rclpy.init(args=args)
node = AddTwoIntsServerNode()
rclpy.spin(node)
12
rclpy.shutdown()
if __name__ == "__main__":
main()
Execute in Terminal #1
Execute in Terminal #2
Execute in Terminal #3
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 4}"
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
from functools import partial
class AddTwoIntClientNode(Node):
def __init__(self):
super().__init__("add_two_ints_client")
self.call_add_two_int_server(6, 7)
except Exception as e:
self.get_logger().error("Service call failed %r" % (e,))
def main(args=None):
rclpy.init(args=args)
node = AddTwoIntClientNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Execute in Terminal #1
colcon build --packages-select my_package –symlink-install
Execute in Terminal #2
ros2 run my_package add_two_int_server
Execute in Terminal #3
ros2 run my_package add_two_ints_client
Execute in Terminal #4
ros2 node list
Execute in Terminal #5
ros2 service list
14
ros2 service type /add_two_ints
ros2 interface show example_interfaces/srv/AddTwoInts
ros2 service call /add_two_int example_interfaces/srv/AddTwoInts
ros2 service call /add_two_int example_interfaces/srv/AddTwoInts "{a: 3, b: 4}"
rqt
plugins→services→service caller
service - /add_two_ints
Enter the values under Expression for a and b
Click call
Response is viewed in the second window
• When the server is called, you check the boolean data from the request. If true, you
set the counter variable to 0.
We will then call the service directly from the command line. You can also decide - for
more practice - to create your own custom node to call this “/reset_counter” service.
15
Add a functionality to reset the counter to zero:
• When the server is called, you check the boolean data from the request. If true, you
set the counter variable to 0.
We will then call the service directly from the command line. You can also decide - for
more practice - to create your own custom node to call this “/reset_counter” service.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.msg import Int64
from example_interfaces.srv import SetBool
class NumberCounterNode(Node):
def __init__(self):
super().__init__("number_counter")
self.counter_ = 0
self.number_count_publisher_ = self.create_publisher(Int64, "number_count", 10)
self.number_subscriber_ = self.create_subscription(Int64, "number",
self.callback_number, 10)
self.reset_counter_service_ = self.create_service(SetBool, "reset_counter",
self.callback_reset_counter)
16
self.get_logger().info("Node started")
def main(args=None):
rclpy.init(args=args)
node = NumberCounterNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Execute in Terminal #1
ros2 interface show example_interfaces/srv/SetBool
Execute in Terminal #1
cd ros2_ws/
colcon build --packages-select my_package
Execute in Terminal #1
ros2 run my_package number_publisher
Execute in Terminal #2
ros2 run my_package number_counter
Execute in Terminal #3
ros2 topic list
ros2 topic echo /number_count
Execute in Terminal #4
ros2 service call /reset_counter example_interfaces/srv/SetBool "{data: False}"
ros2 service call /reset_counter example_interfaces/srv/SetBool "{data: True}"
17
Custom Services
cd ros2_ws/src/my_robot_interface
mkdir srv
cd srv
touch SetDate.srv
SetDate.srv
string robot_name
int64 date
---
bool success
Change CmakeLists.txt as
rosidl_generate_interfaces(my_robot_interface
"msg/ManufactureDate.msg"
"srv/SetDate.srv"
)
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from my_robot_interface.msg import ManufactureDate
from my_robot_interface.srv import SetDate
class RobotDatePublisher(Node):
def __init__(self):
super().__init__("robot_date_publisher")
self.robot_name_="ROBOT"
self.publisher_ = self.create_publisher(ManufactureDate,
"robot_manufacturing_date", 10)
self.timer_ = self.create_timer(0.5, self.publish_news)
self.set_date_ = self.create_service(SetDate, "set_date", self.callback_set_date)
self.get_logger().info("Node Started")
def publish_news(self):
msg = ManufactureDate()
msg.date = 12
msg.month = "March"
msg.year = 2022
self.publisher_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = RobotDatePublisher()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Execute in Terminal #1
colcon build –packages-select my_package
Execute in Terminal #1
ros2 run my_package robot_publisher
Execute in Terminal #2
ros2 service list
ros2 service call /set_date my_robot_interface/srv/SetDate "{name: "ROBOT", date: 12}"
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from example_interfaces.msg import String, Int32
from my_robot_interface.msg import ManufactureDate
from my_robot_interface.srv import SetDate
from functools import partial
class RobotDateSubscriber(Node):
def __init__(self):
super().__init__("robot_date_subscriber")
self.subscriber_ = self.create_subscription(ManufactureDate,
"robot_manufacturing_date", self.callback_robot_news, 10)
self.get_logger().info("robot_subscriber Node Started")
def main(args=None):
rclpy.init(args=args)
node = RobotDateSubscriber()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Execute in Terminal #1
ros2 run colcon build --packages-select my_package
Execute in Terminal #2
ros2 run my_package robot_publisher
Execute in Terminal #3
ros2 run my_package robot_subscriber
Execute in Terminal #1
cd ros2_ws/my_robot_interface/srv
touch MoveLocation.srv
Edit MoveLocation.srv
float32 loc_x
float32 loc_y
---
float32 distance
Change CmakeLists.txt as
rosidl_generate_interfaces(my_robot_interface
20
"msg/ManufactureDate.msg"
"srv/SetDate.srv"
"srv/MoveLocation.srv"
)
Execute in Terminal #1
cd ~/ros2_ws
colcon build –packages-select my_robot_interface
Send the service request to find the distance between current location and new location.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
from geometry_msgs.msg import Twist
from my_robot_interface.srv import MoveLocation
import math
class TurtleControllerNode(Node):
def __init__(self):
super().__init__("turtle_controller")
self.target_x = 9.0
self.target_y = 9.0
self.pose_ = None
self.cmd_vel_publisher_ = self.create_publisher(Twist, "turtle1/cmd_vel", 10)
self.pose_subscriber_ = self.create_subscription(Pose, "turtle1/pose",
self.callback_turtle_pose, 10)
self.control_loop_timer_ = self.create_timer(0.01, self.control_loop)
self.servce_ = self.create_service(MoveLocation, "move_location",
self.callback_get_distance)
def callback_turtle_pose(self,msg):
self.pose_ = msg
def control_loop(self):
if self.pose_ == None:
return
dist_x = self.target_x - self.pose_.x
dist_y = self.target_y - self.pose_.y
distance = math.sqrt(dist_x * dist_x + dist_y * dist_y)
msg = Twist()
if distance > 0.5:
msg.linear.x = distance
goal_theta = math.atan2(dist_y, dist_x)
diff = goal_theta - self.pose_.theta
21
if diff > math.pi:
diff -= 2*math.pi
elif diff < -math.pi:
diff += 2*math.pi
msg.angular.z = diff
else:
msg.linear.x = 0.0
msg.angular.z = 0.0
self.cmd_vel_publisher_.publish(msg)
response.distance = math.sqrt(x * x + y * y)
return response
def main(args=None):
rclpy.init(args=args)
node = TurtleControllerNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
Execute in Terminal #1
colcon build –packages-select my_package
Execute in Terminal #2
ros2 run turtlesim turtlesim_node
Execute in Terminal #3
ros2 run my_package turtlesim_controller
Execute in Terminal #1
ros2 service call /move_location my_robot_interface/srv/MoveLocation "{loc_x: 5.0, loc_y:
5.0}"
Execute in Terminal #1
cd ~/ros2_ws/src/my_robot_interface
mkdir action
touch Navigate2D.action
#Goal
int32 secs
---
#Result
string status
---
#Feedback
string feedback
package.xml
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>action_msgs</depend>
CMakeLists.txt
rosidl_generate_interfaces(my_robot_interface
"msg/ManufactureDate.msg"
"srv/SetDate.srv"
"srv/MoveLocation.srv"
"action/Navigate2D.action"
)
Execute in Terminal #1
colcon build –packages-select my_robot_interface
Execute in Terminal #1
cd ~/ros2_ws/src/my_package/my_package
touch action_client.py
chmod +x action_client.py
23
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
from rclpy.executors import MultiThreadedExecutor
from my_robot_interface.action import Navigate2D
class MyActionClient(Node):
def __init__(self):
super().__init__('action_client')
self._action_client = ActionClient(self, Navigate2D, "navigate")
def main(args=None):
rclpy.init(args=args)
action_client = MyActionClient()
future = action_client.send_goal(5)
executor = MultiThreadedExecutor()
rclpy.spin(action_client, executor=executor)
if __name__ == '__main__':
main()
Execute in Terminal #1
cd ~/ros2_ws/src/my_package/my_package
touch action_server.py
chmod +x action_server.py
24
Edit the file action_server.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
from geometry_msgs.msg import Twist
from rclpy.action import ActionServer
import time
from my_robot_interface.action import Navigate2D
class NavigateAction(Node):
def __init__(self):
super().__init__("action_server")
self.action_server_ = ActionServer(
self, Navigate2D, "navigate", self.navigate_callback)
self.cmd = Twist()
self.publisher_ = self.create_publisher(Twist, "turtle1/cmd_vel", 10)
def main(args=None):
rclpy.init(args=args)
node = NavigateAction()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
25
Edit CmakeLists.txt
entry_points={
'console_scripts': [
'sample = my_package.sample:main',
'robot_publisher = my_package.robot_publisher:main',
'robot_subscriber = my_package.robot_subscriber:main',
'add_two_int_server = my_package.add_two_int_server:main',
'add_two_ints_client = my_package.add_two_ints_client:main',
'turtlesim_controller = my_package.turtle_controller:main',
'action_client = my_package.action_client:main',
'action_server = my_package.action_server:main'
],
Execute in Terminal #1
ros2 run turtlesim turtlesim_node
Execute in Terminal #2
ros2 run my_package action_client
Execute in Terminal #3
ros2 run my_package action_server
26