TWELITE SPOTについて
概要ホームページ
データシート
スタートガイド
ESP32 を使ったファームウェア開発の基礎
注意事項:7Pインターフェイスの接続方向
接続例
※誤った向きで接続すると破損の原因
Arduino IDEを用いたESP32側の開発の基礎
- Arduino IDEをインストール
- ボートマネージャーでESP32を追加
- ツールバーの ツール -> ボード -> ESP32 Arduino -> ESP32 Dev Module を選択
ボード設定を以下と同様にする。Flash size を 4MB (32Mb) から 16MB (128Mb) に変更 - ESP用7PインターフェイスにTWELITE R3を接続。向きに注意。TWELITE R3の表面を接続対象に向ける。
- USB-Cコネクタに5V電源をつなぐ
- ESP32をプログラムモードで起動。ESP32 リセットスイッチ EN(RST) と ESP32 ブートスイッチ BOOT を押し、EN(RST) -> BOOT の順で離す。BOOT を押した状態でリセットすることが重要
- ArduinoIDEからマイコンボードに書き込む
- ESP32を再起動する。ESP32 リセットスイッチ EN(RST) を押して離し、ESP32 をリセット
TWELITE SPOTとTWELITE ARIAを使って温湿度をGoogleスプレッドシートに記録するArduinoプログラム
// TWELITE SPOT to Google App Scritp
// 親機にTWELITE SPOT、子機にTWELITE ARIAを使用
// デバッグ時のシリアル通信用の定義
#define _DEBUG 0 /* _DEBUG = 1のとき、シリアルポートに計測データを出力する*/
#if _DEBUG
#define DBG(...) { Serial.println(__VA_ARGS__); } /* DBG(引数)をSerial.println(引数)と定義*/
#else
#define DBG(...)
#endif
// Arduino,ESP32の公式ライブラリをインクルード
#include <Arduino.h> //Arduinoの基本ライブラリ
#include <NTPClient.h> //現在時刻を取得するためにNTPを使う
#include <WiFi.h> //ESP32 の WiFi を使う
#include <WiFiUdp.h> //UDP を使う、NTPClient に必要
#include <Arduino_JSON.h> //JSON 文字列を扱う ArduinoJsonとは異なる
#include <HTTPClient.h> //HTTPクライアント JsonのPOSTに必要
// Third-party library:サードパーティのライブラリをインクルード
#include <TimeLib.h> //UNIX 時間をフォーマットする
// Mono Wireless TWELITE Wings API for 32-bit Arduinosライブラリをインクルード
#include <MWings.h>
// ユーザ設定を定義。実行時にはこの設定ファイルの内容を書き換える
// ファイルの内容はWIFI_SSID,WIFI_PASSWORD,GAS_URLの定義
#include "config.h"
// ESP32のピン番号を定義
const uint8_t TWE_RST = 5; //TWELITEのRSTピンが接続されているピンの番号
const uint8_t TWE_PRG = 4; //TWELITEのPRGピンが接続されているピンの番号
const uint8_t LED = 18; //基板上のESP32用LED が接続されているピンの番号
const uint8_t ESP_RXD1 = 16; //TWELITEのTXピンが接続されているピンの番号
const uint8_t ESP_TXD1 = 17; //TWELITEのRXピンが接続されているピンの番号
// TWELITE親機に適用する設定を定義
const uint8_t TWE_CH = 18; //TWELITEの周波数チャネル(TWELITE標準アプリの初期値)
const uint32_t TWE_APPID = 0x67720102; //TWELITEのアプリケーションID(TWELITE標準アプリの初期値)
const uint8_t TWE_RETRY = 2; //TWELITEの再送回数(送信時)
const uint8_t TWE_POWER = 3; //TWELITEの送信出力
constexpr int ARIA_BUFFER_PACKETS = 32; //ARIA からのパケットを格納するキューの長さ(32個まで保持可能)
// Type definitions:型を宣言
struct ParsedAppAriaPacketWithTime { //受信したパケットデータを受信時刻と合わせてキューへ格納するための型
ParsedAppAriaPacket packet;
uint32_t elapsedMillis; //パケット受信時の起動からの経過時間(ミリ秒)
uint32_t unixTime; //パケット受信時の UNIX 時間(秒)
};
// Global objects:グローバルオブジェクトを宣言
WiFiUDP ntpUDP; //NTPのためのUDPインタフェース
NTPClient timeClient(ntpUDP, "ntp.nict.jp", 32400); //NTPのインタフェース。UDPインタフェース、NTPサーバー、日本の時差32400秒=9時間を指定。メインループloop()内で必ず、ntp.update()を回す
QueueHandle_t ariaPacketQueue; //ARIAから受信したパケットと受信時刻を格納するキュー。FreeRTOSのQueue機能を使う
// 関数プロトタイプを宣言
void anotherLoop(); //非同期でTWELITEの処理を行う、もうひとつのloop()。 TWELITEの受信処理を止めないために、FreeRTOSのマルチタスク機能を使ってanotherLoop()を作成し、そのなかでTwelite.update()を実行する
String createJsonFrom(const ParsedAppAriaPacketWithTime& packetWithTime); //App_ARIAのパケットデータからJSON文字列を作る関数
bool addDataGAS(); //GASへデータを送信する
// 起動時に実行する内容
void setup() {
// シリアルポートの初期化
Serial.begin(115200); //SerialはArduino IDEのシリアルモニタとの通信
Serial.println("Hello, this is TWELITE SPOT.");
// 受信したパケットデータを受信時刻と合わせて格納するためのキューを初期化
Serial.println("Initializing queue...");
ariaPacketQueue = xQueueCreate(ARIA_BUFFER_PACKETS, sizeof(ParsedAppAriaPacketWithTime)); //FreeRTOSの機能。マルチタスクに対応したキューを作成
if (ariaPacketQueue == 0) { Serial.println("Failed to init a queue."); }
Serial.println("Completed.");
// TWELITE親機の設定と起動
Serial2.begin(115200, SERIAL_8N1, ESP_RXD1, ESP_TXD1); //Serial2はTWELITE SPOTに搭載されたTWELITE親機との通信
if (Twelite.begin(Serial2, LED, TWE_RST, TWE_PRG, TWE_CH, TWE_APPID, TWE_RETRY, TWE_POWER)) {
Serial.println("Started TWELITE.");
}
// パケット受信時のイベントの登録 Twelite.on()を呼び出し、送られたデータに対して行う処理を登録
Twelite.on([](const ParsedAppAriaPacket& packet) {
DBG("Got a new packet from ARIA.");
ParsedAppAriaPacketWithTime packetWithTime; //packetWithTimeオブジェクトを生成
packetWithTime.elapsedMillis = millis(); //起動開始からの経過時間をミリ秒単位で取得
packetWithTime.unixTime = timeClient.getEpochTime(); //NTPインターフェスからUNIX時間を取得
packetWithTime.packet = packet; //パケット本体を取得
//xQueueSend()により、受信したパケットデータを受信時刻と合わせてキューの末尾へ格納、送信成功時にpdPASSを返す
//第一引数はキューのハンドラ、第二引数は送信データへのポインタ、第三引数はキューが満杯だったときの待機時間
if (not(xQueueSend(ariaPacketQueue, &packetWithTime, 10) == pdPASS)) {
DBG("Failed to add packet data to the queue.");
}
});
// 無線LANの設定
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) { //ネットワークに接続できるまで、while文を実行
static int count = 0;
Serial.print('.');
delay(500);
// ネットワークに接続できるまで5秒に一度、再接続
if (count++ % 10 == 0) {
WiFi.disconnect();
WiFi.reconnect();
Serial.print('!');
}
}
Serial.print("\nConnected. IP: ");
Serial.println(WiFi.localIP().toString().c_str()); //IPアドレスを表示
// NTPの設定
Serial.print("Initializing NTP...");
timeClient.begin();
timeClient.update();
Serial.print("Completed. UNIX time: ");
Serial.println(timeClient.getEpochTime()); //NTPから取得したUNIX時間を表示
// Assign a FreeRTOS task to the Core 0 for the another loop procedure updating TWELITE
// Note: Core 0 is also used for the WiFi task, which priority is 19 (ESP_TASKD_EVENT_PRIO - 1)
// TWELITE のデータを非同期で更新するためのタスクを登録
xTaskCreatePinnedToCore(
[](void* params) {
while (true) {
anotherLoop(); //anotherLoop() を無限に呼ぶタスクを登録
vTaskDelay(1); //無限ループを含むタスクでは、必ずvTaskDelay(1); などを挿入し、ウォッチドッグタイマの入る隙を与える
}
},
"Task for anotherLoop()", 8192, nullptr, 18, nullptr, 0);
//タスクの名称はTask for anotherLoop()で、スタックサイズは8192、タスクへのパラメータはなく、優先度は18(大きいほど高く、無線LAN関連の処理は1個上の19)、タスクを操作するインタフェースもなく、実行するCPUコアは無線LANなどのRF処理と同じCore0( loop()等はCore 1)
}
void loop() {
addDataGAS(); //センサーデータをJsonでGASにPOST
timeClient.update(); //NTPライブラリの更新
}
// Another loop procedure; Priority is higher than loop()
void anotherLoop() {
Twelite.update(); //TWELITEのデータの更新 With multi-tasking (async)
}
// Create JSON string from parsed App_ARIA packets
String createJsonFrom(const ParsedAppAriaPacketWithTime& packetWithTime) {
JSONVar jsonData;
jsonData["app_type"] = "app_aria_twelite_aria_mode";
char dateTimeCString[20]; //日時を記録する文字列の変数を宣言
setTime(packetWithTime.unixTime);
sprintf(dateTimeCString, "%04d/%02d/%02d %02d:%02d:%02d", // yyyy/MM/dd hh:mm:ss DateTimeオブジェクトを文字列に変換
year(), month(), day(),
hour(), minute(), second());
jsonData["ArriveTime"] = dateTimeCString;
jsonData["LogicalID"] = packetWithTime.packet.u8SourceLogicalId; // 送信元の論理デバイスID
jsonData["SerialID"] = packetWithTime.packet.u32SourceSerialId; //送信元のシリアルID
jsonData["LQI"] = packetWithTime.packet.u8Lqi; //電波通信品質 50未満(悪い -80dbm 未満)、50~100(やや悪い)、100~150(良好)、150以上(アンテナの近傍)
jsonData["SequenceNumber"] = packetWithTime.packet.u16SequenceNumber; //シーケンス番号
jsonData["Power"] = packetWithTime.packet.u16SupplyVoltage; //電源電圧mV
jsonData["mag_status"] = packetWithTime.packet.u8MagnetState; //磁気イベントID
char temp[10];
sprintf(temp, "%.2f", packetWithTime.packet.i16Temp100x/ 100.0f); //100倍された温度(整数4桁で表現)を100で割り(浮動小数点)、小数点2桁までの文字列に変換
jsonData["Temperature"] = temp;
char humid[10];
sprintf(humid, "%.2f", packetWithTime.packet.u16Humid100x/ 100.0f); //100倍された相対湿度 (%)
jsonData["Humidity"] = humid;
return JSON.stringify(jsonData); //JSONデータの中身を全部確認する時は、String型に変換
}
// GASへデータをPOST
bool addDataGAS(){
UBaseType_t availableAriaPackets = uxQueueMessagesWaiting(ariaPacketQueue); //キューの中で読み出しを待っているデータの個数を返す
if (not(availableAriaPackets > 0)) {
DBG("There's no ARIA packet. Skip.");
return false;
}
for (int i = 0; i < availableAriaPackets; i++) {
ParsedAppAriaPacketWithTime packetWithTime;
if (not(xQueueReceive(ariaPacketQueue, &packetWithTime, 10) == pdPASS)) {
DBG("Failed to get packet data from the queue.");
break;
}
// 受信したデータからJSON 文字列を生成
String jsonStr = createJsonFrom(packetWithTime);
DBG(jsonStr); //JSONデータを表示
// JSON文字列をGASにPOST
HTTPClient http;
http.begin(GAS_URL);
http.addHeader("Content-Type", "application/json");
int status_code = http.POST(jsonStr); //POST
if( status_code == 200 ){ //200以外ではシリアルにエラーを出力
DBG("[POST]Send to server");
}
else{
DBG("[POST]failed to send to server");
}
http.end();
}
return true;
}
データを受信するGoogleスプレッドシートのプログラム
過去に作成したものをそのまま流用
モノワイヤレス TWELITEとGoogle App Scriptで作る農業向け温湿度測定・監視システム