# 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開發為主)

* 將PyQt5降板至5.12
不然就不能玩turtlesim(新手入門用的烏龜範例)啦:
參考:https://github.com/ros/ros_tutorials/issues/126#issuecomment-1124080872
```
pip install PyQt5==5.12.3
```
* 設定PyQt5函式庫的路徑在環境變數下

## 教學
中文的安裝方式
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 控制烏龜範例
用方向鍵控制第一隻烏龜

使用rqt生成第二隻烏龜(Service呼叫)

可以在Service列表找到跟範例程式(turtlesim)相關的參數

透過Call新的參數改變烏龜畫出來的線的屬性(Service呼叫)

remap功能控制turtle2(修改node內的參數)

## Node
### 列出所有在線上的節點
```
ros2 node list
```
### 重命名節點
```
ros2 run turtlesim turtle_teleop_key --ros-args --remap __node:=joystick
--ros-args >> 代表要修改ros執行的引數
--remap >> 重新映射
__node:= >> 將節點名字改成等號後面的東西
```
烏龜的控制器變成了joystick,不是原本的名字

### 檢查節點的內容
```
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

或是使用rqt程式 **Plugins > Introspection > Node Graph**
:::warning
Windows 的 node graph 可能顯示方面有些Bug,會出現一個空白的圈圈於畫面上,不過不影響程式的功能!

:::
### 檢查之間的主從關係(滑鼠放在上面會有顏色強化)

`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與關係

(底下的hide也可以把一些隱藏的topic顯示出來)
### echo聽取某個topic發送的訊息

在圖上產生一個debug節點去聽取某個topic的訊息

文字檢查節點狀態(多少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格式*

once改為rate的話,代表固定頻率下指令

### 檢查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:~$
```

## 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
```

### 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
```

### 以你要的參數啟動節點
```
ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>
```

## Action
Action的結構是給長時間的任務設計的,例如烏龜範例的控制器上的固定旋轉角度就是一種Action

```
[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指令)

```
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
```

## 使用rqt_console
打開它,同時啟動一套turtlesim
```
ros2 run rqt_console rqt_console
```
### 開烏龜去撞牆試試?
可以發現log出現在了console上面

### 調整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`


6. Windows下設定VS Code自動完成參考

```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

2. 將寫好的node放到setup.py中的entry_point(別忘記逗號)

3. 編譯(參考前面)與執行!
Note:記得source自己寫好的新overlay(Windows PowerShell 執行 install 底下的.ps1檔案)

### 簡易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