ROS2 小作業 === [TOC] # 目標 創建一個ROS2節點,使用turtlesim套件控制海龜的移動,實現以下功能: 1. 控制海龜的移動方向和速度 -> **Topic** 2. 改變海龜行徑路徑的顏色和寬度 -> **Service** & **parameter** 3. 傳輸海龜的位置和顏色 -> **Custom msg** --- ## 建立worksapce跟package 建立workspace `mkdir -p ~/ros2_practice/src` 移動到放package的資料夾底下 `cd ~/ros2_practice/src` 建立package `ros2 pkg create --build-type ament_python turtlesim_controller` 執行turtlesim node `ros2 run turtlesim turtlesim_node` ## 撰寫程式 --- 控制海龜的移動方向和速度 Tips:接收烏龜移動控制的topic為**turtle1/cmd_vel**,只要將烏龜移動的參數發佈給他接收就可以改變移動速度 程式放在`~/ros2_practice/src/turtlesim_controller/turtlesim_controller`下 ```python= import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist class TurtlesimController(Node): def __init__(self): super().__init__('turtlesim_controller') #Create publisher for twist messages self.twistPub_ = self.create_publisher(Twist, "turtle1/cmd_vel", 10) #Create subscription for twist messages self.twistSub_ = self.create_subscription( Twist, "turtle1/move", self.twistSub_callback, 10) def twistSub_callback(self, msg): self.twistPub_.publish(msg) def main(args=None): rclpy.init(args=args) turtlesim_controller = TurtlesimController() rclpy.spin(turtlesim_controller) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) turtlesim_controller.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` package.xml加上依賴 ```xml= <exec_depend>rclpy</exec_depend> <exec_depend>geometry_msgs</exec_depend> ``` setup.py加上程式進入點 ```python= entry_points={ 'console_scripts': [ ' controller= turtlesim_controller.controller:main', ], }, ``` Topic之間的關係透過rqt查看 ![](https://hackmd.io/_uploads/By4AZp_42.png) 透過command可以發topic出去使用,[Twist的資料結構](https://docs.ros2.org/latest/api/geometry_msgs/msg/Twist.html) `ros2 topic pub --once /turtle1/move geometry_msgs/msg/Twist "{linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.0}}"` ![](https://hackmd.io/_uploads/ryC9DpvV3.gif) ## 撰寫程式 --- 改變海龜行徑路徑的顏色和寬度 Tips:透過parameter紀錄烏龜路徑的顏色跟寬度,並以一定頻率更新,其中真的修改路徑顏色跟寬度的service為**SetPen**,[SetPen的資料結構](http://docs.ros.org/en/melodic/api/turtlesim/html/srv/SetPen.html) 修改controller程式 ```python= import rclpy from rclpy.node import Node # from std_msgs.msg import String from geometry_msgs.msg import Twist from turtlesim.srv import SetPen class TurtlesimController(Node): def __init__(self): super().__init__('turtlesim_controller') #Create publisher for twist messages self.twistPub_ = self.create_publisher(Twist, "turtle1/cmd_vel", 10) #Create subscription for twist messages self.twistSub_ = self.create_subscription( Twist, "turtle1/move", self.twistSub_callback, 10) self.setPenSrv_ = self.create_client(SetPen, "turtle1/set_pen") self.declare_parameter('pen_rgb', [125, 125, 125]) self.declare_parameter('pen_width', 1) timer_period = 0.5 # seconds self.timer = self.create_timer(timer_period, self.timer_callback) def twistSub_callback(self, msg): self.twistPub_.publish(msg) def changePenColor(self): req= SetPen.Request() r, g, b = self.get_parameter('pen_rgb').value req.r = r req.g = g req.b = b req.width = self.get_parameter('pen_width').value self.future = self.setPenSrv_.call_async(req) return self.future.result() def timer_callback(self): response = self.changePenColor() def main(args=None): rclpy.init(args=args) turtlesim_controller = TurtlesimController() rclpy.spin(turtlesim_controller) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) turtlesim_controller.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` 透過指令修改路徑顏色跟寬度 `ros2 param set /turtlesim_controller pen_width 10` `ros2 param set /turtlesim_controller pen_rgb [255, 255, 255]` ![](https://hackmd.io/_uploads/ryRnOTD43.gif) ## 撰寫程式 --- 傳輸海龜的位置和顏色 Tips:建立一個新的topic資料結構包含海龜的位置跟顏色,就可以進行追蹤 新增一個新的package `cd ~/ros2_practice/src` `ros2 pkg create --build-type ament_cmake my_turtlesim_interfaces` 在~/ros2_pracitce/src/my_turtlesim_interfaces下新增msg資料夾 `mkdir msg` my_turtlesim_interfaces/msg撰寫msg資料型態,==檔案名稱要大寫,拒絕任何特殊字元==,成功的檔案名稱如TurtleInfo.msg ```msg= turtlesim/Pose position uint8[] turtle_color ``` CMakeList.txt新增依賴 ```txt= find_package(turtlesim REQUIRED) find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "msg/Turtle_info.msg" DEPENDENCIES turtlesim ) ``` package.xml新增依賴 ```xml= <depend>turtlesim</depend> <buildtool_depend>rosidl_default_generators</buildtool_depend> <exec_depend>rosidl_default_runtime</exec_depend> <member_of_group>rosidl_interface_packages</member_of_group> ``` 測試我的msg是否正常 `ros2 interface show my_turtlesim_interfaces/msg/TurtleInfo` 輸出 ```txt= turtlesim/Pose position uint8[] turtle_color ``` 修改controller ```python= import rclpy from rclpy.node import Node # from std_msgs.msg import String from geometry_msgs.msg import Twist from turtlesim.srv import SetPen from turtlesim.msg import Pose from my_turtlesim_interfaces.msg import TurtleInfo class TurtlesimController(Node): def __init__(self): super().__init__('turtlesim_controller') #Create publisher for twist messages self.twistPub_ = self.create_publisher(Twist, "turtle1/cmd_vel", 10) #Create subscription for twist messages self.twistSub_ = self.create_subscription( Twist, "turtle1/move", self.twistSub_callback, 10) self.setPenSrv_ = self.create_client(SetPen, "turtle1/set_pen") self.declare_parameter('pen_rgb', [125, 125, 125]) self.declare_parameter('pen_width', 1) timer_period = 0.5 # seconds self.timer = self.create_timer(timer_period, self.timer_callback) self.turtleInfoPub_ = self.create_publisher(TurtleInfo, "turtle1/info", 10) self.poseSub_ = self.create_subscription( Pose, "turtle1/pose", self.poseSub_callback, 10) def twistSub_callback(self, msg): print(msg) self.twistPub_.publish(msg) def changePenColor(self): req= SetPen.Request() r, g, b = self.get_parameter('pen_rgb').value req.r = r req.g = g req.b = b req.width = self.get_parameter('pen_width').value self.future = self.setPenSrv_.call_async(req) return self.future.result() def timer_callback(self): self.changePenColor() def poseSub_callback(self, msg): turtleInfoMsg = TurtleInfo() turtleInfoMsg.position = msg turtleInfoMsg.turtle_color = self.get_parameter('pen_rgb').value self.turtleInfoPub_.publish(turtleInfoMsg) def main(args=None): rclpy.init(args=args) turtlesim_controller = TurtlesimController() rclpy.spin(turtlesim_controller) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) turtlesim_controller.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` 可以透過指令獲取turtleInfo `ros2 topic echo /turtle1/info` 輸出 ```txt= --- position: x: 5.544444561004639 y: 5.544444561004639 theta: 0.0 linear_velocity: 0.0 angular_velocity: 0.0 turtle_color: - 125 - 125 - 125 --- ```