這一篇想介紹如何用 Python 來讓節點發布和訂閱訊息,做到上一篇介紹的 rostopic echo
和 rostopic pub
這兩個指令相同的效果。
這裡寫了一個 turtle_pub.py
來示範發布者的程式結構。這支程式會建立一個發布者節點,發布速度 Twist
的訊息到話題 cmd_vel
,讓烏龜會原地繞圈。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rospy
# 訊息: geometry_msgs/Twist 表示直線速度和角速度
from geometry_msgs.msg import Twist
def turtle_pub():
# 建立發布者, 指定話題'/turtle1/cmd_vel'
publisher = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size = 10)
rate = rospy.Rate(10)
# 發布訊息
while not rospy.is_shutdown():
# 定義訊息類型
msg = Twist()
# 定義訊息類容
msg.linear.x = 2.0
msg.angular.z = -1.8
rospy.loginfo(msg)
# 讓發布者發布訊息
publisher.publish(msg)
rate.sleep()
if __name__ == '__main__':
# 節點初始化
rospy.init_node('turtle_publisher', anonymous=True)
try:
turtle_pub()
except rospy.ROSInterruptException:
pass
下面來解釋這段腳本的每個部份在做什麼。
import rospy
from geometry_msgs.msg import Twist
geometry_msgs.msg
模組的 Twist
類別:
上一篇文章提到 geometry_msgs/Twist
是一個 ROS 內建的訊息類型,用於表示直線速度和角速度。這個訊息是定義在 geometry_msgs.msg
模組的 Twist
類別,於是在程式開頭匯入。
turtle_pub()
函式def turtle_pub():
publisher = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size = 10)
rate = rospy.Rate(10)
while not rospy.is_shutdown():
msg = Twist()
msg.linear.x = 2.0
msg.angular.z = -1.8
rospy.loginfo(msg)
publisher.publish(msg)
rate.sleep()
在這段函式中包含了幾個功能:
建立發布者
使用 rospy.Publisher()
建立一個發布者,指定將訊息發佈到 /turtle/cmd_vel
上,並用一個變數 publisher
儲存。
格式:
pub = rospy.Publisher(topic_name, msg_class, queue_size)
topic_name
:指定要發布的話題名稱。
msg_class
:指定訊息類型。
queue_size
:設定佇列大小,當訊息被發布到一個 topic 時,如果有訂閱者暫時無法接收訊息,那麼這些訊息會被保留最近幾個並存放在一個佇列中,只能設定整數。
發布訊息
在 while 迴圈內,首先利用 Twist()
建立了一個訊息變數 msg
,並且設定其直線速度 linear.x
和角速度 angular.z
的向量大小(前進和右轉),單位是 m/s 和 rad/s。定義完訊息後,再利用 publish()
方法讓 publisher
執行發布。
接著再寫一個 turtle_sub.py
用來示範訂閱者的程式結構。它會建立一個訂閱者節點,訂閱話題 cmd_vel
的訊息,並且在終端機持續輸出所有該話題的訊息。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rospy
# 訊息: geometry_msgs/Twist 表示直線速度和角速度
from geometry_msgs.msg import Twist
# 回呼函式
def callback(data):
print(data.linear)
print("------------------------------")
print(data.angular)
print("------------------------------")
def turtle_sub():
# 建立訂閱者, 指定話題'/turtle1/cmd_vel'
rospy.Subscriber('/turtle1/cmd_vel', Twist, callback)
rospy.spin()
if __name__ == '__main__':
# 節點初始化
rospy.init_node('turtle_subscriber', anonymous=True)
turtle_sub()
這段腳本最主要的部份是定義 callback()
和 turtle_sub()
這兩個函式。
callback()
函式def callback(data):
print(data.linear)
print("------------------------------")
print(data.angular)
print("------------------------------")
這是一個典型的回呼函式(Callback function),也就是在特定條件或事件發生時,才會被做為參數呼叫的函式。函式裡面定義了接收到丟進來參數後,便會輸出四行文字內容。
turtle_sub()
函式def turtle_sub():
rospy.Subscriber('/turtle1/cmd_vel', Twist, callback)
rospy.spin()
這段函式和前一個腳本的 turtle_pub()
概念類似:
建立訂閱者
使用 rospy.Subsriber()
建立一個訂閱者,指定接收發佈到 /turtle/cmd_vel
上的訊息,因為訂閱者基本上是被動接收,不用建立一個變數儲存。
格式:
rospy.Subsriber(topic_name, msg_class, callback)
callback
:指定收到新訊息時呼叫的回呼函式名稱(可以改名稱,但記得要和前面的回呼函式名稱一致)。當執行到這行程式碼時,ROS 便會開始監聽 /turtle/cmd_vel'
話題是否出現 Twist
類型的訊息,並在接收到的訊息時,呼叫並將該訊息傳入 callback()
函式。後面 rospy.spin()
這行是設定使節點保持運作直到被手動停止或意外終止。
以上兩支程式是發布者和訂閱者比較經典的程式結構,透過程式我們就能更精細地設計指令及做出更複雜的動作,比用指令會更方變得多。下一篇會繼續介紹服務代理的 Python 程式結構。