【IoT初級者へ】M5StickC ではじめる MQTT 通信

この記事は [CPS Lab Advent Calendar 2019](https://adventar.org/calendars/3974) 1日目の記事です．
2日目の記事は [生活の全てをトラッキングする計画v0.1](https://hackmd.io/@anozon/life-all-tracking) です．

ついに始まりました！今年も一番槍を拝命した [Rone Loreto](https://qiita.com/Rone_Loreto) です．

初日なので CPS Lab (実空間コンピューティング研究室) っぽい記事です．

しょっぱなから遅刻投稿ですみません．~~二日酔いが悪い~~

# 背景

せっかちな方は `概要` にお進みください．

---

最近 IoT というワードをよく聞くと思います．
**IoT** (**I**nterNet **o**f **T**hings)，すなわち **モノをインターネットと接続すること** です．
これが台頭してきた背景には『無線通信の高速化』『機器の小型化』『機器のコスト低下』などがあります．特に『機器のコスト低下』は凄まじいです．ESP32を搭載したM5Stackの販売，RISC-V搭載のマイコン登場など……数千円の小型マイコンで色々なことが誰でも出来るようになりました．

黎明期から流行期となった今，様々な場所で IoT を使った機器・製品・システムが見られるようになりました．実際，今年の [CEATEC](https://www.ceatec.com/ja/) でも IoT を活用した展示が数多くされており「どんどん界隈がアツくなっているな！」とテンションが上がりっぱなしでした．

さて，**界隈がアツくなる = はじめる人が増える** です．

はじめた人，はじめようとしている人にお伝えしておきます．
**IoT は初めが一番つまづきやすい** です．
とにかく知らない単語が出てきます．デバッグもソフト・ハードを両方見なればならないので大変です．

本記事は中でも **ネットワークの活用** にスポットを当てています．
「ネットワーク使って〇〇やりたいのに！！」と辛い思いをしている方の助けになれば幸いです．

# 概要

本記事は **MQTT を使ったことがない** 方に

- ネットワーク経由でセンサデータをやり取りするイチ手法の共有をする
    - ThingSpeak によるデータの可視化
- MQTT のイメージを掴んでもらう

ことを主眼としています．

**MQTTをガチでやっている** 方には「それもう知ってるが……」という内容です．
ブラウザバックした方が時間の節約になります．

逆に **MQTTを触り始めたばかり** の方には「そうそうこういうのだよね！」「へぇこういうやり方もあるの」くらいの知見にはなる内容を目指しました．
ザーッと読みすすめることをオススメします．

# MQTT (Message Queue Telemetry Transport) ってナニモノ？

通信プロトコルです．
ざっくり図示すると以下のようになります．

![MQTT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/35d2b1b2-b3ef-37ba-fa07-abdac7aa8412.png)

- Publisher: データを発信するところ
- Subscriber: データを受信するところ
- Broker: サーバみたいなもの．データのやり取りを中継する．


この方式は **Publish/Subscribe Model** と呼ばれています．

オーバヘッドが小さい等の利点から，IoT機器・システムによく用いられています．
もっと詳しく知りたい方は，[MQTT の基本知識 - IBM](https://www.ibm.com/developerworks/jp/iot/library/iot-mqtt-why-good-for-iot/index.html) がオススメです．


# 今回つくるシステム構成

というわけで，今回は MQTT 通信を使ったシステムを作ってみます．

![システム構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/2d6d3ca2-4545-a79c-50f3-2cac8c7a32e5.png)

## 【Pubisher】 M5StickC

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/2a907115-0a77-5218-4731-0f8e67262c79.png)
[M5StickC - スイッチサイエンス](https://www.switch-science.com/catalog/5517/)

ESP32内蔵．Wi-Fi接続ができる．6軸(ジャイロ・加速度)センサが入っている．Grove・ジャンパ線経由でセンサを接続することもできる．液晶も付いている．

なのに2000円で買える……化け物マイコンです．


プログラムは **Arduino IDE** を使って書きます．

## 【Broker&Subcriber】 ThingSpeak
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/6ff20b91-a2d7-9045-d828-f6e012bbcb14.png)

データをクラウドでやり取りできるWebアプリケーションで，MQTT 通信ができます．
今回は簡単にするためコレを用いて，**データの可視化** まで行ないます．

MATLABなどで有名なMathWorks製．
アカウントが必要です．持っていない方は[コチラ](https://thingspeak.com/users/sign_up)から．

[IoT Analytics - ThingSpeak Internet of Things](https://thingspeak.com/)
[ThingSpeak Documentation - MathWorks 日本](https://jp.mathworks.com/help/thingspeak/index.html)


## 今回のゴール
### M5StickC のジャイロ値が ThingSpeak で見られる．
→ ThingSpeakで見られるところまで行けば，あとは任意のセンサに置き換えるだけ．
自分の使いたいセンサでも ThingSpeak を 使った MQTT 通信ができるようになります．

※「自分で Broker 立てたいんだが……」というレベルの方．[Mosquitto](https://mosquitto.org/) を使いましょう．

# 手順

以下の手順をおって進めます．

## 1. M5StickCでジャイロ値を取れるようにする．

まずはジャイロ値を取れるようにしましょう．
ジャイロ値とは，傾きを示した値です．
M5StickC にジャイロ値を取れるセンサが内蔵されているので，使っていきます．


### スケッチの作成
**M5StickC.h** のライブラリをArduinoIDEに追加し，以下のコードを書いたスケッチを作成します．
作成後，ツール > ボード「**M5Stick-C**」を選択し，書き込みしましょう．

#### ジャイロ値を液晶に表示するコード
```c++:m5stickcImuTest.ino
#include <M5StickC.h>

// グローバル変数の設定
float gyroCurtX, gyroCurtY, gyroCurtZ;  // ジャイロ値

// 関数宣言
void currentGyro();

void setup() {
  M5.begin();
  M5.MPU6886.Init();
}

void loop() {
  currentGyro();
  delay(500);     // 0.5秒間隔でloopさせる
}

void currentGyro() { // ジャイロ値を計算し表示する関数
  float gyroRawX, gyroRawY, gyroRawZ; // ジャイロ生データ
  M5.MPU6886.getGyroData(&gyroRawX, &gyroRawY, &gyroRawZ);

  gyroCurtX = gyroRawX * M5.MPU6886.gRes;
  gyroCurtY = gyroRawY * M5.MPU6886.gRes;
  gyroCurtZ = gyroRawZ * M5.MPU6886.gRes;

  M5.Lcd.setCursor(0, 30);
  M5.Lcd.printf("Gyro\nX: %7.2f\nY: %7.2f\nZ: %7.2f\n          mg",
                gyroCurtX, gyroCurtY, gyroCurtZ);
}
```

#### 実行結果

![m5stickcGyro.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/a87d7382-a9a1-83ed-e5ed-20324039d833.gif)

表示できました！
M5StickCを色々な方向に傾けると X, Y, Z の値がそれぞれ変化するはずです．
（机などに置いていても微妙に値が動き続けると思います．この症状を直す手段について書くと，それだけで一つの記事になるので今回はこのまま行きます．）


## 2. ThingSpeak側の設定を行なう．

BrokerとSubscriberを務めるThingSpeakの設定をしていきます．
（アカウント作成の手順は省きます．すみません）


### ログイン

まず[ここ](https://thingspeak.com/login)からログインしましょう．
ログインしたら次のような画面に進むと思います．

<img width="1185" alt="MyChannel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/81659ca7-041f-1d82-c395-228354533365.png">


### チャネル作成

初めて使う人は項目がまっさらなはずなので，チャネルを作成していきます．
左上の「New Channel」をクリックしてください．

<img width="596" alt="NewChannel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/fae240b2-16c0-46de-d86f-04dcadbd45e1.png">

こんな画面が出てきます．
今回は次のように設定します．

![ChannelSetting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/8b42cd3a-2fa7-9e0f-d087-2e4f11c7dec3.png)

- Name: チャネルの名前．今回は「**M5stickC_Gyro**」としました．
- Field1〜3: グラフの名前．順に「gyroX」「gyroY」「gyroZ」とします．それぞれX,Y,Z軸方向のジャイロ値のグラフとします．

入力したら「Save Channel」をクリック．

![ChannelInfo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/3bbe59cb-e2ea-2880-dd2c-ffa444b097fa.png)

これがチャネルの画面です．
グラフが3つ作成されています．
この各グラフに送られてきたジャイロ値が表示されていきます．

この画面の左上に表示されている「**Channel ID**」は後で使います．メモっておいてください．


### Write API Keyの確認

では，他に必要な値を確認していきましょう．
「API Keys」をクリックしてください．

![APIKey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/4744269f-266d-bc55-ffda-8df5a7d40888.png)

ここの「**Write API Key**」の値を後で使います．Read API Key は今回使いません．

### MQTT API Key の確認

次に，画面右上の「Account > My Profile」を開き，

![MQTTKey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/e1e479b7-f4d7-ff34-fd70-ffef75ad8285.png)

「**MQTT API Key**」もメモっておいてください．

以上で準備は完了です！
実際にコードを書いていきましょう．


## 3. M5StickC で取得したジャイロ値を ThingSpeak に MQTT 通信で送る．

いよいよ本題です．
M5StickC を無線接続し，MQTT 通信で ThingSpeak にジャイロ値を送ります．

無線接続するため，Wi-Fi 環境が必要です．
また，Wi-Fi 接続と MQTT 通信を行なえるようにするため，**WiFi.h** と **PubSubClient.h** のライブラリをArduinoIDEに追加してください．

PubSubClientは次のリンクからZIPでダウンロードし「ZIP形式のライブラリをインストール」より追加してください．
[knolleary/pubsubclient: A client library for the Arduino Ethernet Shield that provides support for MQTT.](https://github.com/knolleary/pubsubclient)


### スケッチの作成

WiFi 設定は自分が使う環境のものに書き換えてください．
設定するものはSSIDとWiFiのパスワードです．
なお，M5StickCは5GHz帯(a/ac/ad/ax)のWi-Fi接続に対応していないので，2.4GHz帯(b/g/n)のものを使用してください．

MQTT 設定は先ほどメモった内容にそれぞれ書き換えてください．

たとえば，

- 「Channel Name」: M5stickC_Gyro
- 「MQTT API Key」: HOGEAHOGEAHOGEAB
- 「Write API Key」 : HUGAAHUGAAHUGAB
- 「Channel ID」: 000000

ならば

```c++
// MQTT 設定 (subscriber:ThingSpeak)
const char mqttUserName[] = "M5stickC_Gyro";
const char mqttPass[] = "HOGEAHOGEAHOGEAB";
const char writeApiKey[] = "HUGAAHUGAAHUGAB";
const long channelID = 000000;
```
のようにしてください．

#### ジャイロ値を ThingSpeak に MQTT 通信で送るコード
```c++:m5stickcThingspeakMqtt.ino
#include <M5StickC.h>
#include <WiFi.h>
#include <PubSubClient.h>

// WiFi 設定
const char ssid[] = "yourssid";         // yourssid→使うWiFiのSSIDに書き換える
const char password[] = "yourwifissid"; // yourwifissid→使うWiFiのパスワードに書き換える
WiFiClient wifiClient;

// MQTT 設定 (subscriber:ThingSpeak)
const char mqttUserName[] = "name";   // name→設定した名前に書き換える
const char mqttPass[] = "pass";       // pass→設定されたmqttパスに書き換える
const char writeApiKey[] = "apikey";  // apikey→設定されたwriteAPIキーに書き換える
const long channelID = 000000;        // 000000→設定されたチャネルIDに書き換える

static const char alphanum[] ="0123456789"
                              "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                              "abcdefghijklmnopqrstuvwxyz";  // クライアントIDをランダム生成するための文字列

PubSubClient mqttClient(wifiClient);
const char* server = "mqtt.thingspeak.com";

unsigned long lastConnectionTime = 0; 
const unsigned long postingInterval = 10L * 1000L; // Publish の間隔を10秒にする

// グローバル変数の設定
float gyroCurtX, gyroCurtY, gyroCurtZ;  // ジャイロ値

// 関数宣言
void currentGyro();
void reConnect();
void mqttPublish();

void setup() {
  M5.begin();
  M5.MPU6886.Init();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  mqttClient.setServer(server, 1883); 
}

void loop() {
  reConnect();

  mqttClient.loop();

  // 最後に Publish した時間から postingInterval が経過すると動作する
  if (millis() - lastConnectionTime > postingInterval) {
    currentGyro();
    mqttPublish();
  }
}


/* 
 * ----- 以下loop内で用いる関数 -----
 */
void currentGyro() { // ジャイロ値を計算し表示する関数
  float gyroRawX, gyroRawY, gyroRawZ; // ジャイロ生データ
  M5.MPU6886.getGyroData(&gyroRawX, &gyroRawY, &gyroRawZ);

  gyroCurtX = gyroRawX * M5.MPU6886.gRes;
  gyroCurtY = gyroRawY * M5.MPU6886.gRes;
  gyroCurtZ = gyroRawZ * M5.MPU6886.gRes;

  M5.Lcd.setCursor(0, 30);
  M5.Lcd.printf("Gyro\nX: %7.2f\nY: %7.2f\nZ: %7.2f\n          mg",
                gyroCurtX, gyroCurtY, gyroCurtZ);
}

void reConnect() { // 接続が切れた際に再接続する
  char clientID[10];
  
  // 接続まで繰り返す
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Generate ClientID
    for (int i = 0; i < 8; i++) {
        clientID[i] = alphanum[random(51)];
    }

    // MQTT broker に接続する
    if (mqttClient.connect(clientID,mqttUserName,mqttPass)) {
      Serial.print("Connected with Client ID:  ");
      Serial.print(String(clientID));
      Serial.print(", Username: ");
      Serial.print(mqttUserName);
      Serial.print(" , Passwword: ");
      Serial.println(mqttPass);
    }
    else {
      Serial.print("failed, rc=");
      // http://pubsubclient.knolleary.net/api.html#state に state 一覧が書いてある
      Serial.print(mqttClient.state());
      Serial.println("try again in 5 seconds");
      delay(5000);
    }
  }
}

void mqttPublish() {
  // ThingSpeak に Publish するための文字列データを作成する
  String data = ("field1=" + String(gyroCurtX) + "&field2=" + String(gyroCurtY) + "&field3=" + String(gyroCurtZ));
  
  int length = data.length();
  char msgBuffer[length];
  data.toCharArray(msgBuffer,length+1);
  
  // topic 文字列を作成し ThingSpeak の channel feed にデータを Publish する
  String topicString ="channels/" + String(channelID) + "/publish/" + String(writeApiKey);
  length = topicString.length();
  char topicBuffer[length];
  topicString.toCharArray(topicBuffer, length+1);
  mqttClient.publish(topicBuffer, msgBuffer);

  lastConnectionTime = millis(); // 最後に Publish した時間からのカウント
}
```

#### 実行結果

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218217/8352039b-1e13-c8aa-e507-fda0f25faeb6.png)

できました！
10秒間隔でX, Y, Z 軸のジャイロ値が表示されています．


# まとめ
これで MQTT 通信が一応使えるようになりました．
晴れて皆さんは IoT初心者 → 初級者 へステップアップです．

今回はM5StickC搭載済みのジャイロセンサを用いましたが，勿論他のセンサでも MQTT 通信でデータのやり取りを行なうことができます．

具体的に．

```c++
void mqttPublish() {
  // ThingSpeak に Publish するための文字列データを作成する
  String data = ("field1=" + String(gyroCurtX) + "&field2=" + String(gyroCurtY) + "&field3=" + String(gyroCurtZ));
```

ここの`String(gyroCurtX)`等の`gyroCurtX`を任意センサで取得した値を格納する変数に変更し，`field`数を合わせてやれば良いだけです．fieldについては2つ目以降`&field2=`等と&をつける必要がありますので注意してください．
（勿論任意センサの値を取得するコードは自分で実装する必要があります）

という辺りで，今回は終わりとさせてさせていただきます．
みなさんも快適な MQTT 通信ライフを！

# 備考: M5StickCの開発で役に立つリンク
[m5stack/M5StickC: M5Stack Arduino Library](https://github.com/m5stack/M5StickC)
→公式のGitHubリポジトリ

[M5Stack](https://docs.m5stack.com/#/ja/api)
→上記リポジトリ内にもある公式Doc．若干情報が古い……
　(例: IMUがMPU6886ではなく前のSH200Qについての記述になっている)

[M5StickC非公式日本語リファレンス](https://lang-ship.com/reference/unofficial/M5StickC/)
→非公式のリファレンス．MPU6886に対応している点が良い．

[M5StickC: 総合概要](https://lang-ship.com/reference/M5StickC/latest/)
→詳細が載っている．自分でも網羅し切れていない程に詳しく書かれている．