# ROS 2 新手筆記(Foxy版本) **注意:這個東西是"筆記",會依據自我的學習進度持續更新!!** **下方關於colcon的相關安裝資訊請參考** https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Colcon-Tutorial.html#install-colcon ## Ubuntu 20.04 安裝方式 * 安裝colcon(Coding用) ``` sudo apt install python3-colcon-common-extensions ``` ### 照做基本可以完成大部分功能 https://docs.ros.org/en/foxy/Installation/Ubuntu-Install-Debians.html ## Windows 灌 ROS 2 * 安裝colcon(Coding用) ``` pip install -U colcon-common-extensions ``` * 照官方安裝步驟走 注意:**最好準備大概10GB給ROS2使用**!(Visual Studio的編譯環境很佔空間!) (https://docs.ros.org/en/foxy/Installation/Windows-Install-Binary.html) * 官方教學說的可能是取消勾選這個? `Make sure that no C++ CMake tools are installed by unselecting them in the list of components to be installed.` (待確認,後面爆炸了就得裝回去了,不過筆記是使用python開發為主) ![](https://i.imgur.com/LSNyzsr.png) * 將PyQt5降板至5.12 不然就不能玩turtlesim(新手入門用的烏龜範例)啦: 參考:https://github.com/ros/ros_tutorials/issues/126#issuecomment-1124080872 ``` pip install PyQt5==5.12.3 ``` * 設定PyQt5函式庫的路徑在環境變數下 ![](https://i.imgur.com/xquh96v.png) ## 教學 中文的安裝方式 https://ithelp.ithome.com.tw/users/20141476/articles 官方新手指南(很有幫助!) https://docs.ros.org/en/foxy/Tutorials/Beginner-CLI-Tools.html Coding 新手指南 https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries.html ## 開始之前: ROS 2的環境是一層一層的**overlay**(不知道怎麼翻譯比較好XD)疊起來的,從底層的ROS 2環境(每次開新的終端機都需要source的那個腳本,之後ros2跟相關的指令才可以使用),到之後Coding完需要再source自己寫好的工作區下的腳本(ros run才會有自己的package跟node可以呼叫) * Windows(ROS裝在`C:\ros2-windows`): ``` C:\ros2-windows\local_setup.ps1 ``` 依照你使用的命令窗類型:.ps1 = Powershell、.bat = Command Prompt(CMD) * Linux建議在`~/.bashrc`(使用Bash)或是`~/.zshrc`(使用Zsh),在底下增加以下指令在啟動終端機時 直接先Source好底層的指令環境 ```bash # 以下指令如果是zsh的話請使用.zsh副檔名取代原本的.bash(.sh維持原本即可) echo "Source ROS 2 scripts..." source /opt/ros/foxy/setup.bash source /usr/share/colcon_cd/function/colcon_cd.sh source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash ``` ## ros 2 控制烏龜範例 用方向鍵控制第一隻烏龜 ![](https://i.imgur.com/mjsmBdH.png) 使用rqt生成第二隻烏龜(Service呼叫) ![](https://i.imgur.com/V7r1p6Q.png) 可以在Service列表找到跟範例程式(turtlesim)相關的參數 ![](https://i.imgur.com/raFSVeX.png) 透過Call新的參數改變烏龜畫出來的線的屬性(Service呼叫) ![](https://i.imgur.com/48QgXeD.png) remap功能控制turtle2(修改node內的參數) ![](https://i.imgur.com/aheYZuB.png) ## Node ### 列出所有在線上的節點 ``` ros2 node list ``` ### 重命名節點 ``` ros2 run turtlesim turtle_teleop_key --ros-args --remap __node:=joystick --ros-args >> 代表要修改ros執行的引數 --remap >> 重新映射 __node:= >> 將節點名字改成等號後面的東西 ``` 烏龜的控制器變成了joystick,不是原本的名字 ![](https://i.imgur.com/pGsXrqO.png) ### 檢查節點的內容 ``` ros2 node info <節點名> ``` ``` rostest@rpi4-ros-test:~$ ros2 node info /turtlesim /turtlesim Subscribers: /parameter_events: rcl_interfaces/msg/ParameterEvent /turtle1/cmd_vel: geometry_msgs/msg/Twist Publishers: /parameter_events: rcl_interfaces/msg/ParameterEvent /rosout: rcl_interfaces/msg/Log /turtle1/color_sensor: turtlesim/msg/Color /turtle1/pose: turtlesim/msg/Pose Service Servers: /clear: std_srvs/srv/Empty /kill: turtlesim/srv/Kill /reset: std_srvs/srv/Empty /spawn: turtlesim/srv/Spawn /turtle1/set_pen: turtlesim/srv/SetPen /turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute /turtle1/teleport_relative: turtlesim/srv/TeleportRelative /turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters /turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes /turtlesim/get_parameters: rcl_interfaces/srv/GetParameters /turtlesim/list_parameters: rcl_interfaces/srv/ListParameters /turtlesim/set_parameters: rcl_interfaces/srv/SetParameters /turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically Service Clients: Action Servers: /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute Action Clients: ``` ``` rostest@rpi4-ros-test:~$ ros2 node info /joystick /joystick Subscribers: /parameter_events: rcl_interfaces/msg/ParameterEvent Publishers: /parameter_events: rcl_interfaces/msg/ParameterEvent /rosout: rcl_interfaces/msg/Log /turtle1/cmd_vel: geometry_msgs/msg/Twist Service Servers: /joystick/describe_parameters: rcl_interfaces/srv/DescribeParameters /joystick/get_parameter_types: rcl_interfaces/srv/GetParameterTypes /joystick/get_parameters: rcl_interfaces/srv/GetParameters /joystick/list_parameters: rcl_interfaces/srv/ListParameters /joystick/set_parameters: rcl_interfaces/srv/SetParameters /joystick/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically Service Clients: Action Servers: Action Clients: /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute ``` ### rqt_graph 檢查節點與topic ![](https://i.imgur.com/AW3VbTf.png) 或是使用rqt程式 **Plugins > Introspection > Node Graph** :::warning Windows 的 node graph 可能顯示方面有些Bug,會出現一個空白的圈圈於畫面上,不過不影響程式的功能! ![](https://hackmd.io/_uploads/ryGrx8u_3.png) ::: ### 檢查之間的主從關係(滑鼠放在上面會有顏色強化) ![](https://i.imgur.com/L6oUuvL.png) `cmd_vel`就是按鍵值的topic,由控制用的node透過這個topic送往**turtlesim** ## Topic ### ros2 topic list >> 列出所有現在可用的topics ``` rostest@rpi4-ros-test:~$ ros2 topic list /parameter_events /rosout /turtle1/cmd_vel /turtle1/color_sensor /turtle1/pose rostest@rpi4-ros-test:~$ ``` ### rqt顯示所有的topic與關係 ![](https://i.imgur.com/oHUQR7L.png) (底下的hide也可以把一些隱藏的topic顯示出來) ### echo聽取某個topic發送的訊息 ![](https://i.imgur.com/lhPmPXl.png) 在圖上產生一個debug節點去聽取某個topic的訊息 ![](https://i.imgur.com/WapgbdU.png) 文字檢查節點狀態(多少Publisher and Subscription) ``` rostest@rpi4-ros-test:~$ ros2 topic info /turtle1/cmd_vel Type: geometry_msgs/msg/Twist Publisher count: 1 Subscription count: 1 ``` 由上面的Type可以知道接收的類型為何,使用指令檢查這個型別 ``` rostest@rpi4-ros-test:~$ ros2 interface show geometry_msgs/msg/Twist # This expresses velocity in free space broken into its linear and angular parts. Vector3 linear Vector3 angular ``` 跟echo聽到的比對一下,確實是兩個向量 ``` rostest@rpi4-ros-test:~$ ros2 topic echo /turtle1/cmd_vel linear: x: 2.0 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: 0.0 --- linear: x: 0.0 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: -2.0 --- ``` ### 自己對topic下測試訊息 ``` ros2 topic pub <topic_name> <msg_type> '<args>' ``` *`args`要為yaml格式* ![](https://i.imgur.com/UkAmuEo.png) once改為rate的話,代表固定頻率下指令 ![](https://i.imgur.com/pjcfPlc.png) ### 檢查topic的資訊流動頻率 自己使用rate方式發送的訊息,在這邊應該可以偵測到差不多的頻率 ``` rostest@rpi4-ros-test:~$ ros2 topic hz /turtle1/pose average rate: 62.520 min: 0.015s max: 0.017s std dev: 0.00050s window: 64 average rate: 62.489 min: 0.015s max: 0.017s std dev: 0.00052s window: 127 average rate: 62.494 min: 0.015s max: 0.017s std dev: 0.00050s window: 190 average rate: 62.497 min: 0.015s max: 0.017s std dev: 0.00050s window: 253 average rate: 62.496 min: 0.015s max: 0.017s std dev: 0.00051s window: 316 ``` ## Service Service為伺服端(Server)與客戶端(Client),客戶端發出Request,伺服端依據Request做事並發送對應的Response *以下是打開一對烏龜後的操作* ### 檢查現有的服務 ``` rostest@rpi4-ros-test:~$ ros2 service list /clear /kill /reset /spawn /teleop_turtle/describe_parameters /teleop_turtle/get_parameter_types /teleop_turtle/get_parameters /teleop_turtle/list_parameters /teleop_turtle/set_parameters /teleop_turtle/set_parameters_atomically /turtle1/set_pen /turtle1/teleport_absolute /turtle1/teleport_relative /turtlesim/describe_parameters /turtlesim/get_parameter_types /turtlesim/get_parameters /turtlesim/list_parameters /turtlesim/set_parameters /turtlesim/set_parameters_atomically ``` ### 檢查服務的(回傳與發送)類型 ``` rostest@rpi4-ros-test:~$ ros2 service type /turtle1/set_pen turtlesim/srv/SetPen # Empty代表回傳跟發送皆為void的概念 rostest@rpi4-ros-test:~$ ros2 service type /clear std_srvs/srv/Empty # 一次滿足(沒 rostest@rpi4-ros-test:~$ ros2 service list -t /clear [std_srvs/srv/Empty] /kill [turtlesim/srv/Kill] /reset [std_srvs/srv/Empty] /spawn [turtlesim/srv/Spawn] /teleop_turtle/describe_parameters [rcl_interfaces/srv/DescribeParameters] /teleop_turtle/get_parameter_types [rcl_interfaces/srv/GetParameterTypes] /teleop_turtle/get_parameters [rcl_interfaces/srv/GetParameters] /teleop_turtle/list_parameters [rcl_interfaces/srv/ListParameters] /teleop_turtle/set_parameters [rcl_interfaces/srv/SetParameters] /teleop_turtle/set_parameters_atomically [rcl_interfaces/srv/SetParametersAtomically] /turtle1/set_pen [turtlesim/srv/SetPen] /turtle1/teleport_absolute [turtlesim/srv/TeleportAbsolute] /turtle1/teleport_relative [turtlesim/srv/TeleportRelative] /turtlesim/describe_parameters [rcl_interfaces/srv/DescribeParameters] /turtlesim/get_parameter_types [rcl_interfaces/srv/GetParameterTypes] /turtlesim/get_parameters [rcl_interfaces/srv/GetParameters] /turtlesim/list_parameters [rcl_interfaces/srv/ListParameters] /turtlesim/set_parameters [rcl_interfaces/srv/SetParameters] /turtlesim/set_parameters_atomically [rcl_interfaces/srv/SetParametersAtomically] ``` ### 用(回傳與發送)類型來找服務 ``` rostest@rpi4-ros-test:~$ ros2 service find std_srvs/srv/Empty /clear /reset ``` ### 顯示服務支援的參數 ``` # 沒有任何參數只會顯示最下方的--- rostest@rpi4-ros-test:~$ ros2 interface show turtlesim/srv/SetPen uint8 r uint8 g uint8 b uint8 width uint8 off --- ``` ### 用命令列發送一次request #### 格式 ``` ros2 service call <service_name> <service_type> <arguments> ``` ``` rostest@rpi4-ros-test:~$ ros2 service call /turtle1/set_pen turtlesim/srv/SetPen "{r: 255, g: 128, b: 64, width: 5}" requester: making request: turtlesim.srv.SetPen_Request(r=255, g=128, b=64, width=5, off=0) response: turtlesim.srv.SetPen_Response() rostest@rpi4-ros-test:~$ ``` ![](https://i.imgur.com/r0oo8Jt.png) ## Param ### 列出所有Node的Param ``` rostest@rpi4-ros-test:~$ ros2 param list /teleop_turtle: scale_angular scale_linear use_sim_time /turtlesim: background_b background_g background_r use_sim_time ``` ### 取得某個參數值 ``` rostest@rpi4-ros-test:~$ ros2 param get /turtlesim background_g Integer value is: 86 ``` ### 改變參數值 ``` rostest@rpi4-ros-test:~$ ros2 param set /turtlesim background_b 25 Set parameter successful ``` ![](https://i.imgur.com/VFmflpK.png) ### Param dump & load ``` rostest@rpi4-ros-test:~$ ros2 param dump /turtlesim Saving to: ./turtlesim.yaml ``` 在你執行ros的路徑下會看到這樣的yaml檔案 ``` /turtlesim: ros__parameters: background_b: 25 background_g: 86 background_r: 69 use_sim_time: false ``` 改好讀回去 ``` rostest@rpi4-ros-test:~$ ros2 param load /turtlesim ./turtlesim.yaml Set parameter background_b successful Set parameter background_g successful Set parameter background_r successful Set parameter use_sim_time successful ``` ![](https://i.imgur.com/IgnijO8.png) ### 以你要的參數啟動節點 ``` ros2 run <package_name> <executable_name> --ros-args --params-file <file_name> ``` ![](https://i.imgur.com/2siHAUN.png) ## Action Action的結構是給長時間的任務設計的,例如烏龜範例的控制器上的固定旋轉角度就是一種Action ![](https://i.imgur.com/HQZJl4v.png) ``` [INFO] [1675738069.333732826] [turtlesim]: Rotation goal completed successfully 或是 [INFO] [1675738172.635624264] [turtlesim]: Rotation goal canceled 或是 [WARN] [1675738223.363966525] [turtlesim]: Rotation goal received before a previous goal finished. Aborting previous goal ``` ### 透過node_info找Action ``` rostest@rpi4-ros-test:~$ ros2 node info /turtlesim /turtlesim Subscribers: /parameter_events: rcl_interfaces/msg/ParameterEvent /turtle1/cmd_vel: geometry_msgs/msg/Twist ... Action Servers: /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute Action Clients: rostest@rpi4-ros-test:~$ ``` turtlesim為server端,現在來看一下client端是不是控制器的node ``` rostest@rpi4-ros-test:~$ ros2 node info /teleop_turtle /teleop_turtle Subscribers: /parameter_events: rcl_interfaces/msg/ParameterEvent ... Action Servers: Action Clients: /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute ``` ### 列出現在ros環境所有的Action ``` rostest@rpi4-ros-test:~$ ros2 action list /turtle1/rotate_absolute ``` #### 還要列出type ``` rostest@rpi4-ros-test:~$ ros2 action list -t /turtle1/rotate_absolute [turtlesim/action/RotateAbsolute] ``` ### 查看Action的詳細資料(誰是Action client誰是Action servier) ``` rostest@rpi4-ros-test:~$ ros2 action info /turtle1/rotate_absolute Action: /turtle1/rotate_absolute Action clients: 1 /teleop_turtle Action servers: 1 /turtlesim ``` ### 列出Action的type支援的參數(也是interface指令) ![](https://docs.ros.org/en/foxy/_images/Action-SingleActionClient.gif) ``` rostest@rpi4-ros-test:~$ ros2 interface show turtlesim/action/RotateAbsolute # (Goal request structure,也就是你的請求需要的格式) # The desired heading in radians float32 theta --- # (Result structure,Action Server回應的格式) # The angular displacement in radians to the starting position float32 delta --- # (Feedback structure,過程中返回資料的格式) # The remaining rotation in radians float32 remaining ``` ### 呼叫一次Action試試看 ``` ros2 action send_goal <action_name> <action_type> <values> ``` ``` rostest@rpi4-ros-test:~$ ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" Waiting for an action server to become available... Sending goal: theta: 1.57 Goal accepted with ID: 513dbad0d4a54f56ace07d5d56a5c5a2 Result: delta: -0.7680007815361023 Goal finished with status: SUCCEEDED ``` 加上`--feedback`,接收feedback topic的內容 ``` rostest@rpi4-ros-test:~$ ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 3.60}" --feedback Waiting for an action server to become available... Sending goal: theta: 3.6 Goal accepted with ID: ec59e57be10c4e3e9100891b74cf7ab3 Feedback: remaining: 2.0368216037750244 Feedback: remaining: 2.0208213329315186 Feedback: remaining: 2.0048210620880127 Feedback: remaining: 1.988821268081665 Feedback: remaining: 1.9728214740753174 Feedback: remaining: 1.9568212032318115 Feedback: remaining: 1.9408209323883057 ... Feedback: remaining: 0.052817344665527344 Feedback: remaining: 0.036817312240600586 Feedback: remaining: 0.020817279815673828 Feedback: remaining: 0.00481724739074707 Result: delta: -2.0320041179656982 Goal finished with status: SUCCEEDED ``` ![](https://i.imgur.com/qWuCZWR.png) ## 使用rqt_console 打開它,同時啟動一套turtlesim ``` ros2 run rqt_console rqt_console ``` ### 開烏龜去撞牆試試? 可以發現log出現在了console上面 ![](https://i.imgur.com/HbqPjoE.png) ### 調整Node的log level 最尾巴加上`--log-level` log level類型 https://docs.ros.org/en/foxy/Tutorials/Beginner-CLI-Tools/Using-Rqt-Console/Using-Rqt-Console.html#logger-levels ``` Fatal Error Warn Info Debug ``` ``` ros2 run turtlesim turtlesim_node --ros-args --log-level WARN ``` ## 連機連起來! ### 最簡單的方式: 1. 確定兩台電腦都在同網域(可以相互Ping為佳) 2. 設定環境變數(兩台要是相同ID(代表兩台電腦在同個群組)) ``` # Windows(Powershell) $Env:ROS_DOMAIN_ID = 87 # Windows(CMD) set ROS_DOMAIN_ID=87 # Linux export ROS_DOMAIN_ID=87 ``` 3. 開啟Publisher與Subscriber節點,照理說兩端即可相互互動 https://youtu.be/BQpeC5w8CRs https://youtu.be/l9KSY9Q7er0 ## Coding! ### 建立開發環境 Coding部分請同時參考以下文章(有一些看得懂就沒有都搬過來紀錄了XDD) **以下主要使用VS Code進行開發** #### 建立Package https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html 1. 建立`你的工作區資料夾/src`的目錄結構 2. 在`src`資料夾底下輸入這個指令建立一個package(Python)(包名與節點名) ``` ros2 pkg create --build-type ament_python --node-name my_node my_package ``` 3. 編譯時建議在工作區的資料夾下(下層的資料夾有`src`以及`build`等) ``` colcon build --merge-install --packages-select pkgname ``` 如果遇到Windows出現這種結果是通知顯示方面的Bug,看到Success即可,可以忽略 ``` Finished <<< simple_230215 [3.75s] Summary: 1 package finished [4.20s] 1 package had stderr output: simple_230215 WNDPROC return value cannot be converted to LRESULT ``` 4. 使用rosrun跑跑看 * 設定Overlay(一個工作區一個overlay) ``` ros2_ws> .\install\local_setup.ps1 ``` * 如果是Linux使用者請用source 以下指令如果是zsh的話請使用.zsh副檔名取代原本的.bash ``` $ source ./install/setup.bash ``` * 執行工作區下你建立的package底下的node `ros2 run [package_name] [node_name]` ``` ros2_ws> ros2 run simple_230215 simple_230215 Hi from simple_230215. ``` 5. 修改套件的資訊(例如License) 需要改兩處:`package.xml`、`setup.py` ![](https://i.imgur.com/oYnKfW5.png) ![](https://i.imgur.com/8GlOL2j.png) 6. Windows下設定VS Code自動完成參考 ![](https://i.imgur.com/ZJb6ABx.png) ```json { "python.autoComplete.extraPaths": [ "C:\\ros2-windows\\Lib\\site-packages" ], "python.analysis.extraPaths": [ "C:\\ros2-windows\\Lib\\site-packages" ] } ``` Linux如果沒有自動完成可以嘗試以下路徑 ``` /opt/ros/foxy/lib/python3.8/site-packages ``` 7. 編譯前準備: 1. 將相依的套件放到package.xml ![](https://i.imgur.com/flH3IqW.png) 2. 將寫好的node放到setup.py中的entry_point(別忘記逗號) ![](https://i.imgur.com/ZLwtgWw.png) 3. 編譯(參考前面)與執行! Note:記得source自己寫好的新overlay(Windows PowerShell 執行 install 底下的.ps1檔案) ![](https://i.imgur.com/sVAhWJ6.png) ### 簡易Publisher、Subscriber紀錄 https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html 簡易Publisher與Subscriber,結合上面的範例檔以及PySerial通訊功能,以及一些自己理解程式碼註記的註解 * `src/simple_serled/simple_serled/serled_pub_node.py` ```python= # rclpy = ros2 重要功能的函式庫 import rclpy from rclpy.node import Node # 用來進行節點間溝通的String類型 # The next statement imports the built-in string message type # that the node uses to structure the data that it passes on the topic. from std_msgs.msg import Bool # ^^以上用到的函式庫需要在Build之前放到depencies(rclpy、std_msgs)^^ class LEDPub(Node): def __init__(self): super().__init__('led_publisher') # 建立publisher(數據型態、要發佈到的topic、QoS Profile) self.publisher = self.create_publisher(Bool, 'led_topic', 10) self.ask_and_publish() def ask_and_publish(self): # 使用者輸入 response = input("Answer '1' to turn on, or answer '0' to turn off > ") # 建立Message結構 msg = Bool() # 分析使用者輸入甚麼 if(response == "1"): # 底下指令可以在ROS列印紀錄訊息(Log) self.get_logger().info("You want to trun on!") # 將資料填入data msg.data = True # 透過topic發送訊息 self.publisher.publish(msg) elif(response == "0"): self.get_logger().info("You want to trun off!") msg.data = False self.publisher.publish(msg) else: # 輸入錯誤的指令 self.get_logger().info("Wrong command!") # recall itself self.ask_and_publish() def main(args=None): # 初始化 rclpy.init(args=args) # 實例化Node的類別 led_pub = LEDPub() # 開始運行Node要做的程式碼 rclpy.spin(led_pub) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) # 強制回收節點的指令碼,不寫也可以,ROS的垃圾回收機制會自動回收 led_pub.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` * `src/simple_serled/simple_serled/serled_sub_node.py` ```python= # rclpy = ros2 重要功能的函式庫 import rclpy from serial import Serial from rclpy.node import Node # 用來進行節點間溝通的String類型 # The next statement imports the built-in string message type # that the node uses to structure the data that it passes on the topic. from std_msgs.msg import Bool # ^^以上用到的函式庫需要在Build之前放到depencies(rclpy、std_msgs、pyserial)^^ class LEDSub(Node): def __init__(self): super().__init__('led_subscriber') # 建立Subscriber(數據型態、接收的topic、接收到資料觸發的函式、QoS Profile) self.subscription = self.create_subscription( Bool, 'led_topic', self.get_and_control, 10) self.subscription # prevent unused variable warning # 建立Serial COM_PORT = 'COM3' BAUD_RATES = 115200 self.ser = Serial(COM_PORT, BAUD_RATES) # Hint it's ready # 底下指令可以在ROS列印紀錄訊息(Log) self.get_logger().info("Subscriber ready!") # 接收到訊息後要做的事情 def get_and_control(self, msg): # Hint Received Message self.get_logger().info("Received from Node = {r_msg}".format(r_msg = msg.data)) # Analyze want to On or Off, and write it to serial port if msg.data: self.ser.write(b"STAT:1") else: self.ser.write(b"STAT:0") self.get_logger().info("Message sent to serial port finished!") def main(args=None): # 初始化 rclpy.init(args=args) # 實例化Node的類別 led_sub = LEDSub() # 開始運行Node要做的程式碼 rclpy.spin(led_sub) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) # 強制回收節點的指令碼,不寫也可以,ROS的垃圾回收機制會自動回收 led_sub.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() ``` * `package.xml`需要增加的依賴關係 ```xml <!-- 程式會用到的depencies --> <exec_depend>rclpy</exec_depend> <exec_depend>std_msgs</exec_depend> <exec_depend>pyserial</exec_depend> ``` * `setup.py` ```python # Entry points 需要改成這樣 entry_points={ 'console_scripts': [ 'serled_pub_node = simple_serled.serled_pub_node:main', 'serled_sub_node = simple_serled.serled_sub_node:main' ], ``` * 執行範例參考(Arduino控制燈泡部分請自行搜尋範例或自己寫一個,並修改Arduino或是序列埠需寫入/接收的內容) https://youtu.be/my9ZJMWzArI