※Pythonベースの話です。Cの情報は少なすぎて諦めました。
※必ず文字コードはUTF-8、改行はLF、つまり昨今linuxの標準的なコードで記載願います。特に、個々の情報をコピーして使う方や、Windowsやを使ってファイルを作る人は要注意。(Mac?なにそれ?私の前でその言葉を言うな。)
・ワークフォルダの作成
まず、ROS2(Foxy)の開発支援ツール「colcon」は何らかのワークディレクトリを必要とします。
ここでは、よく使われる名前「colcon_ws」とします。
そして以下のような配置をします。
~\colcon_ws\
└src\
基本的にはsrcの中で作業をします。
・パッケージを作る。
用語はさておき、今回以下のような構成でイゴかします。
このためにROS2側で必要なパッケージは3つ
①ROS2ノード:cp_mbed_io_stabの実体となるプログラムのパッケージ
②トピック通信定義:mbed2rasppiを定義するためのパッケージ
③トピック通信定義:rasppi2mbedを定義するためのパッケージ
こういうもんだと思ってください!
以下で①②③作成方法を記載しますが、なかなかむつかしい所もあると思うので、(どうせ作るだけの作業になるし。)①②③を作成したcolcon_wsディレクトリごと固めたファイルを用意しておきます。
→右クリックで保存
以下、作り方ですが、メインはもちろん①のプログラムになりますが、通信定義がないと話にならんので、②③を先に作成します
②、③の作成
メッセージの定義についてはノードは定義せずパッケージしか作らないので、
~/colcon_ws/src$ros2 pkg create mbed2rasppi_msgs
~/colcon_ws/src$ros2 pkg create rasppi2mbed_msgs
とします。同じく書式は
ros2 pkg create <package_name>
ですが、なぜ、「_msg」をつけるのか?→知らんわ!
とにかく、こんなメッセージが出るはずです
すると、それぞれ以下のようなフォルダができています(青字部分は①②で異なる)
~/colcon_ws/src/raspp22mbed_msgs/
├package.xml
├CMakeLists.txt
├include/・・・(以下略)
├src/・・・(以下略)
└msg/
└Rasppi2mbed.msg
赤部分のファイルを以下のとおり変更(追記)する。
そして緑部分のディレクトリとファイルを作る。(ファイル名一文字目が大文字であることに注意)
・緑部分のフォルダを作成し、以下ファイルを作成
(Rsappi2mbed.msg)
uint8 v_bluestat_rpi1
float32 v_bluestat_rpi2
注意:引用するときは注意。コードはutf-8,改行はLF
(Mbed2rasppi.msg)
uint8 v_bluestat1
float32 v_bluestat2
注意:引用するときは注意。コードはutf-8,改行はLF
察しのいい方はわかったかと思いますが、通信したいパラメータを増やしたいならば、ここを増やせばよい
・package.xml
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
注意:インデントそろえること
・CMakeLists.txt
set(msg_files
"msg/Rasppi2mbed.msg"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
)
ament_export_dependencies(rosidl_default_runtime)
注意1:インデントそろえること
注意2:「msg/Rasppi2mbed.msg」は②③で変更すること。
ここまで出来たら、①の作成をする。
①の作成。
ノード(ROS2上の仕事の単位だと思ってください!)なので、ノードも作る宣言をします。
$cd ~/colcon_ws/src
~/colcon_ws/src$ros2 pkg create --build-type ament_python --node-name cp_mbed_io_stab_node cp_mbed_io_stab
一応書式を解説すると
ros2 pkg create –build-type ament_python –node-name
以下のようなメッセージが流れて・・・
結果、以下のようなフォルダ構成が作られる
~/colcon_ws/src/cp_mbed_io_stab/
├package.xml
├setup.cfg
├setup.py
├include/・・・(以下略)
├test/・・・(以下略)
├resource/・・・(以下略)
└cp_mbed_io_stab
├__init__.py
└cp_mbed_io_stab_node.py
・package.xmlを以下の通り編集する
赤囲み部分を挿入。
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
注意:インデントをそろえること。
・cp_mbed_io_stab_node.pyの記載、編集
ちょっとここで解説すると大変なので、まずは掲示のみ。
今回使うのは以下のようなプログラムになります。
メッセージ定義のキモになっている部分は色を変えておきます。改造時の参考にどうぞ
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile,ReliabilityPolicy,DurabilityPolicy
from mbed2rasppi_msgs.msg import Mbed2rasppi
from rasppi2mbed_msgs.msg import Rasppi2mbed
import time
#----------クラス定義-----------
class Central_Controller(Node):
#コンストラクタ(初期定義)
def __init__(self):
super().__init__("input_control")
self.v_rx_cntr:int = 0
self.v_bluestat1:int = 0
self.v_bluestat2:float = 0.0
self.v_bluestat_rpi1:int = 0
self.v_bluestat_rpi2:float = 0.0
#通信品質
profile = QoSProfile(depth=10, reliability=rclpy.qos.ReliabilityPolicy.BEST_EFFORT,\
durability=rclpy.qos.DurabilityPolicy.VOLATILE)
#送信設定
self.pub = self.create_publisher(Rasppi2mbed,'rasppi2mbed',profile)
#受信設定
self.sub = self.create_subscription(Mbed2rasppi,'mbed2rasppi',self.callback,profile)
#コールバック関数
def callback(self,msg):
msg_pub = Rasppi2mbed()
#----------------mbed2rasppi購読値の読み込み
self.v_bluestat1 = msg.v_bluestat1
self.v_bluestat2 = msg.v_bluestat2
#-------------------------演算
self.v_rx_cntr += 1
self.v_bluestat_rpi1 = self.v_bluestat1
self.v_bluestat_rpi2 = self.v_bluestat2
#-------------rasppi2mbedへの発行データセット---------------------------
msg_pub.v_bluestat_rpi1 = self.v_bluestat_rpi1
msg_pub.v_bluestat_rpi2 = self.v_bluestat_rpi2
#----------------コンソールへのデータモニタ------------------
print('rx_counter='+str(self.v_rx_cntr))
print('RX:v_bluestat1='+str(self.v_bluestat1),'v_bluestat2='+str(self.v_bluestat2))
print('TX:v_bluestat_rpi1='+str(self.v_bluestat_rpi1),'v_bluestat_rpi2='+str(self.v_bluestat_rpi2))
print('-------------------')
self.pub.publish(msg_pub)
# メイン関数 、といいつつ通信待機させるだけなので、ここはほぼいじる必要がないハズ。
def main():
#RCLの初期化を実行
rclpy.init()
#クラスをコンスタンス化
node = Central_Controller()
#ループに入りnodeの処理を実行させる
rclpy.spin(node)
#nodeを破壊
node.destroy_node()
#RCLをシャットダウン
rclpy.shutdown()
if __name__ == '__main__':
main()
こんな感じにコードを書いて、①についてはあとはビルドまち。
ここで、このノードの単体試験ってできないのかって話になる。
このノードはmbed2raspppiというトピックを受けると一回実行して結果をrasppi2mbedというトピックとして配信するので、このまま単体だとずっと待機してしまう。
逆を言えば。mbed2rasppiというトピックをはいてくれる誰かがいれば動く。
なので、例えば、「tx_mbed2rasppi」というパッケージを上と同じように作ってMBED2RASPPIトピックを定期的にはかせればよい。こんな感じ。
#cp_mbed_io_stabをテストするためのスタブ。
#---------- include -----------
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile,ReliabilityPolicy,DurabilityPolicy
from mbed2rasppi_msgs.msg import Mbed2rasppi
from rasppi2mbed_msgs.msg import Rasppi2mbed
import time
#----------クラス定義-----------
class Data_Transmitter(Node):
#コンストラクタ
def __init__(self):
super().__init__("generator")
self.v_tx_cntr:int = 0
self.v_bluestat1:int = 0
self.v_bluestat2:float = 0.0
self.v_bluestat_rpi1:int = 0
self.v_bluestat_rpi2:float = 0.0
#通信品質
profile = QoSProfile(depth=10, reliability=rclpy.qos.ReliabilityPolicy.RELIABLE,\
durability=rclpy.qos.DurabilityPolicy.TRANSIENT_LOCAL)
#送信設定
self.pub = self.create_publisher(Mbed2rasppi,'mbed2rasppi',profile)
#受信設定
self.sub = self.create_subscription(Rasppi2mbed,'rasppi2mbed',self.sub_callback,profile)
#タイマー設定
self.time_period = 0.1 #タイマー割り込み周期[sec]
self.tmr = self.create_timer(self.time_period, self.timer_callback) #タイマー割り込み設定
#タイマー用コールバック関数
def timer_callback(self):
msg_pub = Mbed2rasppi()
self.v_tx_cntr += 1
#データ送信
msg_pub.v_bluestat1 = 1
msg_pub.v_bluestat2 = 0.0
#時刻取得[ns]
#current_time = self.get_clock().now().nanoseconds
#コンソール上に時間を表示
print('tx_counter='+str(self.v_tx_cntr))
self.pub.publish(msg_pub)
#受信コールバック関数
def sub_callback(self,msg):
self.v_bluestat_rpi1 = msg.v_bluestat_rpi1
self.v_bluestat_rpi2 = msg.v_bluestat_rpi2
# メイン関数 、といいつつ通信待機させるだけなので、ここはほぼいじる必要がないハズ。
def main():
#RCLの初期化を実行
rclpy.init()
#クラスをコンスタンス化
node = Data_Transmitter()
#ループに入りnodeの処理を実行させる
rclpy.spin(node)
#nodeを破壊
node.destroy_node()
#RCLをシャットダウン
rclpy.shutdown()
if __name__ == '__main__':
main()
●単体テスト
ビルドする。
ちょっとめんどいけど、ここからのアクションは「(ファイルの中身は別にいいけど)ファイル構成を変えたら」ターミナル再起動するようにしましょう。
理由は./bashrcに埋め込んだ以下のスクリプトを走らせたいから。
これがファイル構成をROS2に教える役目をしているっぽい。
source /opt/ros/foxy/setup.bash
source ~/colcon_ws/install/setup.bash
(コマンドラインで毎回打つとか、colcon buildとセットにしたスクリプト作っても構わないですが!)
とにかく、ターミナルを開く
そりゃ初回ビルドしていないから、そんなフォルダないよね。というわけで、一回無視してビルド
たいてい初回はこんなワーニングの嵐にあるんですが
一回ターミナルを閉じてもう一回やると
と、ワーニングなくなります。これは一回ビルドが走って、あるべきファイルが作られて、かつ、ros2がその場所を覚えたからだと思いますがよくわからん!
ビルドしたら、コンソールをさらに2つ開いてそれぞれで以下実行
ros2 run tx_mbed2rasppi tx_mbed2rasppi_node
ros2 run cp_mbed_io_stab cp_mbed_io_stab_node
実行するとこうなる。
こうなれば、左(tx_mbed2rasppi_node)が発行したMbed2rasppiにより、右(cp_mbed_io_stab_node)が動いていることが分かる。
ついでに動いているノードとトピックを見てみよう
ちゃんと動いていると認識されている。
どうせなら、(cp_mbed_io_stab_node)が動いた結果吐き出しているはずのトピックの中身も見てみよう
$ros2 topic echo rasppi2mbed
Rasppi2mbed.msgで定義した情報がちゃんと吐き出されている
これでラズパイ側の準備は完了のはず。
※ちなみに、パッケージの削除は該当のフォルダー消してターミナル再起動(それをROS2に覚えさせて)でOK
※あと、カスタムメッセージを変更したのに反映されないとか、変になったとかいう場合は
colcon_ws内の
install
build
log
ディレクトリを消してターミナル再起動すると案外正常に動くようになる。
ここから下は過去の試行錯誤の記録
●ワークスペースの作成
環境構築の一緒
ワークスペースフォルダを作成します。皆で使う環境ならばなるべく参考書通りにした方がいいと存じます
最後のsauce行は端末を開くたびに必要になるので、./.bashrcに登録しておくと便利かな。
mkdir -p ~/colcon_ws/src
cd ~/colcon_ws
source ~/colcon_ws/install/setup.bash
●パッケージ(管理単位、管理フォルダーと言ってもいいかもしれない)の作成
もちろん2行目がキモです
ここではpythonwライブラリ使用して、helloというパッケージを作ることを指定指定しています。しかも、rosサポートのライブラリを指定しています
cd ~/colcon_ws/src
ros2 pkg create –build-type ament_python hello
●「setup.py」で利用するスクリプトを指定します(説明は別途)
●プログラムを書きます(説明は別途)
●ビルドしします
ビルドしたらワークスペースの再設定が必要になります
cd ~/colcon_ws
colcon build
source ~/colcon_ws/install/setup.bash
●実行します
Helloパッケージのhello_nodeというノードを実行せよってことになります
ros2 run hello hello_node