M5Stackとクラウドを直接連携してみる（Azure IoT hub入門）

　エッジデバイスをクラウドへつなぐ方法はいろいろとあると思います。今回はクラウドサービスを直結する方法を試しました。

　クラウドサービスにはMicroSoftの[Azure](https://azure.microsoft.com/ja-jp/)の「**Azure IoT Hub**」を使用しました。こちらとM5Stack（ESP32デバイスなら何でもOK?）を連携する記事を見つけたので、[こちら](https://docs.microsoft.com/en-us/samples/azure-samples/esp32-iot-devkit-get-started/sample/)を参考に進めていきます。

やることは次の３つです。

1. 開発環境構築
2. Azure IoT Hubの準備
3. M5Stackからデータを送る

#用意するもの
- M5Stack（ESP32デバイス）
- Windows10 or MAC OS 10（今回はWindows）
- Azureのアカウント（サブスクリプション） 
- Arduino IDE
- Visual Studio Code

#１．開発環境構築
　ESP32の開発環境をVisual Studio Code（VS Code）上に作っていきます。本家の解説にはArduino IDEとVisual Studio Codeのインストールが書かれています。私の場合は用意が済んでいたので省きました。

##1-1.VS CODEの拡張機能「Azure IoT Workbench」をインストール
　本家の解説でAzure IoT Hubが推奨されていたので拡張機能から検索してインストールします。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/098802ff-e7c7-d7b8-d553-14b8b5510641.png)

##1-2.VS CodeにArduino IDEの設定をする
　VS Codeの、File > Preference > Settingsを開きます。**Azure Functions Configuration**のArduino設定を開きます。（本家の説明では分かりづらかった...。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/b44f60af-674a-74c3-0b0e-34123b28b6ce.png)

　setting.jsonを開いて、次のようにします。Arduinoは実態があるパスに読み替えてください。
・Windowsの場合

```json
{
    "arduino.path": "C:\\Program Files (x86)\\Arduino",
    "arduino.additionalUrls": "https://dl.espressif.com/dl/package_esp32_index.json",
}
```

・MACの場合

```json
{
  "arduino.path": "/Application",
  "arduino.additionalUrls": "https://dl.espressif.com/dl/package_esp32_index.json"
}
```

・Ubuntuの場合

```json
{
  "arduino.path": "/home/{username}/Downloads/arduino-1.8.5",
  "arduino.additionalUrls": "https://dl.espressif.com/dl/package_esp32_index.json"
}
```

ここで、”arduino.path”がハイライトされておらず、認識されていない状況が発生しました。どうやら「**Visual Studio Code extension for Arduino**」が必要そうなので、VS Codeの拡張機能一覧から追加します。

**注意！**
似たようなのがいますが、間違えずに！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/6cfe4e4c-9f01-28cd-ca7b-440ff4aae4d0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/777cb81f-41c5-8850-d494-08cf40e4a5db.png)

##1-3.Arduino IDEのボードマネージャにESP32を設定
　キーボードの「F1」を押してコマンドパレットを開き、次のコマンドを入力してEnterします。

```
Arduino: Board Manager
```

　次のような画面が表示されるので、ESP32と入力し、最新バージョンのボードファイルをインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/0327d6f7-9ca5-3b9d-9497-0d143aedb8c5.png)

　インストールに成功すると次のようにメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/5ee4b457-858a-6137-ca86-0546ab029660.png)


#２．Azure IoT Hubの準備
　それではAzure側の準備を進めていきます。次の２点が前提です。

- Azureのアカウント（サブスクリプション）を持っている
- Azure IoT Hubのリソースは作成していない

##2-1. Azure IoT Hubと連携するサンプルコードを開く
　キーボードのF1キーを押し、コマンドパレットを表示します。次のように入力し、「Open Examples...」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/30d55ada-91b5-4a7e-0e7e-aaa5133328d7.png)

　次の図のようにESP32のサンプルコードを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/56979391-d5df-e1db-e7bd-5e08298960c2.png)

　ESP32のサンプルコード一覧が次のように表示されるので、「ESP32 Get Started」の「Open Sample」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/cece0eed-9807-f949-78da-7b9984180ac7.png)

##2-2. Azure IoT Hubの設定
　Azure IoT Hubの設定をVS Codeから進めます。コマンドパレットを開き、次のようにプロビジョンコマンドを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/4e17720c-2207-e468-9251-b2fc38ba531e.png)

　ブラウザが自動で立ち上がるので、Azureアカウントの認証を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/8db751a5-1175-ee1a-b894-6716269d4ee3.png)

　Azure PortalからすでにAzure IoT Hubのリソースを作成している場合、ここでの操作が異なるようです。
今回は、Azure IoT Hubのリソースを作成していない状態で進めます。

　前の手順からの続きで、サブスクリプションを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/166564ef-95a3-a9b2-d7c9-563afd625481.png)

　次にリソースグループ名を決めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/1b9f1b48-90ee-0ff4-9ef5-40a9f2aab37f.png)

　今回は次のように分かりやすい名前にしました。決めたらEnterを押して次に進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/7c96c404-dacd-dfec-c506-98312cb0aa3f.png)

　次にリソースグループのロケーションを決めます。特に気にせずJpanならいいかと思い、今回は次のように選択しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/f1ec6a1d-a68e-b9ae-6e5c-28d682a3ce50.png)

　次のようになるので、選択してEnterを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/e7cff392-83fc-a1cb-155c-982b08f85d94.png)

　IoT Hubの設定を新規に作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/79c32d75-8a34-199d-299a-0c297ec97011.png)

 IoT Hubのロケーションを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/938122da-5fbf-2b10-9b74-31c55dc5e77a.png)

　IoT Hubのスケール（料金体系）を選択します。今回は無料枠を選択しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/9c62d85d-53c8-75a9-c4dc-37458f5c103f.png)

　次にIoT Hubの名前を付けます。今回は次のようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/ed5bda7a-6725-598f-cf8a-2b5c6e676d14.png)

　ターミナルのOUTPUTコンソールを見ると。Azure IoT Hubのリソースを作成している状態を確認できます。
リソースの作成まで時間が少々かかるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/b8e56359-054f-fa77-2d2c-120fe08f64fe.png)

　次のように表示されるので、選択してEnterを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/264aa572-d3fe-9262-ea4a-b1f00651d009.png)

　プロビジョン（準備）を行うデバイスを新規作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/4698b259-5f7d-00f1-c0d2-98d0e6a70a44.png)
　
　分かりやすい名前を入力し、Enterを押します。
次のように、TerminalのOUTPUTにkey情報が表示されれば完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/c54b695c-b022-1784-ef3e-3e5cc670067f.png)


#３．M5Stackの準備
 サンプルコードの足りない個所を埋めていきます。

##3-1. Wi-Fi設定を追記する
　サンプルコードの１１行目辺りに次のような記述があるので、M5Stackが接続するWi-Fiのアクセスポイント情報を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/e46b2f3c-1077-9b0a-8329-7875c8406c17.png)

##3-2.認証キーを追記する
　コマンドパレットを開き、次のようにDevice Settingsを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/d9012823-a524-9e16-a869-8cb70cfd0738.png)

　次のようなが麺が表示されるので、Copy～を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/bea386e9-1065-e3c3-4567-71b52eab962b.png)

　ここで、次のようなエラーが発生しました。

```
System Error:  Failed to get Azure configuration iotHubDeviceConnectionString.}
```

または次のエラー。

```
Command failed: c:\Users\ユーザ名\.vscode\extensions\vsciot-vscode.vscode-iot-workbench-0.14.0\fallbacks\windows\clipboard_x86_64.exe --copy
```

　回避策として、コマンドパレットから次のように「Get Device info」を実行。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/83f2498f-bcfc-0697-6498-a43a603e3a82.png)

　ターミナルの最下行に表示される「connectionString」の値をコピーします。そして、ソースコードの「connectionString」に追記します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/2ac2faff-f22f-9d7b-8044-eaceaddef9f1.png)

3-3. M5Stackへの書き込み
　M5Stackを接続し、動作確認していきます。

　コマンドパレットを開き、Upload Device Codeを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/46b2b029-62f6-6269-d45e-7810a1dc0323.png)

　これでエラーが発生しましたので、トラブルシューティングとして残します。

　まずはコマンドパレットを開き、Arduino Borad Configを選択します。
　次のようにボードをを選択できているか確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/0e2bd86c-4663-db9f-908f-1074488a2373.png)

　次のようにProgrammerが選択されていない場合、
[image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/782874ba-8eda-3442-7884-95c03c9a6689.png)

　Arduino Board configからprogrammerをUSBaspに設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/39d6b714-aee8-fc74-1d27-e606000eafe7.png)

　書き込みに成功すると、次のメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/32d8058a-e310-f8f4-e02c-bb16567cef19.png)

##3-4.動作確認

　シリアルモニタを開きます。コマンドパレットから次のようにコマンドを実行するか、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/c0aa59a2-9746-9cc5-2c2f-ae12ff72c619.png)

　次のように画面下部のコネクトボタンを押すとシリアルモニタを開くことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/d4a746ff-9bd9-3568-5e03-8051a12b46dc.png)
　
　うまくプログラムが動いていると、１秒おきくらいにAzure ioT Hubへデータを送信している様子が見られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/acfc1521-54b5-efec-fea6-db582dedef6c.png)

　次に、Azureからデータを送信して確認します。Azure Porttalを開き、Azure ioT Hubのリーソースを開いてIoTデバイスを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/3bc3e67a-2600-10a2-f68a-6b407f37f7c3.png)

　「デバイスへのメッセージ」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/18980214-6850-32cc-1239-cc449b00740d.png)

　メッセージの本文を書いて送信します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/0eed04ca-42ec-e38d-3f46-ea1ae8ccd678.png)

　VS Codeのターミナルを見ると次のように送信したデータが表示されていれば成功です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183282/0457bb05-7108-977c-abf0-f08aaa5da8ad.png)

#おわりに
　ここまで読んでくださいましてありがとうございます。お疲れさまでした。

　ネットに情報が少なく、ここまで動かすのはとても苦労しました(~_~;)

　M5Stackをクラウドで監視できる等になると、センサー端末をばらまいて広域のセンシングが可能となりそうですね。

#参考
[Micro Soft Learn Azure IoT Hub](https://docs.microsoft.com/ja-jp/learn/modules/introduction-to-iot-hub/)
[Azure ESP32 IoT DevKit Get Started](https://docs.microsoft.com/en-us/samples/azure-samples/esp32-iot-devkit-get-started/sample/)
[IoT Hub で device-to-cloud および cloud-to-device メッセージを送信する](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-messaging#message-format)
[Run a simple C sample on M5Stack IoT Kit device running RTOS](https://github.com/Azure/azure-iot-device-ecosystem/blob/master/get_started/rtos-m5stack-c.md)

#ソースコード

```c++:demo.ino
/**
 * A simple Azure IoT example for sending telemetry to Iot Hub.
 */

#include <WiFi.h>
#include "Esp32MQTTClient.h"

#define INTERVAL 10000
#define MESSAGE_MAX_LEN 256
// Please input the SSID and password of WiFi
const char *ssid = "";            // Wi-FiのSSID
const char *password = "";              // Wi-Fiのパスワード

/*String containing Hostname, Device Id & Device Key in the format:                         */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessKey=<device_key>"                */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessSignature=<device_sas_token>"    */
static const char* connectionString = "";
const char *messageData = "{\"messageId\":%d, \"Temperature\":%f, \"Humidity\":%f}";
static bool hasIoTHub = false;
static bool hasWifi = false;
int messageCount = 1;
static bool messageSending = true;
static uint64_t send_interval_ms;

static void SendConfirmationCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result)
{
  if (result == IOTHUB_CLIENT_CONFIRMATION_OK)
  {
    Serial.println("Send Confirmation Callback finished.");
  }
}

static void MessageCallback(const char* payLoad, int size)
{
  Serial.println("Message callback:");
  Serial.println(payLoad);
}

static void DeviceTwinCallback(DEVICE_TWIN_UPDATE_STATE updateState, const unsigned char *payLoad, int size)
{
  char *temp = (char *)malloc(size + 1);
  if (temp == NULL)
  {
    return;
  }
  memcpy(temp, payLoad, size);
  temp[size] = '\0';
  // Display Twin message.
  Serial.println(temp);
  free(temp);
}

static int  DeviceMethodCallback(const char *methodName, const unsigned char *payload, int size, unsigned char **response, int *response_size)
{
  LogInfo("Try to invoke method %s", methodName);
  const char *responseMessage = "\"Successfully invoke device method\"";
  int result = 200;

  if (strcmp(methodName, "start") == 0)
  {
    LogInfo("Start sending temperature and humidity data");
    messageSending = true;
  }
  else if (strcmp(methodName, "stop") == 0)
  {
    LogInfo("Stop sending temperature and humidity data");
    messageSending = false;
  }
  else
  {
    LogInfo("No method %s found", methodName);
    responseMessage = "\"No method found\"";
    result = 404;
  }

  *response_size = strlen(responseMessage) + 1;
  *response = (unsigned char *)strdup(responseMessage);

  return result;
}



void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 Device");
  Serial.println("Initializing...");
  Serial.println(" > WiFi");
  Serial.println("Starting connecting WiFi.");

  delay(10);
  WiFi.mode(WIFI_AP);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    hasWifi = false;
  }
  hasWifi = true;
  
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println(" > IoT Hub");
  if (!Esp32MQTTClient_Init((const uint8_t*)connectionString, true))
  {
    hasIoTHub = false;
    Serial.println("Initializing IoT hub failed.");
    return;
  }
  hasIoTHub = true;
  Esp32MQTTClient_SetSendConfirmationCallback(SendConfirmationCallback);
  Esp32MQTTClient_SetMessageCallback(MessageCallback);
  Esp32MQTTClient_SetDeviceTwinCallback(DeviceTwinCallback);
  Esp32MQTTClient_SetDeviceMethodCallback(DeviceMethodCallback);
  Serial.println("Start sending events.");
  randomSeed(analogRead(0));
  send_interval_ms = millis();

}

void loop() {
if (hasWifi && hasIoTHub)
  {
    if (messageSending && 
        (int)(millis() - send_interval_ms) >= INTERVAL)
    {
      // Send teperature data
      char messagePayload[MESSAGE_MAX_LEN];
      float temperature = (float)random(0,500)/10;
      float humidity = (float)random(0, 1000)/10;
      snprintf(messagePayload, MESSAGE_MAX_LEN, messageData, messageCount++, temperature, humidity);
      Serial.println(messagePayload);
      EVENT_INSTANCE* message = Esp32MQTTClient_Event_Generate(messagePayload, MESSAGE);
      Esp32MQTTClient_SendEventInstance(message);
      send_interval_ms = millis();
    }
    else
    {
      Esp32MQTTClient_Check();
    }
  }
  delay(10);
}
```