/

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.");
    }
}