スケッチを書き始める時はArduino IDEのメニューから[ファイル]→[新規スケッチ] を選択すると中身の無い setup() と loop() が作成されるのでその中にスケッチを書いていきます。
setup() は、M5Stackの電源が入ってから最初に1回だけ実行され、loop() は、setup() 実行後に繰り返し実行されます。
リスト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]13行の WBGT_No() は、「2-2.WBGT(暑さ指数)について」 で説明したとおりに簡易的にWBGTを求めています。
求め方は、日本生気象学会の「室内用のWBGT簡易推定図 Ver.4」をスケッチで扱いやすいデータに変換し、温度と湿度から該当する値を見つけています。
[リスト1]14〜31行がWBGT簡易推定図を元にしたデータとなります。
(「室内用のWBGT簡易推定図 Ver.4」には相対湿度20%と25%のWBGT=31の基準は記載されていなかったため、本スケッチではその部分を推測してデータを補間しています。)
[リスト1]52行の onResult() は、Bluetooth Low Energyのアドバタイジングスキャンを行っている最中にデータを受信するたびに実行される関数です。
周辺にアドバタイジングパケットを送信している機器が多数あったときにM5Stackが再起動してしまうことがあったので今回のスケッチではこの関数を用いて100台以上受信した場合はアドバタイジングスキャンを終了する処理をしています。
[リスト1]62行の Serial.bigin(115200) は115200bpsでシリアル通信する為の初期設定です。シリアル通信はスケッチの動作確認の為に使用し、Arduino IDEのメニューから [ツール]→[シリアルモニタ]で開いた画面に動作状況を出力させる事ができます。
Serial.println("Scanning...") と記述するとシリアルモニタに「Scanning…」と表示されます。
スケッチの途中に上記のような記述を追加しておくことで、スケッチが実行されている途中の状況を確認することができる仕組みです。
[リスト1]68行の M5.Lcd.xxxxxxx という部分はM5StackのLCD表示関連のコマンドで、LCDの初期設定と「Scanning...」と表示させる処理を行っています。
[リスト1]91行の pBLEScan->start(scanTime) でアドバタイジングパケットのパッシブスキャンを起動しています。「scanTime」はスキャンをする時間を秒で指定し、今回は10秒と指定しています。
アドバタイジングスキャンが終了すると、受信した機器台数を変数countに保存します。
グラフを描画する対象の機器が選択されていない場合はmode=1に遷移、選択済みの場合はmode=2に遷移してそれぞれの処理を実行する流れとなります。
mode=0でアドバタイジングスキャンが行われ、グラフを描画をする対象の機器1台が選択されていない場合、このmode=1の処理を行います。
ここでは、アドバタイジングスキャンにより検索されたTR32BのシリアルNo.のリストをLCDに表示し、M5StackのAボタン(左側のボタン)を押す度にシリアルNo.の左側のカーソルが移動し、グラフ描画する対象の機器を選択することができます。
最初にカーソルが表示されたTR32Bを選択する場合は何もする必要はありません。
この画面のまま5〜10秒放置すると、グラフを描画するmodo=2の処理に切り替わります。
アドバタイジングスキャンで得られるTR32BのManufacturerDataは[表4]となります。