TWELITE SPOTで子機のデータをブラウザーで表示する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

TWELITE SPOTはTWELITEとESP32-WROOM-32Eを組み合わせた無線LANモジュールです。子機を出荷時のアプリと同様の設定に合わせることでWiFiを使ってブラウザーに子機から取得したデータを表示させることができます。

TWELITE SPOTの動作確認を行うため、手持ちのTWELITE PAL(開閉センサーパル:PALとする)と自作の子機モジュールのパケット情報をブラウザー(スマホ)に表示します。

TWELITEとESP32-WROOM-32E(以下ではESP32とする)を組み合わせて動作確認した下記記事の親機をTWELITE SPOTに置き換えて動作確認を行います。

ESP32-WROOM-32EとTWELITEの特徴を活かした無線通信

トワイライトを太陽光パネルで動作させたことやMWSTAGEの環境でソフト開発して無線通信したことなどについてまとめています。

トワイライト(TWELITE)のソフト開発と無線通信でできること

TWELITE SPOTの動作確認

TWELITE SPOTはTWELITEの特徴である省電力で中距離通信ができるZigBee通信をESP32のWiFi通信に変換してデータ管理するモジュールです。詳細は下記リンクを参考にしてください。

IoT 無線LANゲートウェイ TWELITE SPOT – トワイライトスポット – MONO-WIRELESS.COM

ESP32のアクセスポイント機能やサーバー機能を使用することでTWELITE SPOTを中継器として使用したり、クラウド接続したりすることができます。

子機のTWELITEから無線通信したデータをESP32を介してブラウザーにデータ履歴などが表示できます。

TWELITE SPOTに実装されているTWELITE及びESP32は任意のアクトやArduinoファイルを書き込むことができるため、カスタマイズして使用することができます。

TWELITE SPOTにプリインストールされているソフトでPAL及びアクトを書き込んだ子機モジュールでの動作確認を行いました。

モノワイヤレス(Mono Wireless) 無線LANゲートウェイ TWELITE SPOT
モノワイヤレス(Mono Wireless)
TWELITEのネットワーク( TWELITE NETトワイライトネット )を無線LANに接続するための装置です。

プリインストールされているアプリは作りこまれており応用範囲が広く、アクトでカスタマイズできるため動作確認して面白かったです。

動作確認(PAL)

TWELITE SPOTでPALから取得したパケット情報を表示します。PALの設定をTWELITE SPOTに合わせるため周波数チャンネルとアプリケーションIDをMWSTAGEのインタラクティブモードで変更します。

TWELITE PAL(開閉センサーパル)の設定
TWELITE PAL(開閉センサーパル)の設定

PALの周波数チャンネル(Channels)を18、アプリケーションID(Application ID)を0x67720102に変更します。変更後[S](save Configuration)で設定を保存します。変更後はトワイライターを外しボタン電池でPALを動作させます。

TWELITE SPOTのアクセスポイントの「TWELITE SPOT」に接続し、「192.168.1.1」をGoogle Chromeなどのブラウザーで開きます。

メイン画面
メイン画面
Serial Viewerの動作確認
Serial Viewerの動作確認

ページを開くとメイン画面が表示されます。Signal ViewerはTWELITE DIPの初期アプリ、CUE ViewerはTWELITE CUEの初期アプリ、ARIA ViewerはTWELITE ARIAの初期アプリに対応しているためPALではデータが表示されません。Serial Viewerは子機から受信したパケットを表示する機能でアプリの種別を問わず使用できます。

PALは磁石を近づけると親機に通知します。Serial Viewerにパケットの情報が表示されており、Packet Typeを確認するとApp_PALの表示になっているためPALの情報が取得できていることが分かります。

PR:【CreatorsFactory】転職率96%!Webスクール説明会申し込み

動作確認(子機モジュール)

子機の構成
子機の構成

アクトによる任意のアプリでSerial Viewerにパケット情報を表示されるかを確認します。子機はESP32-WROOM-32EとTWELITEの特徴を活かした無線通信のものを使用します。子機の通信相手が自作のESP32+TWELITEの親機からTWELITE SPOTになります。

子機のソースコードの周波数チャンネルとアプリケーションIDをPALと同様にTWELITE SPOTに合わせます。PALと同様にアクセスポイントに接続してSerial Viewerを表示して子機からの受信を待機します。

子機のアクトの動作確認
子機のアクトの動作確認

子機からデータを受信するとパケットの情報がSerial Viewerに表示されます。アクトによる任意のパケットなので種別の表示がUnknownになっていますが、アクトのパケットであることは下記のリンクのアクトのデータフォーマットで確認できます。

アクト – WINGS (twelite.info)

データフォーマットのコマンド種別がAAになっていることからアクトのデータであると判断することができます。

アクトのパケットから温湿度のデータを確認します。パケットの最後尾の1バイトがチェックサムで前方の4バイト分が温度/湿度のデータの100倍値を送信するように指定しています。例として一番下のパケットから温度/湿度のデータを確認します。温度のデータが0x846(2118)なので21.18℃になります。湿度のデータは0x1ACC(6860)なので61.60%になります。

自作のアクトのパケットを解析してブラウザーに表示するようにHTMLデータを改造する必要がありますが、温湿度の情報であればTWELITE ARIAを使用するのが手っ取り早い方法だと感じました。

スポンサーリンク

TWELITE SPOTのソフトを変更して使用する

TWELITE SPOTはプリインストールされているアプリで温湿度や加速度センサーの情報を取得してグラフによる履歴表示できますが、アクトを書き込んでカスタマイズすることもできます。下記記事のソースコードをTWELITE SPOTのTWELITEとESP32に書き込んで動作確認を行います。

ESP32-WROOM-32EとTWELITEの特徴を活かした無線通信

ソースコードは流用しますが、TWELITE SPOTのハード構成に合わせるため一部を追加修正行います。

TWELITE SPOTに実装されているTWELITEはインタラクティブモードに遷移できるタイプのものじゃないためESP32のソースコードにMWingsライブラリを追加してTWELITEの設定を行います。

TWELITEのシリアルポートはモニター用のSerial以外にESP32との通信の為TWELITEのSerial1のクラスオブジェクトを実装する必要があります。

MWingsライブラリの追加

MWingsライブラリはモノワイヤレス社が公開しているライブラリです。Arduino IDEで追加して使用することができます。使用方法などの詳細は下記のリンクで説明されています。

TWELITE SPOT スタートガイド

以下はMWingsライブラリの追加とTWELITEの初期化に関わる部分を抜粋してまとめています。

Arduino IDEでMWingsライブラリを追加
Arduino IDEでMWingsライブラリを追加

Arduino IDEのライブラリマネージャの検索欄にTweliteやmwings等を入力するとライブラリの候補が表示されます。候補の中から「MWings by MONO WIRELESS」をインストールします。

ESP32のソースコードにTWELITEの設定を初期化するためにMWings.hをインクルードして使用します。初期化の例は以下の通りです。

#include <MWings.h>

// Pin defs
const uint8_t TWE_RST = 5;
const uint8_t TWE_PRG = 4;
const uint8_t LED = 18;

// TWELITE defs
const uint8_t TWE_CH = 18;
const uint32_t TWE_APPID = 0x67720102;
const uint8_t TWE_RETRY = 2;
const uint8_t TWE_POWER = 3;

void setup() {

    Serial2.begin(115200);
    Twelite.begin(Serial2,
      LED, TWE_RST, TWE_PRG,
      TWE_CH, TWE_APPID, TWE_RETRY, TWE_POWER);//指定しない場合は-1で省略可
}

ESP32のSerial2を使用します。begin()関数でシリアル通信のボーレートを指定します。TWELITEの初期のボーレートである115200を指定します。

TweliteはMWingsクラスをインスタンス化したオブジェクトです。tweliteを操作してTWELITEを初期化を行います。MWingsクラスのbegin()関数でTWELITEの周波数チャンネルとアプリケーションIDなどを設定します。

第1引数はESP32のシリアルポートを指定します。第2引数はステータスLEDを接続したピン番号を指定します。未使用の場合は-1で省略できます。

第3引数はTWELITEのRSTピンを接続した番号を指定します。第4引数はTWELITEのPRGピンを接続したピンの番号を指定します。

第3引数、第4引数はTWELITEをリセットしてプログラムモードに遷移させたりするピンでハードに依存したピン番号になるため固定の番号になります。

第5引数は周波数チャンネルを指定します。第6引数はアプリケーションIDを指定します。

広告

ESP32のソフトを書き込む

TWELITE SPOTのESP32のソフトの書き込みはトワイライターを使って行います。USB TypeCは電源供給用でソフトの書き込みはできません。書き込みの方法は下記リンクに詳細があります。

ESP32 を使ったファームウェア開発の基礎 | TWELITE SPOT スタートガイド

Webサーバーで表示するファビコンやChartライブラリのファイルをFLASH領域に書き込みます。ESP32 Sketch Data Uploadプラグイン(Arduino IDE Ver1.8.19などで使用可能)を使用する方法や下記記事による方法で必要なファイルを書き込みます。

ESP32-WROOM-32EにSDカードのファイルをアップロードする

ESP32のソフトの書き込み方法は以下の通りです。

  1. USB TypeCに電源を供給した状態で行う
  2. ESP32のFLASHのサイズを16MBに設定変更する。(パーティションを管理しない場合は初期の4MBでも問題なし)
  3. BOOTボタンを押したまま、ENボタンを押してESP32をリセットしてからENボタンを外してBOOTモードで起動する。
  4. BOOTモードでソフトを書き込む。書き込み後はENを押してリセットする。

Serial1を実装する

TWELITEとESP32はSerial1を使ってシリアル通信を行いますが、初期状態では実装されていないため追加する必要があります。下記のリンクに詳細が説明されています。

Serial – MWX Library (twelite.info)

void setup(){   
  Serial1.setup(TX_BUFF,RX_BUFF);
  //Serial1.begin(115200,serial_jen::E_CONF::PORT_ALT);//通信なし
  Serial1.begin(115200);//通信可能
}

SerialのクラスのSerial1オブジェクトを操作して設定を行います。setup()関数はSerial1で使用する送信データと受信データの一時保管のサイズを指定します。

第1引数はTX用のバッファサイズを指定します。例ではSerialのサイズに合わせて768を指定しています。

第2引数はRX用のバッファサイズを指定します。例ではSerialのサイズに合わせて256を指定しています。

begin()関数はボーレートとシリアル通信に使用するピンの区分を指定します。第1引数に通信のボーレートを指定します。第2引数はSerial1のピンの区分を指定します。例では区分の指定を行っていません。

回路図ではD14,D15がSerial1になっているためserial_jen::E_CONF::PORT_ALTを指定しましたが、通信ができませんでした。原因が分からなかったため指定を外すと通信ができました。

当方の解釈の間違いである可能性もありますが、回路図の記載ミスもしくはピンの区分に関する記載ミスがありそうです。

adrs =&txdata.header[0];
for(uint8_t i = 0; i < allsz; i++ ){
  Serial1 << (*adrs);
  //Serial1.write(*adrs);
  adrs++;
}

子機モジュールから無線受信したデータを電文に変換してSerial1で送信します。Serialを指定するとPC(トワイライターなどのモニター)に送信することになるため注意が必要です。

広告

動作確認

子機モジュールから取得した温湿度データがESP32を介して表示できるかを確認します。トワイライターをESP32に接続し、シリアルモニタを確認します。

シリアルモニタの結果
シリアルモニタの結果

子機から無線受信したデータが親機のTWELITEからESP32にシリアル通信できていることが確認できました。子機の設定の6秒間毎の温湿度データ送信についても確認できました。次にスマホでWiFiで温湿度データの履歴を確認しました。

温湿度の履歴をWiFiで確認した結果
温湿度の履歴をWiFiで確認した結果

WiFiのアクセスポイント「EngKapi1」を選択しパスワードに「11112222」を入力してアクセスポイントに接続します。

Google ChromeでIPアドレス「192.168.4.1」を指定するとESP32のWebサーバーが応答し、温湿度データの履歴が表示されることが確認できました。また、IPアドレス「192.168.4.1/ti」のように存在しないURLを指定するとFile Not Foundが表示されることも確認できました。

アクセスポイント、パスワード、IPアドレスはソースコードで変更している場合は指定したものと置き換えて接続してください。

TWELITE SPOTのハードを使用することでコンパクトなサイズでTWELITEの特徴を活かしつつWiFi通信が利用できるのは面白いと感じています。

プリインストールされているアプリも作りこまれているため流用することで様々な用途で応用ができそうです。

プリインストールのアプリに戻す方法

ユーザーのアクトやESP32のソフトから初期アプリに戻す場合は公開されているソフトを再度書き込む必要があります。

ファームウェアの初期化方法 | TWELITE SPOT マニュアル

ESP32の場合はArduino IDEでプリインストールされている環境を再現するとよいのでesptoolを使った方法以外でも初期アプリに戻すことができます。

プリインストール済みスケッチ | TWELITE SPOT マニュアル

上記リンクからGitBubに遷移してspot-serverをダウンロードしてESP32に書き込みします。コンパイルして書き込む場合ライブラリが不足しているとエラーになるためGitBubのLibrariesに記載されているライブラリをインストールする必要があります。

スポンサーリンク

ソースコード全体

TWELITE SPOTのソフトを変更して使用するで動作確認したソースコードです。TWELITE SPOTに書き込むとプリインストールされているアプリが使用できなくなります。

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

TWELITEのソースコード:

#include <TWELITE>
#include <NWK_SIMPLE>

#define CNT_MAX 1000
#define HEADER1 'T'
#define HEADER2 'W'
#define TX_BUFF 768
#define RX_BUFF 256

const uint32_t APP_ID = 0x67720102;
const uint8_t CHANNEL = 18;

struct TXCOM_TYP{
  uint8_t header[2];
  uint8_t sz;
  uint8_t cnl;
  uint8_t ts[4];
  uint8_t temp[2];
  uint8_t humi[2];  
  uint8_t sum;
};

int16_t cnt1ms;
uint32_t ts;
bool rcvflg;
TXCOM_TYP txdata;

/*** function prototype */
void receive(void);
void SerialTxSet(void);
uint8_t CalcSum(uint8_t *buf, uint8_t sz);

/*** setup procedure (run once at cold boot) */
void setup() {

  the_twelite
    << TWENET::appid(APP_ID)
    << TWENET::channel(CHANNEL)
    << TWENET::rx_when_idle();

    Serial1.setup(TX_BUFF,RX_BUFF);
    //Serial1.begin(115200,serial_jen::E_CONF::PORT_ALT);
    Serial1.begin(115200);

  // Register Network
  auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
  nwksmpl << NWK_SIMPLE::logical_id(0x00);
  the_twelite.begin();

  Serial << "--- Parent_sht35 act ---" << mwx::crlf;
  //Serial1 << "--- Parent_sht35 act ---" << mwx::crlf;
}

/*** loop procedure (called every event) */
void loop() {

  if(TickTimer.available()){
    ++cnt1ms;
  }

  if( cnt1ms >= CNT_MAX){
    cnt1ms -= CNT_MAX;

    ts = (ts + 1 ) % 40000000;

    if(rcvflg){
      rcvflg = false;
    }else
    {
      //Serial << "ts:" <<  int(ts) << mwx::crlf << mwx::flush;;
    }
  }
  }
/* 受信データをシリアル通信する */
void on_rx_packet(packet_rx& rx, bool_t &handled) {
  uint16_t temp;
  uint16_t humi;

  //Serial << format("..receive(%08x/%d) : ", rx.get_addr_src_long(), rx.get_addr_src_lid());
  //Serial << "ts:" << int(ts) << ":" << int(rx.get_addr_src_lid());

  auto&& np = expand_bytes(rx.get_payload().begin(),rx.get_payload().end()
        ,temp
        ,humi
  );

  txdata.ts[0] = ts & 0xFF;
  txdata.ts[1] = ts >> 8;
  txdata.ts[2] = ts >> 16;
  txdata.ts[3] = ts >> 24;
  txdata.cnl = int(rx.get_addr_src_lid());
  txdata.temp[0] = temp & 0xFF;
  txdata.temp[1] = temp >> 8;
  txdata.humi[0] = humi & 0xFF;
  txdata.humi[1] = humi >> 8;
  SerialTxSet();

  Serial << " temp: " << (double)temp/100 <<"C" 
         << " humid: " << (double)humi/100 << "%" 
  	   << mwx::crlf << mwx::flush;
  rcvflg = true;     
}

/* TX function add */
void SerialTxSet(void){
  uint8_t *adrs;
  uint8_t allsz = 0;
  uint8_t i;

  txdata.header[0] = HEADER1;
  txdata.header[1] = HEADER2;
  txdata.sz = sizeof(txdata.cnl) + sizeof(txdata.ts) + 
              sizeof(txdata.temp) + sizeof(txdata.humi);
  txdata.sum = CalcSum(&txdata.cnl,txdata.sz);
  allsz = sizeof(txdata.header)+ sizeof(txdata.sz)+ txdata.sz + 1;

  adrs =&txdata.header[0];
  for(uint8_t i = 0; i < allsz; i++ ){
    Serial1 << (*adrs);
    //Serial1.write(*adrs);
    adrs++;
  }
}

/* ChkSum function add */
uint8_t CalcSum(uint8_t *buf, uint8_t sz=0){
  uint8_t ret = 0;

  for(uint8_t i=0; i < sz; i++ ){
    ret = ret + (*buf);
    buf++; 
  }
  return ret;
}

TWELITEのソースコードはMWSTAGEの環境でアクト内のソースファイルを置き換えることで使用することができます。

ESP32のソースコード:

#include <WiFi.h>
#include <SPIFFS.h>
#include <WebServer.h>
#include <MWings.h>

#define HEADER_1 'T'
#define HEADER_2 'W'
#define RING_SZ 64
#define OFFSET_SZ 4

#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define TIM_RX_WAIT 5
#define TIM_RWE_WAIT 8000

#define CHART_SZ 600

// Pin defs
const uint8_t TWE_RST = 5;
const uint8_t TWE_PRG = 4;
const uint8_t LED = 18;
const uint8_t ESP_RXD1 = 16;
const uint8_t ESP_TXD1 = 17;

// TWELITE defs
const uint8_t TWE_CH = 18;
const uint32_t TWE_APPID = 0x67720102;
const uint8_t TWE_RETRY = 2;
const uint8_t TWE_POWER = 3;

const char *ssid = "EngKapi1"; //SSID
const char *pass = "11112222"; //password
const IPAddress ip(192,168,4,1); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク

typedef struct{
  uint8_t wp;
  uint8_t rp;
  uint8_t buf[RING_SZ];
}RING_MNG;

RING_MNG esp32Rcv;
uint8_t Rcvdata[RING_SZ];
uint32_t beforetimCnt = millis();
int16_t timRxWait = TIME_OFF;
int16_t timTweRcvWait = TIME_OFF;
float tempdat[CHART_SZ];
float humiddat[CHART_SZ];

WebServer Wserver(80);

/*** Local function prototypes */
void mainApp(void);
void mainTimer(void);
uint8_t CalcSum(uint8_t *buf, uint8_t sz );
void Rcvmain(void);
void ReadPointerAdd(void);
void Sht35Set(bool tweflg);
void HtmlSet(void);
void handleNotFound(void);

void setup() {

    Serial.begin(115200);
    Serial2.begin(115200);

    Twelite.begin(Serial2,
      LED, TWE_RST, TWE_PRG,
      TWE_CH, TWE_APPID, TWE_RETRY, TWE_POWER);
    
    WiFi.softAP(ssid, pass); //WiFiのアクセスポイントの設定
    WiFi.softAPConfig(ip, ip, subnet); //アクセスポイントのIP及びサブネットマスク
    SPIFFS.begin();
    //Webサーバー
    Wserver.serveStatic("/myfavicon.ico",SPIFFS,"/myfavicon.ico");
    Wserver.serveStatic("/chart.umd.js",SPIFFS, "/chart.umd.js");
    Wserver.on("/", HTTP_GET, HtmlSet); //URLを指定して処理する関数を指定
    Wserver.onNotFound(handleNotFound); //URLが存在しない場合の処理する関数を指定
    Wserver.begin(); //Webサーバーの開始
}

void loop() {
  
  mainApp();
  mainTimer();
  Wserver.handleClient(); //クライアント接続待ち
}

/* メイン関数 */
void mainApp(void){

  while( Serial2.available()){
    esp32Rcv.buf[esp32Rcv.wp] = Serial2.read(); //データリード
    //Serial.write(esp32Rcv.buf[esp32Rcv.wp]);

    if( ++esp32Rcv.wp >= sizeof(esp32Rcv.buf)){
      esp32Rcv.wp = 0;
    }
  }

  Rcvmain();

  if( timTweRcvWait == TIME_UP ){
    Sht35Set(false);
  }

}

/* Timer Management function add */
void mainTimer(void){

  if ( millis() - beforetimCnt >= BASE_CNT ){
      beforetimCnt = millis();

        if( timRxWait > TIME_UP ){
            timRxWait--;
        }

        if( timTweRcvWait > TIME_UP ){
          timTweRcvWait--;
        }
    }
}

/* 受信データの処理 */
void Rcvmain(void){
  int8_t  rxsz;
  uint8_t datsz=0;
  uint8_t allsz;
  uint8_t rp = esp32Rcv.rp;  
  uint8_t i;
  uint8_t sumchk;
  uint8_t dat[3];

  if( timRxWait == TIME_UP){
    timRxWait = TIME_OFF;
    ReadPointerAdd();    
  }

  rxsz = esp32Rcv.wp - esp32Rcv.rp; //受信データ数の算出

  if( rxsz < 0 ){
    rxsz = rxsz + sizeof(esp32Rcv.buf);
  }          
  if( rxsz == 0 ){
    timRxWait = TIME_OFF;
  }
  else{
    if( timRxWait == TIME_OFF ){
      timRxWait = TIM_RX_WAIT;
    }

    if( esp32Rcv.buf[ rp ] != HEADER_1 ){ //ヘッダーの確認
      ReadPointerAdd(); 
    }
    else{
      if( rxsz >= 3 ){
        for( i = 0; i < 3; i++){//データサイズ算出のため仮おき
          dat[i] = esp32Rcv.buf[ rp ];
          if(++rp >= sizeof(esp32Rcv.buf) ) rp = 0;
        }

        if( dat[2] > sizeof(esp32Rcv.buf) - OFFSET_SZ ){
          allsz = sizeof(esp32Rcv.buf) - OFFSET_SZ;
        }
        else{
          datsz = dat[2];
          allsz = datsz + OFFSET_SZ; //header,sz,sumを含む
        }

        if( rxsz >= allsz){
          timRxWait = TIME_OFF;
                    
          for(i=0; i < sizeof(Rcvdata); i++){
            Rcvdata[i] = 0;
          }
                    
          for( i=0; i < allsz; i++ ){
            Rcvdata[i] = esp32Rcv.buf[esp32Rcv.rp];
            ReadPointerAdd();
          }

          sumchk = CalcSum(&Rcvdata[3], datsz); 
          if( sumchk == Rcvdata[allsz-1]){ //チェックサムが一致するか
            Sht35Set(true);
          }
        }
      }
    }
  }
}

/* 読み込み位置の更新 */
void ReadPointerAdd(void){
    
  if(++esp32Rcv.rp >= sizeof(esp32Rcv.buf) ){
    esp32Rcv.rp = 0;
  }
}
/* チェックサムの計算 */
uint8_t CalcSum(uint8_t *buf, uint8_t sz ){
  uint8_t ret = 0;

  for(uint8_t i=0; i < sz; i++ ){
    ret += *buf;
    buf++; 
  }
  return ret;
}
/* TWELiteから取得したデータをセット  */
void Sht35Set(bool tweflg){
    uint32_t tp=0;
    uint16_t temp=0;
    uint16_t humi=0;
    float temp_f;
    float humi_f;

    timTweRcvWait = TIM_RWE_WAIT;

    if( tweflg){
      tp = (Rcvdata[7] << 24) + (Rcvdata[6] << 16) + (Rcvdata[5] << 8) + Rcvdata[4];
      temp = (Rcvdata[9] << 8) + Rcvdata[8];
      humi = (Rcvdata[11] << 8) + Rcvdata[10];
    }
    temp_f = (float)temp/100;
    humi_f = (float)humi/100;

    Serial.print("tp:");
    Serial.print(tp);
    Serial.print(" temp:");
    Serial.print(temp_f);
    Serial.print("C");
    Serial.print(" humi:");
    Serial.print(humi_f);
    Serial.print("%");
    Serial.println();

    memmove( &tempdat[1], &tempdat[0],(CHART_SZ-1)*sizeof(float));
    memmove( &humiddat[1], &humiddat[0],(CHART_SZ-1)*sizeof(float));
    tempdat[0] = temp_f;
    humiddat[0] = humi_f;
}
/* グラフ表示 */
/* クライアントに返信するhtmlデータを生成 */
void HtmlSet(void){
  String str = "";

  str += "<html lang=\"ja\">";
  str += "<head>";
  //str += "<meta http-equiv=\"refresh\" content=\"5\">";
  str += "<meta charset=\"UTF-8\">";
  str += "<title>Sensor SHT35</title>";
  str += "<link rel='shortcut icon' href='/myfavicon.ico' />";
  str += "<script src = '/chart.umd.js'></script>"; 
  str += "</head>";
  str += "<body>";
  str += "<h1>ESP32-SHT35-DIS温湿度センサ</h1>";
  str += "<h2>TWELITEで取得したデータをWifeで確認</h2>";
  str += "<h2>温度: ";
  str += tempdat[0];
  str += "℃  ";
  str += "湿度: ";
  str += humiddat[0];
  str += "%RH";
  str += "</h2>";
  str += "<div style='height: 600px; width: 800px; margin: auto;'>";
  str += "<canvas id='ChartID'></canvas>";
  str += "</div>";
  str += "<script>";
  str += "var ctx = document.getElementById('ChartID').getContext('2d');";
  str += "var myChart = new Chart(ctx, {";
  str += "   type: 'line',";
  str += "   data: {";
  str += "     labels: [ ";
                 for(uint16_t i = 0; i< CHART_SZ; i++ ){
                   str += i; 
                   if(i != CHART_SZ-1 )str += ",";
                 }   
  str += "     ],";
  str += "       datasets: [{";
  str += "         label: '温度',";
  str += "         fill: false,";
  str += "         borderColor: 'red',";
  str += "         borderWidth: 1,";
  str += "         pointRadius: 0,";
  str += "         pointHoverBorderWidth: 10,";
  str += "         data: [";
                     for(uint16_t i = 0; i< CHART_SZ; i++ ){
                       str += tempdat[i]; 
                       if(i != CHART_SZ-1 )str += ",";
                     }
  str += "         ]";
  str += "       }, {";
  str += "         label: '湿度',";
  str += "         fill: false,";
  str += "         borderColor: 'blue',";
  str += "         borderWidth: 1,";
  str += "         pointRadius: 0,";
  str += "         pointHoverBorderWidth: 10,";
  str += "         pointStyle: 'triangle',";
  str += "         yAxisID: 'y2',";
  str += "         data: [";
                     for(uint16_t i = 0; i< CHART_SZ; i++ ){
                       str += humiddat[i]; 
                       if(i != CHART_SZ-1 )str += ",";
                     }
  str += "         ]";
  str += "       }]";
  str += "     },";
  str += "     options: {";
  str += "       responsive: true,";
  str += "       plugins: {";
  str += "         title: {";
  str += "           display: true,";
  str += "           text: 'SHT35-DISから取得した温度・湿度',";
  str += "           font: {";
  str += "             size: 18,";
  str += "           },";
  str += "         },";
  str += "       },";
  str += "       scales: {";
  str += "         x: {";
  str += "           display: true,";
  str += "           stacked: false,";
  str += "           title: {";
  str += "             display: true,";
  str += "             text: 'サンプル数',";
  str += "             font: {";
  str += "               size: 16,";
  str += "             },";      
  str += "           },";
  str += "         },";
  str += "         y: {";
  str += "           min: -20,";
  str += "           max: 80,";
  str += "           title: {";
  str += "             display: true,";
  str += "             text: '温度[℃]',";
  str += "             color: 'red',";
  str += "             font: {";
  str += "               size: 16,";
  str += "             },";
  str += "           },";
  str += "           ticks:{";
  str += "             display: true,";          
  str += "             color: 'red',";
  str += "           },";
  str += "         },";
  str += "         y2: {";
  str += "           min: 0,";
  str += "           max: 100,";
  str += "           title: {";
  str += "             display: true,";
  str += "             text: '湿度[%]',";
  str += "             color: 'blue',";
  str += "             font: {";
  str += "               size: 16,";
  str += "             },";
  str += "           },";
  str += "           position: 'right',";
  str += "           ticks:{";
  str += "             display: true,";
  str += "             color: 'blue',";
  str += "           },";
  str += "         },";
  str += "       },";
  str += "     },";
  str += "})";
  str += "</script>";
  str += "</body>";
  str += "</html>";

  Wserver.send(200,"text/html", str); 
  //HTTPレスポンス200でhtmlデータとして送信
}

/* URLが存在しない場合の処理 */
void handleNotFound(void) {

  String message = "File Not Found\n\n";
  message += "URI: ";
  message += Wserver.uri();
  message += "\nMethod: ";
  message += (Wserver.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += Wserver.args();
  message += "\n";

  for (uint8_t i = 0; i < Wserver.args(); i++) {
    message += " " + Wserver.argName(i) + ": " + Wserver.arg(i) + "\n";
  }
  Wserver.send(404, "text/plain", message); //テキストファイルであることを示している。
}

本ソースコードでグラフ表示するためにChart.jsライブラリをESP32 Sketch Data Uploadまたは下記の参考記事による方法でFLASHにファイルを書き込む必要があります。

ESP32-WROOM-32EにSDカードのファイルをアップロードする

またChart.jsのバージョンが4.0.0以上ならchart.umd.js(旧バージョンではchart.min.js)を使用します。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました