重要なお知らせ

2024年4月19日 価格改定のお知らせ

T&D ラボ

M5Stack BasicでTR32B/TR43Aの測定値からグラフとWBGT(暑さ指数)を表示する

  • TR4Aシリーズ
  • おんどとりease
  • M5Stack

1.はじめに

弊社から発売中の「おんどとりease (イーズ) 」ことTR32Bは、置かれた場所の温度と湿度を測定して記録する製品です。この製品は「おんどとり」シリーズの中でも最もシンプルな温度湿度ロガーですが、Bluetooth Low Energy通信機能を備え、本体の液晶画面 (LCD) はもちろん、スマート端末や弊社の無料クラウドでも温湿度が確認できる、非常に便利な機種です。

以前、市販のマイコンモジュールを用いてTR32BのBluetooth Low Energyの電波を受信する方法を紹介しましたが、(https://www.tandd.co.jp/lab/microcontroller/)
今回、更に実用的な用途を目指し、マイコンモジュールのLCDにグラフと熱中症予防の指針になっているWBGT(暑さ指数)を表示させてみました。

使用するマイコンモジュールは、前回より大型のカラーLCDを搭載したM5Stack Basic(以下、M5Stackと記載) を使用しました。

今回紹介する内容は弊社のBluetooth対応温度湿度データロガー TR43Aでも共通しています。
TR43Aを使用する場合、文中の「TR32B」を「TR43A」と読み替えてください。

それでは、どうやって実現するかを以下で説明していきましょう。

2.動作概要

2-1. Bluetooth Low Energy通信

Bluetooth Low Energyでは、TR32Bのようなセンサ側はペリフェラルデバイス、ペリフェラルデバイスからデータを取得する側をセントラルデバイスと呼びます。スマホなどはセントラルデバイスの代表例ですが、今回はM5Stackでセントラルデバイスを作成します。

ペリフェラルデバイスは、[図1]のように常時自らの情報を載せたアドバタイジングパケットという信号を送信しています。
TR32Bのアドバタイジングパケットには温度と湿度の測定データが含まれており、M5Stackはアドバタイジングパケットを受信し、その中の測定値を表示する動作をします。

図1:アドバタイジングパケットの送信イメージ

2-2. WBGT(暑さ指数)について

WBGTは湿球黒体温度(Wet Bulb Globe Temperature)の略で、熱中症を予防する目的でアメリカで提案された指数です。日本では暑さ指数とも呼ばれています。
熱中症予防の指針として有用とされ、日射の無い室内に限っては温度と湿度から簡易的に推定できるとのことなので、今回はTR32Bの測定値から求めました。

WBGTの推定は日本生気象学会様の「日常生活における熱中症予防指針 Ver.4」の「室内用のWBGT簡易推定図 Ver.4」を参考に行っています。
また、環境省の「熱中症予防サイト」「全国の暑さ指数(WBGT)」でのWBGTの区分を参考に、M5StackのカラーLCDで[表1]のように色分けして表示しています。

表1

WBGT(暑さ指数) 基準 スケッチで定義した値
(WBGT_No()関数の返り値)
25未満 注意 0
25~28未満 警戒 1
28~31未満 厳重警戒 2
31以上 危険 3
  • ・「室内用のWBGT簡易推定図 Ver.4」で求めたWBGTは「日射のない室内専用」です。屋外では使用できません。また、室内でも日射や発熱体のある場合は使用できません。そのような環境では、黒球付きのWBGT測定器等を用いて評価して下さい。
  • ・危険、厳重警報等の分類は、日常生活の上での基準であって、労働の場における熱中症予防の基準には当てはまらないことに注意してください。

2-3. グラフ表示について

グラフ表示は、予め選択した1台のTR32Bの測定値に対して行います。温度と湿度は同じスケールの折れ線グラフとして表示し、縦軸の最大値は70でそれ以上の値は上端に張り付いた表示としました。
グラフ表示の左側には縦軸の値を示す「0」「20」「40」「60」の数値を表示し、数値の単位は温度は℃、湿度は%を表しています。
横軸は時間を表し、補助線の間隔は1時間を示し、最大で過去5時間分の折れ線グラフを表示します。
グラフの更新は1分毎に行われます。

3.必要な物

3-1. おんどとりease TR32B

TR32Bは温湿度を内部のメモリーに保存し、後からスマホアプリで変化を確認できる商品です。
スマホアプリとはBluetooth Low Energyを使用して通信を行います。

3-2. M5Stack Basic

M5Stack Basicは54mm角の手のひらサイズのケースの中に、CPU、Bluetooth機能、2インチカラーLCD、無線LAN機能などが搭載されているマイコンモジュールです。
2023年8月時点で6,391円で購入が可能です。

詳細は下記サイトを参照ください。
https://docs.m5stack.com/ja/core/basic_v2.7

3-3. Arduino IDE

M5StackのプログラムはArduino IDEを使って作成します。
(Arduino IDEではプログラムの事をスケッチと呼ぶため、以後この記事の中ではプログラムの事をスケッチと記述します。)
Arduino IDEはスケッチを書くエディタ、スケッチをM5Stackに書き込むツール、シリアルモニタなどが含まれる総合開発環境(Integrated Development Environment)です。

Arduino IDEのインストールについては下記サイトを参照してください。
https://docs.m5stack.com/ja/quick_start/m5core/arduino

インストール直後のArduino IDEのメニューは英語表示となっていますが、下記の設定をすると日本語表示に変更できます。
メニューから[File]→[Preferences]を選択し、表示された設定画面の Language: の選択肢から[日本語]を選択して[OK]ボタンを押す。
以降の説明は全て 日本語表示として説明します。

他にも「M5Stack Arduino」などで検索すると参考になる情報が得られると思います。

本記事を執筆時のArduino IDE の動作環境は以下の通りです。

・Arduino IDE バージョン:2.1.0
・ボードマネージャ M5Stack バージョン:2.0.7
・Arduino IDEのメニューで右記を選択:[ツール]→[ボード]→[M5Stack-Core-ESP32]
・OS:Windows 11 Home

その他、PCとM5Stackを接続するUSBケーブルが必要ですが、M5Stack BasicにはUSBケーブルが付属しています。

4.スケッチの説明

4-1.概要

スケッチを書き始める時はArduino IDEのメニューから[ファイル]→[新規スケッチ] を選択すると中身の無い setup() と loop() が作成されるのでその中にスケッチを書いていきます。
setup() は、M5Stackの電源が入ってから最初に1回だけ実行され、loop() は、setup() 実行後に繰り返し実行されます。

今回のスケッチでは、setup() と loop() の他にも関数があります。
全てを入力するのはちょっと大変なので、後ほど記載する[リスト1]をコピペするなどしてスケッチを書いてみましょう。

スケッチの中で「//」が記載されている場合、その行の「//」以降はコメントとなります。

4-2.スケッチ

以下の[リスト1]が今回作成したスケッチの全体です。

スケッチのダウンロード

リスト1

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
#include <M5Stack.h>
#include <BLEDevice.h>

#define CompanyID 0x0392   // T&D CompanyID

int scanTime = 10; //In seconds
BLEScan* pBLEScan;
uint8_t adv_cnt;  //アドバタイジングスキャン中に検索した機器数カウント
ulong tm;
TFT_eSprite graph1 = TFT_eSprite(&M5.Lcd);

//***** 温度、湿度からWBGTを求める(0~3を返す) *****
uint8_t WBGT_No(float temp , float humi){
  const float WBGT_tbl[17][4] = {
    {20, 35,39,42},
    {25, 33,37,41},
    {30, 32,36,40},
    {35, 32,35,39},
    {40, 31,34,38},
    {45, 30,33,37},
    {50, 29,33,36},
    {55, 29,32,35},
    {60, 28,31,35},
    {65, 27,31,34},
    {70, 27,30,33},
    {75, 26,29,33},
    {80, 26,29,32},
    {85, 25,28,31},
    {90, 25,28,31},
    {95, 24,27,30},
    {100,24,27,30},
    };
    uint8_t i,i2,rtn;
    rtn = 3;   // 条件に合わなかった時の応答(WBGT=31以上とみなす)
    for(i = 0 ; i < 17 ; i++){
      if(humi <= WBGT_tbl[i][0]){
        for(i2 = 0 ; i2 < 3 ; i2++){
          if(temp <= WBGT_tbl[i][i2+1]){
            rtn = i2;
            Serial.printf("WG_hm:%3.1f WG_tp:%3.0f\n",WBGT_tbl[i][0],WBGT_tbl[i][i2+1]);
            i = 17;
            break;
          }
        }
      }
    }
    Serial.printf("WG_rtn= %d\n",rtn);
    return rtn;
}

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      if(adv_cnt++ > 100){
        pBLEScan->stop();
      }
      //Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
    }
}; 

void setup() {
  M5.begin();
  Serial.begin(115200);             // シリアル通信初期設定
  Serial.println("Scanning...");

  graph1.setColorDepth(8);        // グラフ描画の色数は8bit(256色)に指定
  graph1.createSprite(300, 198);  // グラフ描画エリアのサイズは300×198dot

  M5.Lcd.drawString("Scanning...",0,0,4);
  BLEDevice::init("");              // Bluetooth Low Energy初期設定
  pBLEScan = BLEDevice::getScan();  // BLEスキャンオブジェクト取得

  pBLEScan->setActiveScan(false);   // パッシブスキャン
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(3200);
  pBLEScan->setWindow(3200);
}

void loop() {
  uint8_t i;
  static uint8_t mode = 0,count,TargetCnt = 0,TargetNo;
  static BLEScanResults foundDevices;
  static BLEAdvertisedDevice d;
  static uint32_t siri[8];      // アドバタイジングスキャンで検索したTR32BのシリアルNo
  static uint16_t Xpos = 0,Xpos_bf = 0,tp_y_bf = 0,hm_y_bf = 0;  // グラフプロット位置の変数

  Serial.printf("mode= %d\n",mode);
  switch(mode){
    case 0:
      if(tm == 0)tm = millis() + 60000;     // グラフ更新する時間を60秒後に設定する
      adv_cnt = 0;
      foundDevices = pBLEScan->start(scanTime); // アドバタイジングスキャン開始
      count = foundDevices.getCount();
      // count=スキャンで見つけたデバイス数//Serial.printf("count= %d\n",count);
      if(TargetCnt != 0)mode = 2;       // 対象機器1台が確定したらグラフ表示(mode=2)
      else mode = 1;                    // 未確定なら機器選択画面(mode=1)
      break;

    case 1:
      for(i = 0 ; i < count ; i++){            // デバイスの個数の回数繰り返す
        d = foundDevices.getDevice(i);
        if(d.haveManufacturerData()){
          //### アドバタイジングパケットにManufacturerDataが含まれる場合以下実行 ###

          std::string data = d.getManufacturerData(); // data=受信したManufacturerData
          int manu = data[1] << 8 | data[0];          // manu=受信したCompany ID
          if((manu == CompanyID)&&((data[4] >= 0x44)&&(data[4] <= 0x47))){  
            //### ManufacturerDataがTR32B/TR43Aならば以下実行 ###
            siri[TargetCnt] = ((uint32_t)data[5]<<24)+
                ((uint32_t)data[4]<<16)+((uint32_t)data[3]<<8)+data[2];
            M5.Lcd.drawString("------ Select Device ------",0,1,4);     // 機器選択画面のタイトル
            M5.Lcd.setCursor(25,(TargetCnt+1)*26,4);
            if((data[4] == 0x44)||(data[4] == 0x45))     M5.Lcd.print("TR43A");  // 機種はTR43A
            else if((data[4] == 0x46)||(data[4] == 0x47))M5.Lcd.print("TR32B");  // 機種はTR32B
            M5.Lcd.printf(" S/N %08X",siri[TargetCnt]); // シリアルNo表示
            if(TargetCnt < 7)TargetCnt++;
          }
        }
      }
      if(TargetCnt == 0)mode=0;   // 1個も見つからない場合は再度スキャン
      tm = millis() + 10000;      // グラフ表示に遷移するまでの時間を10秒後に設定
      TargetNo = 1;
      M5.Lcd.drawString(">",1,26,4);          // 機器選択のカーソルの初期表示
      M5.Lcd.fillRect(40, 230, 50, 10, RED);  // スイッチ位置を示す赤いマーク
      while(mode==1){
        M5.update();
        if(M5.BtnA.wasPressed()){     // M5StackのAボタン(左)押下したか判断
          TargetNo = TargetNo + 1;
          if(TargetCnt < TargetNo)TargetNo = 1;
          for(i = 1 ; i < 8 ; i++){
            if(i == TargetNo)M5.Lcd.drawString(">",1,TargetNo*26,4);  // 選択行はカーソル表示
            else M5.Lcd.fillRect(0,i*26,20,26,BLACK);       // 選択行以外はカーソル消去
          }
          tm = millis() + 5000;
        }
        if(millis() > tm){            // スイッチ待ち時間が終了したか
          M5.Lcd.fillScreen(BLACK);   // 画面を黒く塗りつぶす(画面クリア)
          mode = 2;
        }
      }
      break;

    case 2:
      if(Xpos < 299){
        Xpos++;
      }
      else {
        graph1.scroll(-1, 0);
        Xpos = 299; Xpos_bf--;
      }
      for(i = 0 ; i < count ; i++){            // デバイスの個数の回数繰り返す
        d = foundDevices.getDevice(i);
        if(d.haveManufacturerData()){
          //### アドバタイジングパケットにManufacturerDataが含まれる場合以下実行 ###
          std::string data = d.getManufacturerData(); // data=受信したManufacturerData

          int manu = data[1] << 8 | data[0];          // manu=受信したCompany ID
          if((manu == CompanyID)&&((data[4] >= 0x44)&&(data[4] <= 0x47))){
            //### ManufacturerDataがTR32B/TR43Aならば以下実行 ###
            uint32_t siri_tmp = ((uint32_t)data[5]<<24)+
                ((uint32_t)data[4]<<16)+((uint32_t)data[3]<<8)+data[2];
            if(siri[TargetNo-1] == siri_tmp){
              float temp = data[11] << 8 | data[10];  // ManufacturerData →温度を取得
              temp = (temp - 1000) / 10;              // 温度データをXX.X[℃]の値に変換
              float humi = data[13] << 8 | data[12];  // ManufacturerData →湿度を取得
              humi = (humi - 1000) / 10;              // 温度データをXX.X[%]の値に変換

              Serial.printf("Xpos_bf = %d tp_y_bf = %d\n",Xpos_bf,tp_y_bf);

              uint16_t tp_cal = 0;
              if(temp < 70)tp_cal = 200-int((temp+10)*2.5);   // 温度→グラフ描画のY座標算出
              uint16_t hm_cal = 0;
              if(humi < 70)hm_cal = 200-int((humi+10)*2.5);   // 湿度→グラフ描画のY座標算出

              if(tp_y_bf == 0){                           // グラフの左端描画はプロットのみ
                graph1.fillRect(Xpos, tp_cal,2,2,MAGENTA);       // 温度グラフのドット
                graph1.fillRect(Xpos, hm_cal,2,2,CYAN);          // 温度グラフのドット
              }
              else{                                       // グラフの左端以外はラインを描画
                graph1.drawLine(Xpos_bf,tp_y_bf,Xpos,tp_cal,MAGENTA);     // 温度の折線
                graph1.drawLine(Xpos_bf,hm_y_bf,Xpos,hm_cal,CYAN);        // 湿度の折線
                graph1.drawLine(Xpos_bf,tp_y_bf+1,Xpos,tp_cal+1,MAGENTA); // 温度の折線
                graph1.drawLine(Xpos_bf,hm_y_bf+1,Xpos,hm_cal+1,CYAN);    // 湿度の折線
              }
              Xpos_bf = Xpos;     // 今回のX軸の位置を保存
              tp_y_bf = tp_cal;   // 今回の温度Y軸の位置を保存
              hm_y_bf = hm_cal;   // 今回の湿度Y軸の位置を保存

              graph1.pushSprite(18, 40);  // グラフ描画の左上の座標を指定(X,Y)
              Serial.printf("temp_tate = %d humi_tate = %d\n",tp_cal,hm_cal);

              M5.Lcd.drawRoundRect(17, 38, 302, 202, 2, WHITE);  // グラフ白枠表示
              M5.Lcd.setTextColor(GREENYELLOW);   // シリアルNo.の文字色指定
              M5.Lcd.setCursor(38,39,2);          // シリアルNoの表示位置を指定
              if((data[4] == 0x44)||(data[4] == 0x45))     M5.Lcd.print("TR43A");  // 機種はTR43A
              else if((data[4] == 0x46)||(data[4] == 0x47))M5.Lcd.print("TR32B");  // 機種はTR32B
              M5.Lcd.printf(" S/N %08X",siri[TargetNo-1]);  // シリアルNo表示

              M5.Lcd.setTextColor(DARKGREY);                  // 縦軸数値の色
              M5.Lcd.drawString("60",0,55,2);                 // 縦軸数字「60」
              M5.Lcd.drawString("40",0,105,2);                // 縦軸数字「40」
              M5.Lcd.drawString("20",0,155,2);                // 縦軸数字「20」
              M5.Lcd.drawString(" 0",0,205,2);                // 縦軸数字「0」
              M5.Lcd.drawLine(18, 66, 317, 66, DARKGREEN);    // 横補助線[60]
              M5.Lcd.drawLine(18, 116, 317, 116, DARKGREEN);  // 横補助線[40]
              M5.Lcd.drawLine(18, 166, 317, 166, DARKGREEN);  // 横補助線[20]
              M5.Lcd.drawLine(18, 216, 317, 216, DARKGREEN);  // 横補助線[0]
              M5.Lcd.drawLine(77, 55, 77, 238, DARKGREEN);    // 縦補助線1
              M5.Lcd.drawLine(137, 55, 137, 238, DARKGREEN);  // 縦補助線2
              M5.Lcd.drawLine(197, 39, 197, 238, DARKGREEN);  // 縦補助線3
              M5.Lcd.drawLine(257, 39, 257, 238, DARKGREEN);  // 縦補助線4

              M5.Lcd.fillRect(0, 0, 319, 38, TFT_BLACK);
              M5.Lcd.setTextColor(MAGENTA);
              M5.Lcd.drawString(String(temp,1),17,0,6);M5.Lcd.drawString("C",115,17,4); // 温度を表示
              M5.Lcd.setTextColor(CYAN);
              M5.Lcd.drawString(String(humi,0),155,0,6);M5.Lcd.drawString("%",215,17,4);// 湿度を表示 

              uint32_t clrNo;     // WBGTの値により「WBGT」表示の背景色を設定する変数
              M5.Lcd.setTextColor(WHITE,BLACK); // WBGTの値を表示する文字色と背景色を指定
              switch(WBGT_No(temp, humi)){      // WBGTの値により背景色を選択する
                case 0: clrNo = 0x001F; M5.Lcd.drawString("< 25",275,20,2); break;  //青
                case 1: clrNo = 0xFFE0; M5.Lcd.drawString("25-27",270,20,2); break; //黄
                case 2: clrNo = 0xFDA0; M5.Lcd.drawString("28-30",270,20,2); break; //橙
                case 3: clrNo = 0xF800; M5.Lcd.drawString("30 <",275,20,2); break;  //赤
              }
              M5.Lcd.fillRect(255, 0, 64, 20, clrNo);   // WBGTの背景塗りつぶし
              M5.Lcd.setTextColor(DARKGREY,clrNo);      // WBGT文字色と背景色指定
              M5.Lcd.drawString("WBGT",272,2,2);        // 「WBGT」表示
              M5.Lcd.drawRect(255, 0, 64, 36, clrNo);   // WBGTの枠を表示
            }              
          }
        }
      }
      mode = 0;               // グラフ表示後はアドバタイジングスキャンを行うモードに遷移させる
      while(millis() < tm){}  // 1分間待つ
      tm = 0;
      break;
  }
}

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
            

[リスト1]のスケッチは、[表2]に示した関数で構成されています。

表2

行番号 関数名 機能
13~49 uint8_t WBGT_No() 温度と湿度の値を入力すると、WGBT(暑さ指数)を簡易的に推定する関数です。
出力される簡易的なWBGTは、下記0〜3の4種類です。
WBGT 25未満 →0
WBGT 25~28未満 →1
WBGT 28~31未満 →2
WBGT 31以上 →3
51~58 void onResult() アドバタイジングのスキャンをしている最中に1個検索されるたびに実行される関数です。
実行されるごとにadv_cntというカウンタを加算し、100を超えた場合はアドバタイジングのスキャンを停止します。
60~76 void setup() スケッチを起動したときに最初に1回だけ実行される関数です。
M5StackのカラーLCD表示、Bluetooth通信の初期設定を行います。
78~239 void loop() M5Stackの起動中は連続して常に実行される関数です。
グラフの描画などはこの処理の中で行います。

次に各関数について説明していきます。

4-3. WBGT_No について

[リスト1]13行の WBGT_No() は、「2-2.WBGT(暑さ指数)について」 で説明したとおりに簡易的にWBGTを求めています。
求め方は、日本生気象学会の「室内用のWBGT簡易推定図 Ver.4」をスケッチで扱いやすいデータに変換し、温度と湿度から該当する値を見つけています。
[リスト1]14〜31行がWBGT簡易推定図を元にしたデータとなります。
(「室内用のWBGT簡易推定図 Ver.4」には相対湿度20%と25%のWBGT=31の基準は記載されていなかったため、本スケッチではその部分を推測してデータを補間しています。)

4-4. onResult について

[リスト1]52行の onResult() は、Bluetooth Low Energyのアドバタイジングスキャンを行っている最中にデータを受信するたびに実行される関数です。
周辺にアドバタイジングパケットを送信している機器が多数あったときにM5Stackが再起動してしまうことがあったので今回のスケッチではこの関数を用いて100台以上受信した場合はアドバタイジングスキャンを終了する処理をしています。

4-5. setup について

スケッチを起動すると最初に[リスト1]の60行目からの setup() を実行します。
setup() では、M5StackでBluetoothやカラーLCDなどを動作させるための初期設定を行っています。

[リスト1]62行の Serial.bigin(115200) は115200bpsでシリアル通信する為の初期設定です。シリアル通信はスケッチの動作確認の為に使用し、Arduino IDEのメニューから [ツール]→[シリアルモニタ]で開いた画面に動作状況を出力させる事ができます。
Serial.println("Scanning...") と記述するとシリアルモニタに「Scanning…」と表示されます。
スケッチの途中に上記のような記述を追加しておくことで、スケッチが実行されている途中の状況を確認することができる仕組みです。

[リスト1]65〜66行は、グラフ描画に「スプライト」を使う初期設定をしています。
「スプライト」とは、M5StackのLCDに表示する異なる複数の画面を用意し、それを合成して表示させる処理を指します。
今回のスケッチでは、1分ごとスクロールして変化するグラフのプロットと、変化しないグラフの枠や補助線とを別画面として合成して表示しています。

[リスト1]68行の M5.Lcd.xxxxxxx という部分はM5StackのLCD表示関連のコマンドで、LCDの初期設定と「Scanning...」と表示させる処理を行っています。

[リスト1]72〜75行ではBluetoothの初期設定を行っています。
アドバタイジングパケットをパッシブスキャンという設定でスキャンする設定や、アドバタイジングスキャンの実行中に、機器を受信する毎に51~58行目のMyAdvertisedDeviceCallbackのonResultが実行されることなどを設定しています。

4-6. loop について

Loop関数は、modeという変数を用意し、そのmodeの値により処理を切り替えています。
切り替えている処理の概要を[表3]に示します。

表3

modeの値 処理概要
0 アドバタイジングスキャンを約10秒間行う。
アドバタイジングスキャン終了後、下記のようにmodo=1又は2に移行させる。

グラフ表示する1台のTR32Bが選択されていない場合は、mode=1に、選択されている場合はmode=2に移行する。
1 アドバタイジングスキャンで検索されたTR32Bをリストで表示する。
その時M5Stackの左側スイッチを押すたびにリストの選択行が切り替わり、グラフ表示する対象の1台を選択させる。
対象の1台を選択して5秒間又は10秒間放置するとmode=1の処理を終了する。
2 TR32Bの測定値を使用し、グラフと簡易的なWBGTの表示を行う。

Mode毎のフローチャートを[図2]フローチャートに示します。

図2:フローチャート

4-6-1. mode=0

loopを実行すると、最初はこのmode=0の動作からスタートします。
[リスト1]89行では、1分間のタイマtmを設定しています。
この1分がアドバタイジングスキャンとグラフ画面の更新を行う周期です。

[リスト1]91行の pBLEScan->start(scanTime) でアドバタイジングパケットのパッシブスキャンを起動しています。「scanTime」はスキャンをする時間を秒で指定し、今回は10秒と指定しています。
アドバタイジングスキャンが終了すると、受信した機器台数を変数countに保存します。

その後、次に処理する内容により、mode=1かmode=2かを切り替えています。

グラフを描画する対象の機器が選択されていない場合はmode=1に遷移、選択済みの場合はmode=2に遷移してそれぞれの処理を実行する流れとなります。

4-6-2. mode=1

mode=0でアドバタイジングスキャンが行われ、グラフを描画をする対象の機器1台が選択されていない場合、このmode=1の処理を行います。
ここでは、アドバタイジングスキャンにより検索されたTR32BのシリアルNo.のリストをLCDに表示し、M5StackのAボタン(左側のボタン)を押す度にシリアルNo.の左側のカーソルが移動し、グラフ描画する対象の機器を選択することができます。
最初にカーソルが表示されたTR32Bを選択する場合は何もする必要はありません。
この画面のまま5〜10秒放置すると、グラフを描画するmodo=2の処理に切り替わります。

アドバタイジングスキャンで得られるTR32BのManufacturerDataは[表4]となります。

4-6-3. mode=2

mode=0でアドバタイジングスキャンが行われ、グラフを描画をする対象の機器1台が選択されている場合このmode=2の処理を行います。
アドバタイジングスキャンでの検索結果から、グラフを描画する対象のシリアルNo.のデータを見つけ出し、温度、湿度の測定値を取得します。
[リスト1]162〜165行では、ManufacturerData から温度と湿度の測定データを抜き出し、それぞれ温度は[temp]、湿度は[humi]という変数に格納しています。
167行では[temp]、[humi]の値を、動作確認の為に62行で設定したシリアル通信で出力しています。

[リスト1]169〜188行は、[temp]、[humi]の値から、グラフにプロットする座標を計算し、グラフを描画しています。
また、[リスト1]191〜210行は、グラフ表示の外枠や補助線、縦軸の値を示す20〜60の数値などを表示しています。

[リスト1]212〜216行ではM5StackのカラーLCDに温度[temp]、湿度[humi]の値を表示します。

[リスト1]の218~229行は、
[temp]、[humi]の値からWBGT(暑さ指数)を求めて表示しています。
WBGTを求めるために、220行で「4-3.WBGT_Noについて」で記述したWBGT_No()関数を使用しています。

[リスト1]235行は、アドバタイジングスキャンを行いTR32Bから測定値を取得してグラフ表示を更新するという一連の動作を1分間隔で行う為の時間調整(Wait処理)を行っています。

以降は、1分ごとに下記の動作をくり返します。

mode=0でアドバタイジングスキャン mode=2で選択したTR32Bの測定値とグラフを描写。

表4 : TR32B/TR43AのManufacturerData

byte位置 形式 内容  
0~1 Uint16 Company ID 0x0392→T&D
2~5 Uint32 機器シリアルNo. TR32B: 0xXX46XXXX or 0xXX47XXXX
TR43A: 0xXX44XXXX or 0xXX45XXXX
6 Byte 制御コード  
7 Byte 各種カウンタ  
8 Byte 状態コード1  
9 Byte 状態コード2  
10~11 Uint16 測定値1 温度 測定値
12~13 Uint16 測定値2 湿度 測定値
14~15 Uint16 測定値3  
16~17 Uint16 測定値4  
18~19 Uint16 検査用  

5.実行

5-1. コンパイルと書き込み

スケッチの入力が終わったら、PCとM5StackをUSBケーブルで接続し、Arduino IDEのメニューから [スケッチ]→[書き込み] を実行します。(又は上部の丸い右矢印(→)のスイッチを押下)
実行するとPCの画面右下に「スケッチをコンパイル中...」→「書き込み中」→「書き込み完了」と処理状態を示すメッセージが変化していきます。
「書き込み完了」と表示されたら自動的にM5Stackはスケッチ通りの動作を開始します。

ただし、途中でエラーが発生した場合、「書き込み完了」に至らないこともあります。
その場合、原因として[表5]の状況が考えられるので確認してみてください。

表5 : Arduino IDEエラー例

Arduino IDE
画面下部の出力の表示
対処内容
exit status 1
・・・
プログラムの記述が間違っている。
「exit status 1」以下の記述やリストの色が変わっている箇所を確認。

メニューの [ツール]→[ボード] でM5Stack-Core-ESP32 が選択されているか確認。
M5Stack-Core-ESP32が選択肢にない場合、下記を参考にM5Stackライブラリをインストールする。
https://docs.m5stack.com/ja/quick_start/m5core/arduino
Failed uploading: no upload port provided M5StackがPCとUSBケーブルで接続されているか確認。
[ツール]→[ポート]: M5Stackが接続されているCOMポートの選択を確認。
Failed uploading: uploading
error: exit status 1
M5Stickへのスケッチの書き込みに失敗しました。
下記の様に書き込み時の通信スピードを下げて確認。

メニューの[ツール]→[Upload Speed]→[115200]

5-2. 動作確認

M5Stackでスケッチが動作し始めると、M5StackのLCDに「Scanning…」と表示されます。
この状態で付近のTR32Bを見つけると、見つけたTR32BのシリアルNo.がLCDに表示されます。

TR32Bが付近に複数ある場合、複数のシリアルNo.がリスト表示されます。その場合、M5StackのボタンA(左ボタン)を押す事で1台のTR32Bを選択する事ができます。
ここで選択した1台のTR32Bに対してグラフ描画を行います。
この状態で5〜10秒ほど放置すると自動的にグラフ表示に切り替わります。

TR32Bのアドバタイジングパケットの温湿度データは1分毎に更新されるため、M5Stackのグラフ描画の更新も1分周期としています。

M5StackをPCに接続した状態で、Arduino IDEのメニューから [ツール]→[シリアルモニタ] を実行し、シリアルモニタ画面右上の設定を「115200 baud」に変更してみてください。
スケッチの中で Serial.print… と記述している内容がシリアルモニタに表示され、スケッチの実行状況を確認することができます。

今回のスケッチをM5Stackの内蔵バッテリーで動作させた場合、長くて1時間程度の電池寿命と思われるので、USB等で給電して使用して頂くのが実用的かと思います。

6.最後に

今回は、TR32Bの測定値を単に表示するだけでなく、測定値を加工して表示するというM5Stackを使うからこそ実現可能なことを紹介しました。
無事M5StackのLCDにTR32Bの測定値、グラフ、簡易的なWBGTなどを表示させる事ができたでしょうか。
気軽に使える「おんどとりease」とM5Stackなどのようなマイコンモジュールを組み合わせて活用していただくきっかけになることを願っています。

免責

本記事の中で紹介したスケッチは、全ての環境、条件において動作する保証をするものではありません。
また、記載されている情報の利用やスケッチの実行等による万一の不利益に対して、弊社では責任を負いかねます。

プライバシー設定

プライバシー設定

当ウェブサイトでは、サイトの利便性やサービスの向上を目的に、cookieを使用しております。このまま当ウェブサイトをご利用になる場合、cookieの使用に同意いただいたものとみなされます。cookieに関する詳細や設定については「個人情報保護方針」をご覧ください。

保存する