メインコンテンツまでスキップ

IoTデバイスをクラウドに接続する - Wio Terminal

このレッスンのこの部分では、Wio TerminalをIoT Hubに接続して、テレメトリを送信し、コマンドを受信します。

デバイスをIoT Hubに接続する

次のステップは、デバイスをIoT Hubに接続することです。

タスク - IoT Hubに接続する

  1. VS Codeでsoil-moisture-sensorプロジェクトを開きます。

  2. platformio.iniファイルを開きます。knolleary/PubSubClientライブラリ依存関係を削除します。これはパブリックMQTTブローカーに接続するために使用されていましたが、IoT Hubに接続するためには必要ありません。

  3. 次のライブラリ依存関係を追加します:

    seeed-studio/Seeed Arduino RTC @ 2.0.0
    arduino-libraries/AzureIoTHub @ 1.6.0
    azure/AzureIoTUtility @ 1.6.1
    azure/AzureIoTProtocol_MQTT @ 1.6.0
    azure/AzureIoTProtocol_HTTP @ 1.6.0
    azure/AzureIoTSocket_WiFi @ 1.0.2

    Seeed Arduino RTCライブラリは、Wio Terminalのリアルタイムクロックと対話するためのコードを提供します。残りのライブラリは、IoTデバイスがIoT Hubに接続するためのものです。

  4. platformio.iniファイルの一番下に次の内容を追加します:

    build_flags =
    -DDONT_USE_UPLOADTOBLOB

    これは、Arduino IoT Hubコードをコンパイルする際に必要なコンパイラフラグを設定します。

  5. config.hヘッダーファイルを開きます。すべてのMQTT設定を削除し、デバイス接続文字列のための次の定数を追加します:

    // IoT Hub設定
    const char *CONNECTION_STRING = "<connection string>";

    <connection string>を、以前にコピーしたデバイスの接続文字列に置き換えます。

  6. IoT Hubへの接続は時間ベースのトークンを使用します。これは、IoTデバイスが現在の時間を知る必要があることを意味します。Windows、macOS、Linuxのようなオペレーティングシステムとは異なり、マイクロコントローラーはインターネット経由で自動的に現在の時間を同期しません。これにより、NTPサーバーから現在の時間を取得するコードを追加する必要があります。時間が取得されると、Wio Terminalのリアルタイムクロックに保存され、デバイスが電力を失わない限り、後で正しい時間を要求することができます。次のコードを含むntp.hという新しいファイルを追加します:

    #pragma once

    #include "DateTime.h"
    #include <time.h>
    #include "samd/NTPClientAz.h"
    #include <sys/time.h>

    static void initTime()
    {
    WiFiUDP _udp;
    time_t epochTime = (time_t)-1;
    NTPClientAz ntpClient;

    ntpClient.begin();

    while (true)
    {
    epochTime = ntpClient.getEpochTime("0.pool.ntp.org");

    if (epochTime == (time_t)-1)
    {
    Serial.println("NTPエポック時間の取得に失敗しました!再試行まで2秒待ちます。");
    delay(2000);
    }
    else
    {
    Serial.print("取得したNTPエポック時間は:");

    char buff[32];
    sprintf(buff, "%.f", difftime(epochTime, (time_t)0));
    Serial.println(buff);
    break;
    }
    }

    ntpClient.end();

    struct timeval tv;
    tv.tv_sec = epochTime;
    tv.tv_usec = 0;

    settimeofday(&tv, NULL);
    }

    このコードの詳細はこのレッスンの範囲外です。これは、NTPサーバーから現在の時間を取得し、それを使用してWio Terminalの時計を設定するinitTimeという関数を定義します。

  7. main.cppファイルを開き、PubSubClient.hヘッダーファイル、PubSubClient変数の宣言、reconnectMQTTClientおよびcreateMQTTClientメソッド、およびこれらの変数とメソッドへのすべての呼び出しを含むすべてのMQTTコードを削除します。このファイルには、WiFiに接続し、土壌湿度を取得し、それを含むJSONドキュメントを作成するコードのみが含まれている必要があります。

  8. IoT Hubライブラリのヘッダーファイルと時間を設定するためのヘッダーファイルを含めるために、main.cppファイルの先頭に次の#includeディレクティブを追加します:

    #include <AzureIoTHub.h>
    #include <AzureIoTProtocol_MQTT.h>
    #include <iothubtransportmqtt.h>
    #include "ntp.h"
  9. 現在の時間を設定するために、setup関数の最後に次の呼び出しを追加します:

    initTime();
  10. ファイルの先頭に、インクルードディレクティブのすぐ下に次の変数宣言を追加します:

    IOTHUB_DEVICE_CLIENT_LL_HANDLE _device_ll_handle;

    これは、IoT Hubへの接続のハンドルであるIOTHUB_DEVICE_CLIENT_LL_HANDLEを宣言します。

  11. これの下に次のコードを追加します:

    static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void *user_context)
    {
    if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED)
    {
    Serial.println("デバイスクライアントはiothubに接続されています");
    }
    else
    {
    Serial.println("デバイスクライアントは切断されました");
    }
    }

    これは、接続ステータスが変わったときに呼び出されるコールバック関数を宣言します。ステータスはシリアルポートに送信されます。

  12. これの下に、IoT Hubに接続するための関数を追加します:

    void connectIoTHub()
    {
    IoTHub_Init();

    _device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(CONNECTION_STRING, MQTT_Protocol);

    if (_device_ll_handle == NULL)
    {
    Serial.println("Iothubデバイスの作成に失敗しました。ヒント:接続文字列を確認してください。");
    return;
    }

    IoTHubDeviceClient_LL_SetConnectionStatusCallback(_device_ll_handle, connectionStatusCallback, NULL);
    }

    このコードは、IoT Hubライブラリコードを初期化し、config.hヘッダーファイルの接続文字列を使用して接続を作成します。この接続はMQTTに基づいています。接続に失敗した場合、これはシリアルポートに送信されます - 出力でこれが表示された場合、接続文字列を確認してください。最後に、接続ステータスコールバックが設定されます。

  13. setup関数のinitTime呼び出しの下にこの関数を呼び出します:

    connectIoTHub();
  14. MQTTクライアントと同様に、このコードは単一のスレッドで実行されるため、ハブによって送信されるメッセージとハブに送信されるメッセージを処理する時間が必要です。これを行うために、loop関数の先頭に次のコードを追加します:

    IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
  15. このコードをビルドしてアップロードします。シリアルモニターに接続が表示されます:

    WiFiに接続中..
    接続されました!
    取得したNTPエポック時間は:1619983687
    テレメトリを送信中 {"soil_moisture":391}
    デバイスクライアントはiothubに接続されています

    出力には、NTP時間の取得後、デバイスクライアントの接続が表示されます。接続には数秒かかることがあるため、デバイスが接続されるまでに土壌湿度が出力に表示されることがあります。

    💁 NTPのUNIX時間をより読みやすいバージョンに変換するには、unixtimestamp.comのようなウェブサイトを使用できます。

テレメトリを送信する

デバイスが接続されたので、MQTTブローカーの代わりにIoT Hubにテレメトリを送信できます。

タスク - テレメトリを送信する

  1. setup関数の上に次の関数を追加します:

    void sendTelemetry(const char *telemetry)
    {
    IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(telemetry);
    IoTHubDeviceClient_LL_SendEventAsync(_device_ll_handle, message_handle, NULL, NULL);
    IoTHubMessage_Destroy(message_handle);
    }

    このコードは、パラメータとして渡された文字列からIoT Hubメッセージを作成し、それをハブに送信し、メッセージオブジェクトをクリーンアップします。

  2. テレメトリがシリアルポートに送信された行の直後に、loop関数でこのコードを呼び出します:

    sendTelemetry(telemetry.c_str());

コマンドを処理する

デバイスは、リレーを制御するためにサーバーコードからのコマンドを処理する必要があります。これは、直接メソッドリクエストとして送信されます。

タスク - 直接メソッドリクエストを処理する

  1. connectIoTHub関数の前に次のコードを追加します:

    int directMethodCallback(const char *method_name, const unsigned char *payload, size_t size, unsigned char **response, size_t *response_size, void *userContextCallback)
    {
    Serial.printf("直接メソッドを受信しました %s\r\n", method_name);

    if (strcmp(method_name, "relay_on") == 0)
    {
    digitalWrite(PIN_WIRE_SCL, HIGH);
    }
    else if (strcmp(method_name, "relay_off") == 0)
    {
    digitalWrite(PIN_WIRE_SCL, LOW);
    }
    }

    このコードは、直接メソッドリクエストを受信したときにIoT Hubライブラリが呼び出すことができるコールバックメソッドを定義します。リクエストされたメソッドはmethod_nameパラメータに送信されます。この関数は、シリアルポートに呼び出されたメソッドを印刷し、メソッド名に応じてリレーをオンまたはオフにします。

    💁 これは、リレーの希望する状態をメソッドリクエストと共に渡すペイロードに渡し、payloadパラメータから利用可能にする単一の直接メソッドリクエストとしても実装できます。

  2. directMethodCallback関数の最後に次のコードを追加します:

    char resultBuff[16];
    sprintf(resultBuff, "{\"Result\":\"\"}");
    *response_size = strlen(resultBuff);
    *response = (unsigned char *)malloc(*response_size);
    memcpy(*response, resultBuff, *response_size);

    return IOTHUB_CLIENT_OK;

    直接メソッドリクエストには応答が必要であり、応答はテキストとしての応答と戻りコードの2つの部分で構成されます。このコードは、次のJSONドキュメントとして結果を作成します:

    {
    "Result": ""
    }

    これがresponseパラメータにコピーされ、応答のサイズがresponse_sizeパラメータに設定されます。このコードは、メソッドが正しく処理されたことを示すためにIOTHUB_CLIENT_OKを返します。

  3. connectIoTHub関数の最後に次のコードを追加して、コールバックを接続します:

    IoTHubClient_LL_SetDeviceMethodCallback(_device_ll_handle, directMethodCallback, NULL);
  4. loop関数は、IoT Hubによって送信されたイベントを処理するためにIoTHubDeviceClient_LL_DoWork関数を呼び出します。これはdelayのために10秒ごとにしか呼び出されないため、直接メソッドは10秒ごとにしか処理されません。これをより効率的にするために、10秒の遅延を多くの短い遅延として実装し、毎回IoTHubDeviceClient_LL_DoWorkを呼び出すことができます。これを行うために、loop関数の上に次のコードを追加します:

    void work_delay(int delay_time)
    {
    int current = 0;
    do
    {
    IoTHubDeviceClient_LL_DoWork(_device_ll_handle);
    delay(100);
    current += 100;
    } while (current < delay_time);
    }

    このコードは、IoTHubDeviceClient_LL_DoWorkを呼び出し、毎回100ms遅延させることを繰り返します。これを、delay_timeパラメータの時間だけ遅延させるために必要な回数だけ行います。これにより、デバイスは直接メソッドリクエストを処理するために最大100ms待機します。

  5. loop関数で、IoTHubDeviceClient_LL_DoWorkの呼び出しを削除し、delay(10000)の呼び出しを次のように置き換えて、この新しい関数を呼び出します:

    work_delay(10000);

💁 このコードはcode/wio-terminalフォルダーにあります。

😀 あなたの土壌湿度センサープログラムはIoT Hubに接続されています!