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

もとのページに戻る

2024-12-20 現在

ThingSpeak によるグラフ表示

最新版
    無線 LAN 子機として振る舞い、MathWorks のサービス ThingSpeak を使って温湿度データをグラフ化するためのサンプルスケッチ spot-thingspeak の解説です。

    ソースコードの入手

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

    システムの概要

    spot-thingspeak サンプルは、TWELITE ARIA から受信したデータを HTTP GET リクエストとして ThingSpeak のサーバへ送信し、グラフとして表示できるようにします。

    表示例

    表示例

    開発に必要なもの

    環境整備

    ThingSpeak の設定

    1. ThingSpeak のサイトへアクセスし、MathWorks のアカウントを作成します
    2. “Channel” を作成し、次のように設定します。
    Channelの設定例

    Channelの設定例

    1. “API Keys” タブにある、16文字の “Write API key” を控えておきます

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

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

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

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

    ユーザ設定の変更

    Arduino IDE 上部のタブから config.h を開き、Wi-Fi の SSID と パスワードを設定してください。WPA2-PSK ネットワークを想定しています。

    また、先ほど控えた 16文字の “Write API key” についても設定してください。必要に応じて、ルート証明書も書き換えてください。

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

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

    TWELITE ARIA の設定と起動

    1. TWELITE ARIA の設定を変更し、TWELITE ARIA モード送信間隔を30秒以上(例:60秒)とします(サーバへの過負荷を避けるため)
    2. CR2032 電池を投入し、TWELITE ARIA を起動します

    スケッチ

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

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

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

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

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

    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接続するネットワークのパスワード

    ThingSpeak の Write API key

    config.h の15行目では、ThingSpeak の “Channel” の “Field” へデータを追加するために必要な “Write API key” を定義しています。

    ルート証明書

    config.h の18-41行目では、Google Chrome を使って api.thingspeak.com から取得したルート証明書の中身を記述しています。必要に応じて書き換えてください。

    const char *CA_CERT = R"(
    -----BEGIN CERTIFICATE-----
    中略
    -----END CERTIFICATE-----
    )";

    ホストの設定

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

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

    各種定数の定義

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

    const int QUERIES_MAX_LENGTH = 128;         // bytes (without \0)
    const int32_t CONNECT_TIMEOUT = 10;     // seconds
    const uint32_t RECONNECT_MIN_INTERVAL = 5; // seconds
    // According to thingspeck free limitations, SEND_MIN_INTERVAL is set for 20s.
    const uint32_t SEND_MIN_INTERVAL = 20; // seconds
    const uint32_t REQUEST_TIMEOUT = 10;   // seconds
    
    名称内容
    QUERIES_MAX_LENGTHクエリ文字列の最大長(ヌル文字含まず)
    CONNECT_TIMEOUTサーバへの接続時のタイムアウト
    RECONNECT_MIN_INTERVALWi-Fiアクセスポイントへ再接続する際の最短間隔
    SEND_MIN_INTERVALリクエスト間隔の最短間隔
    REQUEST_TIMEOUTリクエストからレスポンスまでのタイムアウト

    ピン番号の定義

    spot-thingspeak.ino の29-34行目では、ピン番号を定義しています。

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

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

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

    static WiFiClientSecure client;
    名称内容
    clientHTTPS通信のインタフェース

    グローバル変数の宣言

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

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

    関数プロトタイプの宣言

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

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

    setup()

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

    void setup() {
        Serial.begin(115200);
    
        initTWELITE();
        initWiFi();
    
        // 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)
    }

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

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

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

    loop()

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

    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()

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

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

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

    initTWELITE()

    107-114行目は、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()

    117-144行目は、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) {
            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);
    }

    onAppAriaPacket()

    147-157行目には、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()

    160-220行目は、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 for the Channel on the ThingSpeak
                char queries[QUERIES_MAX_LENGTH+1];
                snprintf(queries, sizeof(queries),
                         "api_key=%s&field1=%s&field2=%s&field3=%s&field4=%d",
                         // Write API key for the Channel
                         WRITE_API_KEY,
                         // Field 1: Temperature
                         String(data.temp100x / 100.0f, 2),
                         // Field 2: Humidity
                         String(data.humid100x / 100.0f, 2),
                         // Field 3: Supply Voltage
                         String(data.supplyVoltage / 1000.0f, 2),
                         // Field 4: Link Quality
                         data.linkQuality);
    
                // Send a request
                client.println(String("GET https://") +
                               SERVER_HOST +
                               String("/update?") +
                               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.print("Index (if succeeded): ");
                        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();
                Serial.println("");
            }
            lastTimeRequested = millis();
        } else {
            Serial.println("Requests are too frequently; skip.");
        }
    }