---
# System prepended metadata

title: ROS 2 新手筆記(Foxy版本)

---

# 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