ThingSpeak によるグラフ表示
spot-thingspeak
の解説です。本稿では、サードパーティのオープンソースソフトウェアを使用します。
サードパーティのソフトウェアについて、その詳しい使用方法を弊社からご案内することはいたしかねます。また、サードパーティのソフトウェアを使用されたことによるいかなる損害についても、弊社は一切の責任を負いません。
ソースコードの入手
GitHub リポジトリ monowireless/spot-thingspeak から入手できます。
システムの概要
spot-thingspeak サンプルは、TWELITE ARIA から受信したデータを HTTP GET リクエストとして ThingSpeak のサーバへ送信し、グラフとして表示できるようにします。
基本的に、spot-httpbin サンプルを改変して作られています。そちらの解説もご覧ください。
主に NTP 関連のコードを消去し、リクエストの内容を変えている点が異なります。
開発に必要なもの
-
無線LANゲートウェイ TWELITE SPOT
- 電源用 USB-C ケーブル
- USB AC アダプタ(1A 以上供給できるもの)
-
磁気・温度・湿度センサー無線タグ TWELITE ARIAなどの子機 (お持ちでない場合はご購入ください 👉 販売店一覧)
- CR2032 コイン電池 などの電源
-
USBアダプター TWELITE R3 (お持ちでない場合はご購入ください 👉 販売店一覧)
- 通信用 USB-C ケーブル
- 💻 開発用コンピュータ
環境整備
ThingSpeak の設定
- ThingSpeak のサイトへアクセスし、MathWorks のアカウントを作成します
- “Channel” を作成し、次のように設定します。
- “API Keys” タブにある、16文字の “Write API key” を控えておきます
IDE とツールチェインの導入
Arduino IDE 1.x による開発環境の構築方法 をご覧ください。
プロジェクトファイルの入手
- GitHub (monowireless/spot-thingspeak) から Zip ファイルをダウンロードします
- Zip ファイルを展開し、フォルダ名を
spot-thingspeak-main
からspot-thingspeak
に変更します - 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 の設定と起動
- TWELITE ARIA の設定を変更し、TWELITE ARIA モードの送信間隔を30秒以上(例:60秒)とします(サーバへの過負荷を避けるため)
- 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.h | Arduino の基本ライブラリ | 省略できる場合もあるが念のため記載 |
WiFiClientSecure.h | ESP32 で SSL通信を行う | |
WiFi.h | Wi-Fiを扱う | 省略できる場合もあるが念のため記載 |
MWings ライブラリ
13行目では、MWings ライブラリをインクルードしています。
#include <MWings.h>
ユーザ設定の定義
16行目では、config.h
をインクルードしています。
#include "config.h"
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 | 電源電圧 |
linkQuality | LQI |
temp100x | 100倍された温度 |
humid100x | 100倍された湿度 |
ここでは、TWELITE ARIA を使用します。
config.h
再起動間隔の定義
config.h
の4行目では、ESP32 の再起動間隔を指定しています。
const uint32_t REBOOT_INTERVAL = 21600; // seconds
ここでは、21600秒=6時間としています。
長期間の運用では、メモリリークが累積して不具合を起こしてしまう場合があります。
そこで Wi-Fi ルータのように定期的な再起動を行うようにしています。
TWELITE 設定の定義
config.h
の7-8行目では、TWELITE SPOT に搭載された TWELITE 親機に適用する設定を定義しています。
const uint8_t TWE_CH = 18;
const uint32_t TWE_APPID = 0x67720102;
名称 | 内容 |
---|---|
TWE_CH | TWELITE の 周波数チャネル |
TWE_APPID | TWELITE の アプリケーション 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 | サーバのポート番号 |
SERVER_HOST
には、https://
を含めないようにしてください。DNS Failed / with error '-54'
のエラーを引き起こします。各種定数の定義
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_INTERVAL | Wi-Fiアクセスポイントへ再接続する際の最短間隔 |
SEND_MIN_INTERVAL | リクエスト間隔の最短間隔 |
REQUEST_TIMEOUT | リクエストからレスポンスまでのタイムアウト |
SEND_MIN_INTERVAL
は、20秒に設定しています。
ThingSpeak の API の呼び出し制限によるものです。
SEND_MIN_INTERVAL
を短くすると、連続してパケットを受信した場合にサーバへ負担をかけてしまいます。
必ず適度な間隔を空けてください。
ピン番号の定義
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_PIN | TWELITE の RST ピンが接続されているピンの番号 |
PRG_PIN | TWELITE の PRG ピンが接続されているピンの番号 |
LED_PIN | 基板上の ESP32 用 LED が接続されているピンの番号 |
RX1_PIN | TWELITE の RX1 ピンが接続されているピンの番号 |
TX1_PIN | TWELITE の TX1 ピンが接続されているピンの番号 |
グローバルオブジェクトの宣言
37行目では、グローバルオブジェクトを宣言しています。
static WiFiClientSecure client;
名称 | 内容 |
---|---|
client | HTTPS通信のインタフェース |
グローバル変数の宣言
40-41行目では、グローバル変数を宣言しています。
static DataFromAria LatestDataFromAria;
static bool IsThereNewDataFromAria;
名称 | 内容 |
---|---|
LatestDataFromAria | TWELITE ARIA から受信した最新のデータ |
IsThereNewDataFromAria | TWELITE ARIA から新たなデータを受信したことを示すフラグ |
関数プロトタイプの宣言
44-54行目では、関数プロトタイプを宣言しています。
void anotherLoopForTWELITE();
名称 | 内容 |
---|---|
anotherLoopForTWELITE | TWELITEのデータを処理するためのループ関数 |
xTaskCreatePinnedToCore()
により、別のタスクとして登録しています。
void initTWELITE();
void initWiFi();
名称 | 内容 |
---|---|
initTWELITE | TWELITEの初期化関数 |
initWiFi | Wi-Fiの初期化関数 |
void onAppAriaPacket(const ParsedAppAriaPacket& packet);
名称 | 内容 |
---|---|
onAppAriaPacket | TWELITE ARIA からデータを受信した際のコールバック関数 |
void sendAriaData(const DataFromAria& data)
名称 | 内容 |
---|---|
sendAriaData | TWELITE 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
}
},
vTaskDelay()
を挿入しています。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);
}
下記リファレンスも合わせてご覧ください。
Twelite.begin()
mwings::MWings クラス | MWings API リファレンスTwelite.on()
mwings::MWings クラス | MWings API リファレンス
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.");
}
}