EV3とPCで相互通信してみる!(MQTT)

LEGOで2*2*2ルーブックキューブをカラーセンサーで色を読み取って自動で揃えてくれる機械を作成してる途中のこと。

# はじめに
EV3上で2*2*2ルービックキューブをIDA*アルゴリズムで解くプログラム動かしてみました。が、EV3のCPU性能の低さ(と自分のアルゴリズム理解度の低さ)のせいで9手で解けるルービックキューブを解くのに10分近くかかったので、MQTTを用いて、

1. EV3がルービックキューブの色を読み、パソコンにルービックキューブの状態を送信する
2. パソコンがルービックキューブの解法を求め、EV3に送信する
3. EV3が解法に基づいてルービックキューブを揃える

という形にしようと思います。
この記事では、「EV3がルービックキューブの状態をパソコンへ送信し、パソコンで解いて、EV3へ解法を送信する」ということまでやります。

# MQTTとは?
MQTTとは、IBMが開発したTCP/IPをベースにした軽量な通信プロトコルです。軽量なので、IoT機器によく利用されているようです。
Publisher(送信者)、Broker(仲介者)、Subscriber(受信者)の3つの役割に分かれて通信を行います。まず、Subscriberが受信したい情報の見出しを決めて待機します。次に、Publisherが送信したい情報に見出しを付けて、一旦Brokerに送ります。その後、その情報をBrokerがその見出しの情報を受信しようとしているSubscriberへ送信します。


# PC・EV3のスペックなど
PC
OS : Windows10
CPU : Intel Core i5-8600K @ 3.60GHz
RAM : 16GB
Python : 3.8.0
(pipを使えるようにしておいてください)

EV3
OS : EV3 MicroPython (正式名称かどうかは不明)
CPU : ARM9 @ 300MHz
RAM : 64MB
Python : 3.5.3

EV3 MicroPythonのDL・主な使用方法は[ここから](https://education.lego.com/ja-jp/support/mindstorms-ev3/python-for-ev3)
ちなみにこのEV3 MicroPythonはev3devにPybricks MicroPythonランタイムライブラリを乗せたものなので、ev3devの機能をそのまま使えます。

# 準備
基本的に、MQTTを使って機器同士を通信させるためにはその機器をインターネットにつなぐ必要があるのですが、そもそもEV3にWi-Fiモジュールはついてないので、今回はPCとEV3を直接USBケーブルで接続して通信します。
インターネットを使って通信させる場合は別途Wi-Fiドングルを購入する必要があります。

###SSH接続
EV3とPCをケーブルでつないだら、次はPCからEV3にSSHでログインします。LinuxやmacOSを使用している方はターミナルからsshコマンドでログインできます(デフォルトのユーザーIDは'robot'、パスワードは'maker'です)。Windowsの方はTeraTermというソフトを使いましょう(Windows 10 バージョン 1903 以降ならば「Windows Terminal」というソフトを用いるとsshコマンドで接続できます)。

TeraTermを立ち上げると以下のウィンドウが出ると思います。
![TeraTerm-新しい接続.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550250/c081817c-d0a0-8479-a71d-6cbab7b8c991.jpeg)
「ホスト」のところには`robot@ev3dev.local`を入力して「OK」。インターネット接続で行う場合は、`ev3dev.local`の部分をIPアドレスにすればOKです(`robot@192.168.xx.xx`みたいな感じ)。

次に、初めて接続するときだけ「セキュリティ警告」なるウィンドウが出てくるため、そのまま「続行」を選択します。

するとパスワードを聞かれるので、'maker'と入力して、「OK」を選択します。
![TeraTerm-SSH認証.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550250/5f11d7ef-7271-2998-1dc3-c083b653abbb.jpeg)

以下のように表示されればSSH接続完了です。
<pre><code>
Linux ev3dev 4.14.96-ev3dev-2.3.2-ev3 #1 PREEMPT Sun Jan 27 21:27:35 CST 2019 armv5tejl```
             _____     _
   _____   _|___ /  __| | _____   __
  / _ \ \ / / |_ \ / _` |/ _ \ \ / /
 |  __/\ V / ___) | (_| |  __/\ V /
  \___| \_/ |____/ \__,_|\___| \_/
```
Debian stretch on LEGO MINDSTORMS EV3!
Last login: (日付と時刻) from ...
robot@ev3dev:~$ 
</pre></code>

###Brokerのインストール
EV3とPCをMQTT通信するためにブローカーをインストールします。mosquittoを使います。PC、EV3のどちらに入れてもOKですが、今回はEV3にインストールします。
以下のコマンドを実行します。
<pre><code>
robot@ev3dev:~$ sudo apt-get update
robot@ev3dev:~$ sudo apt-get install mosquitto
</pre></code>

インストールできたら、以下のコマンドでブローカーが起動しているかを確認しましょう。
<pre><code>
robot@ev3dev:~$ sudo service mosquitto status
● mosquitto.service - LSB: mosquitto MQTT v3.1 message broker
   Loaded: loaded (/etc/init.d/mosquitto; generated; vendor preset: enabled)
   Active: active (running) since (日付と時刻)
     Docs: man:systemd-sysv-generator(8)
  Process: 1282 ExecStart=/etc/init.d/mosquitto start (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/mosquitto.service
           mq1289 /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf

(日付と時刻) ev3dev systemd[1]: Starting LSB: mosquitto MQTT v3.1 message broker...
(日付と時刻) ev3dev mosquitto[1282]: Starting network daemon:: mosquitto.
(日付と時刻) ev3dev systemd[1]: Started LSB: mosquitto MQTT v3.1 message broker.
</pre></code>

●と`active (running)`の部分が緑色になっていれば正しく起動しています。デフォルトのポート番号は1883番です

もし以下のように表示されたら、起動していません。
<pre><code>
robot@ev3dev:~$ sudo service mosquitto status
● mosquitto.service - LSB: mosquitto MQTT v3.1 message broker
   Loaded: loaded (/etc/init.d/mosquitto; generated; vendor preset: enabled)
   Active: inactive (dead)
     Docs: man:systemd-sysv-generator(8)
</pre></code>

ですので以下のコマンドを実行してもう一度起動しているか確かめてください。
<pre><code>
robot@ev3dev:~$ sudo service mosquitto start
</pre></code>

###ライブラリのインストール
次にPythonからMQTTを扱うためにライブラリをインストールします。EV3で以下のコマンドを実行してください。
<pre><code>
robot@ev3dev:~$ sudo pip3 install paho-mqtt
</pre></code>

また、PCにも同じようにインストールしてください。
<pre><code>
```C:\Users\(ユーザー名)>pip install paho-mqtt```
</pre></code>

これで準備完了です。

#EV3側のプログラム
```python:main.py
#!/usr/bin/env python3

import paho.mqtt.client as mqtt  # MQTTのライブラリをインポート
import subprocess
from time import sleep

get_way = False

def on_connect(client, userdata, flags, rc):     # Brokerへの接続時に実行されるコールバック関数の定義
	print("Connected with result code "+str(rc)) # 正しく接続できれば rc は 0
	client.subscribe("pc/solve_way")             # "pc/solve_way"という見出しを読む

def on_message(client, userdata, message):       # メッセージの受信時に実行されるコールバック関数の定義
	print(message.topic + " " + str(message.payload))  # topicに見出し名、payloadにメッセージが入る
	global get_way
	get_way = True

def main():
	client = mqtt.Client()                # MQTTのクライアントのインスタンスを生成
	client.on_connect = on_connect        # 上で定義した接続時のコールバック関数を渡す
	client.on_message = on_message        # 上で定義した受信時のコールバック関数を渡す

	client.connect("localhost", 1883, 60) # Broker(自分自身)の1883番ポートに接続 (Keep Aliveは60秒)
	
	client.publish("ev3/cube_state", "5,2,6,3,4,7,1,0:0,0,0,0,0,1,2,0") # 第1引数に見出し、第2引数に情報をとる
	# "5,2,6,3,4,7,1,0:0,0,0,0,0,1,2,0"の数字の羅列は、ルービックキューブの状態を表す
	
	print("published")
	client.loop_start()       # メッセージ受信ループの開始

	while not get_way:
		sleep(0.01)

	client.loop_stop()        # 受信ループを停止
	client.disconnect()       # 接続を切断

if __name__ == '__main__':
	main()
```


#PC側のプログラム
```python:pc-main.py
import paho.mqtt.client as mqtt  # MQTTのライブラリをインポート
from time import sleep
import rcSolver as solver        # 2*2*2ルービックキューブを解くプログラム

get_state = False
solve_way = ""

def on_connect(client, userdata, flags, rc):     # Brokerへの接続時に実行されるコールバック関数の定義
	print("Connected with result code "+str(rc)) # 正しく接続できれば rc は 0
	client.subscribe("ev3/cube_state")           # "ev3/cube_state"という見出しを読む

def on_message(client, userdata, message):       # メッセージの受信時に実行されるコールバック関数の定義
	print(message.topic + " " + str(message.payload))  # topicに見出し名、payloadにメッセージが入る
	cpco_s = message.payload.decode("utf-8").split(":")
	cp_s = cpco_s[0].split(",")
	co_s = cpco_s[1].split(",")
	cp = [int(s) for s in cp_s]
	co = [int(s) for s in co_s]
	cube_state = solver.State(cp, co)            # ルービックキューブの状態を格納
	global solve_way
	solve_way = solver.solve(cube_state)         # ルービックキューブを解いて解法を格納
	print("solve way:", solve_way)
	global get_state
	get_state = True

def main():
	client = mqtt.Client()                   # MQTTのクライアントのインスタンスを生成
	client.on_connect = on_connect           # 上で定義した接続時のコールバック関数を渡す
	client.on_message = on_message           # 上で定義した受信時のコールバック関数を渡す

	client.connect("ev3dev.local", 1883, 60) # Broker(EV3)の1883番ポートに接続 (Keep Aliveは60秒)
	client.loop_start()       # メッセージ受信ループの開始

	while not get_state:
		sleep(0.01)

	client.loop_stop()        # 受信ループを停止
	sleep(0.5)                # EV3側のプログラムがBrokerへ接続するため、少し待つ
	
	client.publish("pc/solve_way", solve_way) # 第1引数に見出し、第2引数に情報をとる
	
	print("published")
	client.disconnect()       # 接続を切断

if __name__ == '__main__':
	main()

```

#実行してみる
まずは、PC側から`pc-main.py`を実行します。
<pre><code>
```C:\Users\(ユーザー名)>python pc-main.py```
Connected with result code 0
</pre></code>

`Connected with result code 0`と表示されれば、EV3側の`main.py`も実行します。
<pre><code>
robot@ev3dev:~$ ./main.py
</pre></code>

#結果・まとめ
###PC側
<pre><code>
```C:\Users\(ユーザー名)>python pc-main.py```
Connected with result code 0
ev3/cube_state b'5,2,6,3,4,7,1,0:0,0,0,0,0,1,2,0'
solve time: 3.744987964630127
solve way: U2 R2 U R' U2 R' F2 U2 R'
published
</pre></code>
###EV3側
<pre><code>
robot@ev3dev:~$ ./main.py
published
Connected with result code 0
pc/solve_way b"U2 R2 U R' U2 R' F2 U2 R'"
</pre></code>

PCを使えば、9手で解けるルービックキューブを4秒弱で解け、しっかりその結果がEV3のほうにも表示されていますね！

#参考文献
[EV3 - ロボティクス入門ゼミ](http://yakushi.shinshu-u.ac.jp/robotics/?EV3)

ルービックキューブの状態の表現方法については、こちらを参考にしました。
[[連載]ルービックキューブを解くプログラムを書いてみよう(前編)](https://qiita.com/7y2n/items/a840e44dba77b1859352)