セクションの複数ページをまとめています。 印刷またはPDF形式で保存...

もとのページに戻る

2024-11-14 現在

REST API の使用

mwings-v1.1.3
    無線 LAN 子機として振る舞い、Web 上のモックサーバ httpbin.org へ受信したパケットのデータを送信するサンプルスケッチ spot-httpbin の解説です。

    ソースコードの入手

    GitHub リポジトリ monowireless/spot-httpbin から入手できます。

    システムの概要

    spot-httpbin は、TWELITE 親機が受信したデータの一部と NTP による現在時刻を HTTP GET リクエストとしてモックサーバへ送信し、そのレスポンスをシリアルモニタへ表示します。

    開発に必要なもの

    環境整備

    IDE とツールチェインの導入

    Arduino IDE 1.x による開発環境の構築方法 をご覧ください。

    ライブラリの導入

    このサンプルでは、依存するライブラリをはじめから同梱しています。

    プロジェクトファイルの入手

    1. GitHub (monowireless/spot-httpbin) から Zip ファイルをダウンロードします
    2. Zip ファイルを展開し、フォルダ名を spot-httpbin-main から spot-httpbin に変更します
    3. Arduino のスケッチブックの保存場所(Arduino IDE 環境設定に記載。例:C:\Users\foo\Documents\Arduino)に spot-httpbin フォルダを配置します

    ユーザ設定の変更

    Arduino IDE 上部のタブから config.h を開き、Wi-Fi の SSID と パスワードを設定してください。WPA2-PSK ネットワークを想定しています。また、ルート証明書も登録してください。ルート証明書は、Chrome などのウェブブラウザの各ページに対するセキュリティ画面から入手できます。

    プロジェクトファイルの書き込み方法

    ESP32 へのスケッチの書き込み方法 をご覧ください。

    スケッチ

    Arduino スケッチ spot-httpbin.ino および config.h の解説です。

    ライブラリのインクルード

    Arduino および ESP32 公式ライブラリ

    4-6行目では、Arduino および ESP32 の公式ライブラリをインクルードしています。

    #include <Arduino.h>
    #include <WiFiClientSecure.h>
    #include <WiFiUdp.h>
    ヘッダファイル内容備考
    Arduino.hArduino の基本ライブラリ省略できる場合もあるが念のため記載
    WiFiClientSecure.hESP32 で SSL通信を行う
    WiFiUdp.hUDP 通信を行うNTP に必要

    サードパーティのライブラリ

    9-10行目では、同梱されたサードパーティのライブラリをインクルードしています。

    #include "src/NTPClient/NTPClient.h"
    #include "src/Time/TimeLib.h"
    ヘッダファイル内容備考
    NTPClient.hNTP サーバへアクセスする
    TimeLib.hエポック時間を変換する

    MWings ライブラリ

    13行目では、MWings ライブラリをインクルードしています。

    #include <MWings.h>

    ユーザ設定の定義

    16行目では、config.h をインクルードしています。

    #include "config.h"

    データ型の定義

    19-26行目では、子機から受信したデータを保管しておく構造体の型を定義しています。

    struct DataFromAria {
        uint32_t serialId;
        uint8_t logicalId;
        uint16_t supplyVoltage;
        uint8_t linkQuality;
        int16_t temp100x;
        uint16_t humid100x;
    };
    名称内容
    serialIdシリアルID
    logicalId論理デバイスID
    supplyVoltage電源電圧
    linkQualityLQI
    temp100x100倍された温度
    humid100x100倍された湿度

    ここでは、TWELITE ARIA を使用します。

    config.h

    再起動間隔の定義

    config.h の4行目では、ESP32 の再起動間隔を指定しています。

    const uint32_t REBOOT_INTERVAL = 21600; // seconds
    

    ここでは、21600秒=6時間としています。

    TWELITE 設定の定義

    config.h の7-8行目では、TWELITE SPOT に搭載された TWELITE 親機に適用する設定を定義しています。

    const uint8_t TWE_CH = 18;
    const uint32_t TWE_APPID = 0x67720102;
    名称内容
    TWE_CHTWELITE の 周波数チャネル
    TWE_APPIDTWELITE の アプリケーション ID

    Wi-Fi 設定の定義

    config.h の11-12行目では、TWELITE SPOT に搭載された ESP32 に適用するWi-Fi 設定を定義しています。

    const char* WIFI_SSID = "YOUR SSID";
    const char* WIFI_PASSWORD = "YOUR PASSWORD";
    名称内容
    WIFI_SSID接続するネットワークの SSID
    WIFI_PASSWORD接続するネットワークの パスワード

    ルート証明書

    config.h の14-16行目では、ルート証明書の内容を記述するためのテンプレートを用意しています。

    const char *CA_CERT =
        "-----BEGIN CERTIFICATE-----\n"
        "-----END CERTIFICATE-----\n";

    ルート証明書は、Chrome などのウェブブラウザの各ページに対するセキュリティ画面から入手してください。 すべての行をダブルクォートで囲い、末尾のダブルクォートの前には改行文字 \n を追加する必要があります。

    ホストの設定

    config.h の18-19行目では、ホストの設定を定義しています。

    const char *SERVER_HOST = "www.httpbin.org";
    const uint16_t SERVER_PORT = 443;
    名称内容
    SERVER_HOSTサーバのホスト名
    SERVER_PORTサーバのポート番号

    各種定数の定義

    config.h の21行目からは、各種定数を定義しています。

    const uint32_t NTP_UPDATE_INTERVAL = 10000; // ms
    
    const int QUERIES_MAX_LENGTH = 128;         // bytes (without \0)
    const int32_t CONNECT_TIMEOUT = 10;     // seconds
    const uint32_t RECONNECT_MIN_INTERVAL = 5; // seconds
    // SEND_MIN_INTERVAL must be longer than NTP_UPDATE_INTERVAL
    const uint32_t SEND_MIN_INTERVAL = 10; // seconds
    const uint32_t REQUEST_TIMEOUT = 10;   // seconds
    
    名称内容
    NTP_UPDATE_INTERVALNTP時刻の取得間隔
    QUERIES_MAX_LENGTHクエリ文字列の最大長(ヌル文字含まず)
    CONNECT_TIMEOUTサーバへの接続時のタイムアウト
    RECONNECT_MIN_INTERVALWi-Fiアクセスポイントへ再接続する際の最短間隔
    SEND_MIN_INTERVALリクエスト間隔の最短間隔
    REQUEST_TIMEOUTリクエストからレスポンスまでのタイムアウト

    ピン番号の定義

    29-31行目では、ピン番号を定義しています。

    static const int RST_PIN = 5;
    static const int PRG_PIN = 4;
    static const int LED_PIN = 18;
    名称内容
    RST_PINTWELITE の RST ピンが接続されているピンの番号
    PRG_PINTWELITE の PRG ピンが接続されているピンの番号
    LED_PIN基板上の ESP32 用 LED が接続されているピンの番号

    グローバルオブジェクトの宣言

    34-37行目では、グローバルオブジェクトを宣言しています。

    static WiFiClientSecure client;
    static WiFiUDP ntpUDP;
    static NTPClient timeClient(ntpUDP, "ntp.nict.jp",
                                32400, NTP_UPDATE_INTERVAL); // JST(UTC+9)
    
    名称内容
    clientHTTPS通信のインタフェース
    ntpUDPNTP用のUDP通信のインタフェース
    timeClientNTPのインタフェース

    グローバル変数の宣言

    40-41行目では、グローバル変数を宣言しています。

    static DataFromAria LatestDataFromAria;
    static bool IsThereNewDataFromAria;
    名称内容
    LatestDataFromAriaTWELITE ARIA から受信した最新のデータ
    IsThereNewDataFromAriaTWELITE ARIA から新たなデータを受信したことを示すフラグ

    関数プロトタイプの宣言

    44-56行目では、関数プロトタイプを宣言しています。

    void anotherLoopForTWELITE();
    void anotherLoopForNTP();
    名称内容
    anotherLoopForTWELITETWELITEのデータを処理するためのループ関数
    anotherLoopForNTPNTPで時刻を取得するためのループ関数
    void initTWELITE();
    void initWiFi();
    void initNTP();
    名称内容
    initTWELITETWELITEの初期化関数
    initWiFiWi-Fiの初期化関数
    initNTPNTPの初期化関数
    void onAppAriaPacket(const ParsedAppAriaPacket& packet);
    名称内容
    onAppAriaPacketTWELITE ARIA からデータを受信した際のコールバック関数
    void sendAriaData(const DataFromAria& data)
    名称内容
    sendAriaDataTWELITE ARIAのデータを HTTP GET リクエストにのせて送る関数

    setup()

    59-87行目では、全体の初期化を行います。

    void setup() {
        Serial.begin(115200);
    
        initTWELITE();
        initWiFi();
        initNTP();
    
        // Attach another loop function for TWELITE
        // Note: Core 0 is also used for the WiFi task, which priority is 19 (ESP_TASKD_EVENT_PRIO - 1)
        xTaskCreatePinnedToCore(
            [](void *params) {
                while (true) {
                    anotherLoopForTWELITE();
                    vTaskDelay(1); // IMPORTANT for Watchdog
                }
            },
            "Task for anotherLoopForTWELITE()", 8192, nullptr, 18, nullptr,
            0); // Priority is 18 (lower than WiFi)
        // Attach another loop function for NTP
        xTaskCreatePinnedToCore(
            [](void *params) {
                while (true) {
                    anotherLoopForNTP();
                    vTaskDelay(1); // IMPORTANT for Watchdog
                }
            },
            "Task for anotherLoopForNTP()", 8192, nullptr, 17, nullptr,
            0); // Priority is 17 (lower than WiFi and TWELITE)
    }

    xTaskCreatePinnedToCore() により、loop() とは別のタスクを登録しています。

    下記の部分はキャプチャのない無名関数です。不要なグローバル空間の汚染を避けることができます。

            [](void *params) {
                while (true) {
                    anotherLoopForTWELITE();
                    vTaskDelay(1); // IMPORTANT for Watchdog
                }
            },

    loop()

    90-111行目は、主となるループ処理です。

    HTTP リクエストの処理、Wi-Fi 切断時の再接続処理、定期リセットの処理を行います。

    void loop() {
        static uint32_t lastTimeReconnected = 0;
        if (WiFi.status() == WL_CONNECTED) {
            // Regular operations
            // Check for new data
            if (IsThereNewDataFromAria) {
                IsThereNewDataFromAria = false; // Clear first; data is updated on another thread
                DataFromAria data = LatestDataFromAria; // Now, the buffer is open for incoming data
                sendAriaData(data);
            }
        } else if (millis() - lastTimeReconnected > RECONNECT_MIN_INTERVAL * 1000) {
            // Lost connection, reconnect periodically
            Serial.println("Disconnected. Reconnecting to WiFi...");
            WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
            lastTimeReconnected = millis();
        }
        // Reboot every x interval
        if (millis() > REBOOT_INTERVAL * 1000) {
            Serial.println("Rebooting...");
            ESP.restart();
        }
    }

    anotherLoopForTWELITE()

    114-116行目は、TWELITE のためのループ処理です。

    データの受信と解釈を逐次行うため、ブロッキング処理を含む loop() とは別のタスクとしています。

    void anotherLoopForTWELITE() {
        Twelite.update();
    }

    anotherLoopForNTP()

    117-120行目は、NTP のためのループ処理です。

    こちらについても UDP の通信を行うため、ブロッキング処理を含む loop() とは別のタスクとしています。

    void anotherLoopForNTP() {
        timeClient.update();
        setTime(timeClient.getEpochTime());
    }

    initTWELITE()

    123-130行目は、TWELITE の初期化処理です。

    TWELITE SPOT に搭載された TWELITE を指定された設定で起動し、パケット受信時のコールバック関数を登録しています。

    void initTWELITE() {
        Serial2.begin(115200);
        if (Twelite.begin(Serial2, LED_PIN, RST_PIN, PRG_PIN, TWE_CHANNEL, TWE_APP_ID)) {
            Serial.println("Started TWELITE.");
        }
        // Attach event handlers to process packets
        Twelite.on(onAppAriaPacket);
    }

    initWiFi()

    133-157行目は、Wi-Fi の初期化処理です。

    接続されない場合は、5秒置きに再接続を試みます。

    void initWiFi() {
        Serial.print("\nConnecting to the WiFi network ");
        Serial.print(WIFI_SSID);
        Serial.println("...");
        // Begin
        WiFi.mode(WIFI_STA);
        WiFi.setAutoReconnect(true);
        WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
        // Wait for connection
        Serial.print("Connecting.");
        while (WiFi.status() != WL_CONNECTED) x
            static int count = 0;
            Serial.print('.');
            delay(500);
            // Retry every 5 seconds
            if (count++ % 10 == 0) {
                WiFi.disconnect();
                WiFi.reconnect();
                Serial.print('!');
            }
        }
        Serial.println("\nConnected!");
        // Set Root CA certificate
        client.setCACert(CA_CERT);
    }

    initNTP()

    160-164行目は、NTP の初期化処理です。

    void initNTP() {
        timeClient.begin();
        timeClient.update();
        setTime(timeClient.getEpochTime());
    }

    onAppAriaPacket()

    167-177行目には、TWELITE ARIA からデータを受信した際の処理を記述しています。

    ここでは HTTP の送信処理を行わず、グローバル変数へセットしています。 グローバル変数へセットしたデータは、別のタスクで sendAriaData() によって処理します。

    void onAppAriaPacket(const ParsedAppAriaPacket& packet)
    {
        // Store data
        LatestDataFromAria.serialId = packet.u32SourceSerialId;
        LatestDataFromAria.logicalId = packet.u8SourceLogicalId;
        LatestDataFromAria.supplyVoltage = packet.u16SupplyVoltage;
        LatestDataFromAria.linkQuality = packet.u8Lqi;
        LatestDataFromAria.temp100x = packet.i16Temp100x;
        LatestDataFromAria.humid100x = packet.u16Humid100x;
        IsThereNewDataFromAria = true;
    }

    sendAriaData()

    180-237行目は、TWELITE ARIA のデータを HTTP GET リクエストのクエリ文字列にセットして送信する関数です。

    サーバへの過度な負荷を防ぐため、高頻度でパケットが到着した際には送信をスキップしています。

    void sendAriaData(const DataFromAria& data)
    {
        static uint32_t lastTimeRequested = 0;
        if (millis() - lastTimeRequested > SEND_MIN_INTERVAL * 1000 or lastTimeRequested == 0) {
            Serial.println("Connecting to the server...");
            if (not client.connect(SERVER_HOST, SERVER_PORT, CONNECT_TIMEOUT * 1000)) {
                Serial.println("Connection failed!");
            } else {
                Serial.println("Connected to the server!");
                // Make a query string
                char queries[QUERIES_MAX_LENGTH+1];
                snprintf(queries, sizeof(queries),
                         "datetime=%04d%02d%02d%02d%02d%02d&sid=%X&lid=%d&temp=%d&humid=%d&bat=%d&lqi=%d",
                         // Note that NTP_UPDATE_INTERVAL is set for 10000ms by default; second() delays up to 10s.
                         // To prevent duplication of datetime, SEND_MIN_INTERVAL is set for 10s.
                         year(), month(), day(), hour(), minute(), second(),
                         data.serialId,
                         data.logicalId,
                         data.temp100x,
                         data.humid100x,
                         data.supplyVoltage,
                         data.linkQuality);
    
                // Send a request
                client.println(String("GET https://") +
                               SERVER_HOST +
                               String("/get?") +
                               queries +
                               String(" HTTP/1.1"));
                client.println("Accept: */*");
                client.println(String("Host: ") + SERVER_HOST);
                client.println("Connection: close");
                client.println();
                uint32_t timeSentRequest = millis();
    
                // Handle a response
                while (client.connected()) {
                    String line = client.readStringUntil('\n');
                    if (line == "\r") {
                        Serial.println("Headers received");
                        break;
                    }
                    if (millis() - timeSentRequest > REQUEST_TIMEOUT * 1000) {
                        Serial.println("Request was timed out");
                        break;
                    }
                }
                while (client.available()) {
                    char c = client.read();
                    Serial.write(c);
                }
                client.stop();
            }
            lastTimeRequested = millis();
        } else {
            Serial.println("Requests are too frequently; skip.");
        }
    }