0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

無線LANゲートウェイ TWELITE SPOTとGoogle App Scriptで作る農業向け温湿度測定・監視システム

Posted at

TWELITE SPOTについて

概要ホームページ
データシート
スタートガイド
ESP32 を使ったファームウェア開発の基礎
IMG_20230811_203716.jpg

注意事項:7Pインターフェイスの接続方向

接続例
※誤った向きで接続すると破損の原因

Arduino IDEを用いたESP32側の開発の基礎

  1. Arduino IDEをインストール
  2. ボートマネージャーでESP32を追加
  3. ツールバーの ツール -> ボード -> ESP32 Arduino -> ESP32 Dev Module を選択
    ボード設定を以下と同様にする。Flash size を 4MB (32Mb) から 16MB (128Mb) に変更
  4. ESP用7PインターフェイスにTWELITE R3を接続。向きに注意。TWELITE R3の表面を接続対象に向ける。
  5. USB-Cコネクタに5V電源をつなぐ
  6. ESP32をプログラムモードで起動。ESP32 リセットスイッチ EN(RST) と ESP32 ブートスイッチ BOOT を押し、EN(RST) -> BOOT の順で離す。BOOT を押した状態でリセットすることが重要
  7. ArduinoIDEからマイコンボードに書き込む
  8. 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で作る農業向け温湿度測定・監視システム

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?