
Ch4. Python์ผ๋ก ROS2 ํ ํฝ ๋ค๋ฃจ๊ธฐ
์ด๋ฒ ๊ธ์์๋ ์ ์ฑ ์์ Ch4์์์ Python์ผ๋ก ROS2 ํ ํฝ์ ๋ค๋ฃจ๋ ๋ฒ์ ๋ํด์ ์์ฑํ๊ณ ์ ํ๋ค.
VScode๋ฅผ ์ด์ฉํด์ ์ปดํจํฐ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ฐ๋ก ์ด์ฉํ์ฌ ํ ์๋ ์์ง๋ง, ์ฑ ์์ ๋์ค๋ ๋ฐฉ๋ฒ์ฒ๋ผ jupyter notebook์ ์ด์ฉํ์ฌ ์งํํด๋ณด๊ณ ์ ํ๋ค.
1. ์ค์น ๋ฐ ์ค๋น
ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ด์ฉํด์ pip๋ฅผ ์ค์นํ๊ณ , ์ ๋ฐ์ดํธ๋ฅผ ์งํํ๋ค.
sudo apt install python3-pip
pip3 install --upgrade pip

๊ธ์ ์ฐ๋ ์์ ์ ์ต์ ๋ฒ์ ์ผ๋ก ์ ๋ฐ์ดํธ๊ฐ ๋๋ ๊ฒ์ ๋ณผ ์ ์๊ณ , ๊ทธ ์ดํ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ์ฌ pip๋ฅผ ์ด์ฉํ์ฌ jupyter, ipywidegets, pyyaml, bqplot์ ์ค์นํ๊ณ ์ ํ๋ค.
pip3 install jupyter ipywidgets pyyaml bqplot
์ค์น๊ฐ ๋๋๋ฉด ์ฌ๋ถํ ํ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ์ฌ ์น๋ธ๋ผ์ฐ์ ๋ฅผ ์คํ์ํจ๋ค.
jupyter notebook

ํน์๋ผ๋ ์๋ฌ๊ฐ ๋๋ค๋ฉด ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ ๋ค ์ฌ๋ถํ ์ ํ ํ ๋ค์ ์๋ํ๋ฉด ๋๋ค.
sudo apt install jupyter-core
2. Python์ผ๋ก ํ ํฝ ๊ตฌ๋ ํ๊ธฐ
๋จผ์ home์ python ํด๋๋ฅผ ๋ง๋ค๊ณ ๊ฑฐ๊ธฐ์ humble์ ์คํํ ๋ค jupyter notebook์ ์คํํ๋ค.

์ดํ python ํด๋์์ turtlesim ํจํค์ง์ turtlesim_node๋ฅผ ์คํํ๋ค.
์ดํ Jupyter์์ Subscriber Test๋ผ๋ ipynb ํ์ผ์ ๋ง๋ ๋ค ์ฝ๋ฉ์ ์์ํ๋ค.

๊ฐ์ฅ ๋จผ์ ๋ชจ๋์ import ํ๊ธฐ ์ํด์ ๋ค์๊ณผ ๊ฐ์ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ค.
import rclpy as rp
from turtlesim.msg import Pose
๊ฐ์ฅ ๋จผ์ alias๋ฅผ ์ด์ฉํด rclpy ๋ชจ๋์ ๋ถ๋ฌ์์ผ๋ฉฐ, turtlesim_node์ ํ ํฝ ์ค /turtle1/pose๋ผ๋ ํ ํฝ์ ๊ตฌ๋ ํ๊ธฐ ์ํด์ turtlesim/msg/Pose ๋ผ๋ ๋ฐ์ดํฐ ํ์ ์ ์ด์ฉํจ์ ์๊ณ , ์ด๋ฅผ ์ด์ฉํด ๋ค์๊ณผ ๊ฐ์ ๋ช ๋ น์ด๋ฅผ ์ด์ฉํด Pose๋ผ๋ ํด๋์ค๋ฅผ ์ด์ฉํ๋ค.
์ดํ rclpy๋ฅผ ์ด๊ธฐํ ํ๊ธฐ ์ํด์ ๋ค์ ์ฝ๋๋ฅผ ์คํ ํ, rcply์ create_node๋ฅผ ์ด์ฉํด sub_test๋ผ๋ ์ด๋ฆ์ ๋ ธ๋๋ฅผ ๋ง๋ ๋ค.
rp.init()
test_node = rp.create_node('sub_test')
๊ทธ ์์ ๊น์ง ํ๋ค๋ฉด ros2 node list๋ฅผ ํตํด์ ๋ ธ๋๋ฅผ ํ์ธํ๋ฉด /sub_test์ /turtlesim ์ด๋ ๊ฒ 2๊ฐ๊ฐ ์ ์์ ์ผ๋ก ์กํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.

โป ์ง๊ธ ์์ฑ์ค์ธ ์ฝ๋ โป
: ๊ฐ๋จํ ํ ํฝ์ ๊ตฌ๋ ํ๋ python ์ฝ๋
> ์ด๋ค ๋ ธ๋๊ฐ ํ ํฝ์ ๋ฐํํ๋ฉด python์ผ๋ก ๋ ธ๋๋ฅผ ๊ตฌ๋ ํ๋ subscriber๊ฐ ๋์ํจ.
>> callback ํจ์
def callback(data):
print("--->")
print("/turtle1/pose : ", data)
print("X : ", data.x)
print("Y : ", data.y)
print("Theta : ", data.theta)
์๋๋ log์ ๊ธฐ๋ก์ ๋จ๊ธฐ๋ฉด ์ข์ง๋ง, ๊ฐ๋จํ ๊ณต๋ถ์ด๋ฏ๋ก print๋ก ์งํํ์๋ค.
์ดํ ํ ํฝ subscriber๋ฅผ ๋ง๋ค๊ธฐ ์ํด์ ๋ค์ ๋ช ๋ น์ ์ ๋ ฅํ๋ค.
test_node.create_subscription(Pose, '/turtle1/pose', callback, 10)
#test_node.create_subscription(<data_type>, '<topic_name>', <callback>, <QoS History>)
์ด๋ ํธ์ถ์ ๋ฐ๋ก ๋ฐ ์ค๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์งํํ๋ฉด ๋๋ค.
์ฌ๊ธฐ์ QoS History๋ ๊ฐ๋จํ๊ฒ ์ ์ฅํ ์ํ์ ์๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.

rqt_graph๋ฅผ ์คํํ ๋ชจ์ต์ ๋ค์๊ณผ ๊ฐ๋ค.
์ดํ ๋ค์์ ๋ช ๋ น์ ์ด์ฉํด ํ ํฝ์ ๊ตฌ๋ ํ ์ ์๋ค.
rp.spin_once(test_node) # ํ ๋ฒ๋ง ๊ตฌ๋
rp.spin(test_node) # ๋ฌดํํ ๊ตฌ๋
- ์ ํ๋ ํ์๋งํผ ํ ํฝ์ ๋ฐ๋ ํจ์ ๋ง๋ค๊ธฐ
import rclpy as rp
from turtlesim.msg import Pose
rp.init()
test_node = rp.create_node('sub_test')
cnt = 0
def callback(data):
global cnt
cnt += 1
print(">", cnt, " -> X : ", data.x, " -> Y : ", data.y, " -> Theta : ", data.theta)
if cnt > 3:
raise Exception("Subscription Stop")
test_node.create_subscription(Pose, '/turtle1/pose', callback, 10)
rp.spin(test_node)

์ด๋ฅผ ํตํด ์ ๋๋ก ๊ตฌ๋ ์ ์คํํจ์ ์ ์ ์๋ค.
3. Python์ผ๋ก ํ ํฝ ๋ฐํํ๊ธฐ
Publisher Test.ipynb ํ์ผ์ ๋ง๋ ๋ค ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
import rclpy as rp
from geometry_msgs.msg import Twist
rp.init()
test_node = rp.create_node('pub_test')
msg = Twist()
print(msg)
msg.linear.x = 2.0
print(msg)
pub = test_node.create_publisher(Twist, '/turtle1/cmd_vel', 10)
pub.publish(msg)
msg.linear.x = 2.0
msg.angular.z = 2.0
pub.publish(msg)

์ดํ ์ผ์ ์ฃผ๊ธฐ๋ก ํ ํฝ์ ๋ฐํํ๊ณ ์ ํ๋๋ฐ, ์ด๋ด ๋๋ ์ผ์ ์๊ฐ์ ์ ์งํด์ผ ํ๋ฏ๋ก timer ๊ธฐ๋ฅ์ ํ์ฉํด์ผ ํจ.
์ด๋ create_timer ๋ฅผ ์ด์ฉํ๋ฉฐ ๋จ.
cnt = 0
def timer_callback():
global cnt
cnt += 1
print(cnt)
pub.publish(msg)
if cnt > 3:
raise Exception("Publisher Stop")
timer_period = 0.1
timer = test_node.create_timer(timer_period, timer_callback)
rp.spin(test_node)

์ดํ ํ์คํ ํด๋น ๋ ธ๋๋ฅผ ์ข ๋ฃํ๊ธฐ ์ํด์ ๋ค์ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋๋ค.
test_node.destroy_node()
๋ค์์๋ Ch5. Python์ผ๋ก ์๋น์ค ํด๋ผ์ด์ธํธ ๋ค๋ฃจ๊ธฐ ์ ๋ํด์ ์จ๋ณด๊ณ ์ ํ๋ค.
ํน์ ๋ชฐ๋ผ ํ์ผ์ ๋จ๊ธด๋ค.(์ถํ ๊นํ์ ๋ค์ ์ ๋ฆฌํ ์์ ์ด๋ค.)