ロボティクス入門ゼミ 教材 2017-12-27公開 (最新版2018-10-19)
MQTTというプロトコルを使ってEV3間あるいはEV3とパソコン間などの通信を行ってみる。
MQTTは、ブローカー(仲介者)を介してメッセージ(データ)のやりとりをする軽量のプロトコル(通信規約)。 ブローカーはMQTTサーバとも呼ばれる。 また、メッセージを送信する側をパブリッシャー(出版者)、 受信する側をサブスクライバー(購読者)と呼ぶ。 パブリッシャーやサブスクライバーは単数でも複数でもかまわない。 パブリッシャーはトピック(話題)を指定してメッセージをブローカーに送る(直接サブスクライバーに送るのではない)。 そしてブローカーがそのトピックを購読しているサブスクライバーにメッセージを配信する。
例えば、あるEV3から他のEV3にメッセージを転送する場合、 それを仲介するためのブローカーが必要である。 ブローカーを別途用意したりパソコンをブローカーとして使用してもかまわないが、 以下の例のように、一方のEV3をブローカとして使用すると便利である。 つまり、そのEV3は他のEV3やパソコンからのメッセージを受信する場合はブローカー兼サブスクライバーとして機能し、 センサの値などを他のEV3やパソコンに送信する場合は、自分自身がパブリッシャーにもなる。
ev3devのサイトでMQTTを使ったサンプルが紹介されているので、それも参考にする。
http://www.ev3dev.org/docs/tutorials/sending-and-receiving-messages-with-mqtt/
以下、パソコン側のシェルは、EV3側と区別するために次にようなプロンプトで表記してある。
your_pc:~$
ところで、パソコンが利用できない、あるいは、WindowsやMacを使っているのでセットアップをするのが面倒、 というような場合には、別のEV3をパソコンとして利用してもかまわない。 もちろん通常のパソコンではなく Raspberry Pi を始めとしたシングルボードコンピュータでも十分作業できる。
MQTTのサーバにmosquitto、クライントにmosquitto-clientsというオープンソースのパッケージを使用する。 stretch版のev3devには、mosquitto, mosquitto-clients がすでに収録されているので新たにインストールする必要はない (2018-10-19追記)。 また、pythonからもMQTTを使えるように paho-mqtt というライブラリをインストールしておく。 paho-mqttはev3devに収録されていないので、pip3を使ってインストールする。
robot@ev3dev:~$ sudo apt update robot@ev3dev:~$ sudo apt install python3-pip robot@ev3dev:~$ sudo pip3 install paho-mqtt
パソコン側からMQTTでEV3にメッセージを送ったり受け取ったりするために、 上記のmosquitto-clientsをパソコンにもインストールしておく。 DebianやUbuntuの場合は、次のように簡単にインストールできる (WindowsやMacOSで使用する場合は各自で調べること)。
your_pc:~$ sudo apt-get install mosquitto-clients
パソコン側でもpythonを使用する場合には、上記のEV3のセットアップ同様にして paho-mqtt をインストールしておく。
まずEV3にログインして、mosquitto (MQTTサーバ) が動いているか確認する。
robot@ev3dev:~$ sudo netstat -apt
netstat
はネットワーク接続の状態を表示するコマンドで、
次の出力のように、mosquittoが1883番ポートをListenしていればOK。
Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 localhost:domain *:* LISTEN 395/connmand tcp 0 0 *:ssh *:* LISTEN 398/sshd tcp 0 0 *:1883 *:* LISTEN 370/mosquitto tcp 0 0 ev3dev:ssh 192.168.2.23:49358 ESTABLISHED 635/sshd: robot [pr
(プログラム名を表示するためには管理者権限が必要なので sudo を使った)
mosquittoが見当たらない場合は、正しくインストールされていない可能性がある。
次に、EV3をサブスクライバーにして'test'というトピック(話題)を購読(サブスクライブ)する。 トピック名は適当につけてよいが、'ev3/outA/speed_sp' のように / で区切った階層構造のようなトピック名がよく使われる。
robot@ev3dev:~$ mosquitto_sub -h 192.168.2.30 -t 'test'
この例ように、ブローカのホスト名あるいはIPアドレスを-h に続けて指定する
(今回は、EV3自身がブローカーなので自分のIPアドレスを指定する)。
また、購読するトピックは-t
に続けて指定する(この例では'test')。
"command not found" のようなエラーが出る場合は、 mosquitto-clients が正しくインストールされていない可能性がある。
ところでこの例では、MQTTサーバを同一のEV3上で動かしているので、IPアドレスの代わりに localhost と指定してもよい。
robot@ev3dev:~$ mosquitto_sub -h localhost -t 'test'
これで、'test'というトピックを購読している状態になる。 新しいメッセージが出版されたら、その都度ここに表示される。
購読を停止する(サブスクライブを中断する)場合は、Controlキーを押しながら c を押して強制終了する (Ctrl-c)。
ではパソコンから'Hello, EV3'というメッセージをブローカー(を経由してサブスクライバー)に送ってみる。
メッセージは -m
に続けて記述する。
your_pc:~$ mosquitto_pub -h 192.168.2.30 -t 'test' -m 'Hello, EV3!'
EV3側で次のように表示されていればOK。
robot@ev3dev:~$ mosquitto_sub -h 192.168.2.30 -t 'test'
Hello, EV3!
さらに続けていくつかの文字列や数値をメッセージとして送ってみる。 上の矢印キーで以前入力したコマンドが表示されるので、メッセージだけを変更して入力すればよい。
your_pc:~$ mosquitto_pub -h 192.168.2.30 -t 'test' -m 'Hello, EV3!' your_pc:~$ mosquitto_pub -h 192.168.2.30 -t 'test' -m 'abcde' your_pc:~$ mosquitto_pub -h 192.168.2.30 -t 'test' -m '2018'
EV3側で次のように表示されていればOK。
robot@ev3dev:~$ mosquitto_sub -h 192.168.2.30 -t 'test'
Hello, EV3!
abcde
2018
サブスクラブを中断する時は、Controlキーを押しながら c を押す (Ctrl-c)。
【練習問題】 パソコンとEV3の役割を交代させて、上の確認をしなさい (パソコンをサブスクライバーに、EV3をパブリッシャーにする)。
pythonでは、pahoというライブラリを使ってMQTTのメッセージを送受信できる。
基本的には、ブローカーに接続した時に実行される on_connect() という関数と、 メッセージを受け取った時に実行される on_message() という関数を用意すればよい。 これらの関数はコールバック関数と呼ばれる。
パソコンからMQTTでモータを制御する前に、まず、パソコンが出版したメッセージを受け取って表示する、という簡単なスクリプトを作ってみる。
pahoの詳細は https://pypi.python.org/pypi/paho-mqtt/1.2 を参照のこと。
#!/usr/bin/env python3 import paho.mqtt.client as mqtt # MQTTのクライアントライブラリをインポート from ev3dev.ev3 import * from time import sleep ts = TouchSensor('in3') # スクリプトの停止にタッチセンサを使う def on_connect(client, userdata, flags, rc): # ブローカへの接続時に実行されるコールバック関数の定義 print("Connected with result code "+str(rc)) # 正しく接続できれば rc は 0 client.subscribe("ev3/outA") # "ev3/outA"というトピックを購読 def on_message(client, userdata, message): # メッセージの受信時に実行されるコールバック関数の定義 print(message.topic + " " + str(message.payload)) # topicにトピック名、payloadにメッセージが入る def main(): client = mqtt.Client() # MQTTのクライアントのインスタンスを生成 client.on_connect = on_connect # 上で定義した接続時のコールバック関数を渡す client.on_message = on_message # 上で定義した受信時のコールバック関数を渡す client.connect("localhost", 1883, 60) # ブローカ(自分自身)の1883番ポートに接続 (Keep Aliveは60秒) client.loop_start() # メッセージ受信ループの開始 while ts.value() == 0: # タッチセンサが押されていない間、繰り返し sleep(0.01) client.loop_stop() # 受信ループを停止 client.disconnect() # 接続を切断 if __name__ == '__main__': main()
上で使った on_connect()の引数は以下の通り。
上で使ったon_message()の引数は以下の通り。
コールバック関数としては、上記の'on_connect'と'on_message'以外に、 'on_disconnect'、'on_subscribe'、'on_unsubscribe'、'on_publish' などが使える。
上記のスクリプトを適当な名前(例えばmqtt-test.py)で保存して実行してみる。 実行後、しばらくして"Connected with result code 0"が表示されればOK。
robot@ev3dev:~$ ./mqtt-test.py
Connected with result code 0
この状態でパソコンからメッセージをパブリッシュしてみる (将来的にはduty_cycle_spの値をメッセージとして送る予定)。
your_pc:~$ mosquitto_pub -h 192.168.2.30 -t 'ev3/outA' -m '50' your_pc:~$ mosquitto_pub -h 192.168.2.30 -t 'ev3/outA' -m '-30'
パソコン側でパブリッシュする度にEV3側でメッセージが表示されればOK。
robot@ev3dev:~$ ./mqtt-test.py
Connected with result code 0
ev3/outA b'50'
ev3/outA b'-30'
受信するメッセージが文字列ではなくて「バイト列」であることに注意する。
前のプログラムが正しく動いたら、パソコン側でモータの速度を指定できるようにプログラムを変更する。
#!/usr/bin/env python3 import paho.mqtt.client as mqtt from ev3dev.ev3 import * from time import sleep ts = TouchSensor('in3') m = LargeMotor('outA') # 動作確認用のモータ m.reset() # モータをリセット def on_connect(client, userdata, flags, rc): print("Connected with result code "+str(rc)) client.subscribe("ev3/outA") m.run_direct() # モータの属性変更が即時に反映されるモードで回転 def on_message(client, userdata, message): print(message.topic + " " + str(message.payload)) m.duty_cycle_sp = int(message.payload) # 整数型に変換してから代入 def main(): client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message client.connect("localhost", 1883, 60) client.loop_start() while ts.value() == 0: sleep(0.01) client.loop_stop() client.disconnect() mL.stop() # モータを停止 mR.stop() # if __name__ == '__main__': main()
スクリプトを実行して、パソコンからメッセージを送ってみる。 指定した duty_cycle_sp でモータが回転すればOK。
【練習問題】 上のプログラムを変更して、左右のモータのスピードを同時に指定できるようにしなさい。
(ヒント)指定したい左右のモータのduty_cycle_spを'30,-40'のようなメッセージで送る。
そして得られたメッセージを dc = message.payload.decode('utf-8').split(',')
のようにして、バイト列から文字列へ、文字列をさらに','で分割してリストへ変換し、最後に
int(dc[0]) や int(dc[1])のように整数型に変換すれば、一つのトピックを購読するだけでよい。
これまでの例では、パソコンとEV3の間でメッセージのやり取りをしたが、 EV3とEV3の間の通信も全く同じと言って良い。
以下の例は、別のEV3のモータをアクセルのように使用して、 これまで使っきたEV3をコントロールする例である。 動く方(コントロールされる側)のEV3のスクリプトは以前のものをそのまま使う。
以下はコントローラ側のプログラム例。
#!/usr/bin/env python3 import paho.mqtt.client as mqtt # pahoのライブラリをインポート from ev3dev.ev3 import * from time import sleep ts = TouchSensor('in3') # プログラム停止用のボタンとして使用 m = LargeMotor('outA') # コントローラとして使うモータ m.reset() def main(): duty_cycle_remote = 0 # 相手側モータのduty_cycle_spとして送信する値を格納するための変数 client = mqtt.Client() # MQTTのクライアントを生成 client.connect("192.168.2.30",1883, 60) # ブローカ(192.168.2.30)の1883番portに接続 # (60秒以上接続がないと切断) while ts.value() == 0: # タッチセンサが押されていない間だけ繰り返し pos = m.position # 現在のモータのポジションを取得 if pos > 90: # ポジションからduty_cycle_remoteの値を計算する duty_cycle_remote = 100 # 90度以上で100、-90度以下で-100 となるようにした elif pos < -90: duty_cycle_remote = -100 else: duty_cycle_remote = pos*100/90 client.publish("ev3/outA", duty_cycle_remote) # "ev3/outA"というトピックでブローカに送る time.sleep(0.01) client.disconnect() # 切断 if __name__ == '__main__': main()
【練習問題】 上の例を参考にして、コントローラ側に左右のモータを付け、それを使ってリモート側の左右のモータを独立に制御できるようにしなさい。