Arduinoを USB/HIDデバイス(仮想キーボード)として活用する(第六回）ロギングとプロット③


##本記事について
　Arduinoを USB/HIDデバイスとして活用する第六回③です。[第六回②はここ](https://qiita.com/pronechia/items/10b61a5c9765edfe9743)。②でロギングした各種データを、プロット描画するところを紹介します。
##第六回）「赤外線によるエアコンのON/OFFと結果のプロット③」
　②で紹介したロギングを実行後、ロギングした周期毎のセンサー値や制御結果をch指定で時系列に取り出し、IDEのシリアルプロッターに出力します。IDEのシリアルプロッターは基本的な機能に限定されるため、その使い方を確認した。

##シリアルプロッターの使い方
　まず、IDEに付属のシリアルプロッターの使い方を説明します。ツールメニューからシリアルプロッタ画面を開いた状態で、プログラムからグラフデータを文字列としてSerial出力すると、グラフを表示することができます。文字列には以下のルールがあります。
・１行目は、各chの名称。chの数だけカンマ区切りで書き出し、改行コードで終了。
・２行目からは、プロットする数値。chの数だけカンマ区切りで書き出し、改行コードで終了。
・これをX軸の数だけ繰り返す。
・X軸は、点の数が目盛りとなる。500点までが１ページに表示され、それ以上はスクロールして最後の５００点が表示される。
・Y軸は、書き出した数値が目盛りとなる。全体が表示されるようオートスケールされる。

１）シリアルプロッタ画面を開く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/4359c658-61d4-f746-d22b-c2c45008f3cb.png)

２）入力フィールドにページ番号”１”を入力する
入力されたページに従って、データをシリアル出力するようにプログラムを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/3a197fee-6cc6-89b8-7cc7-0a4f83151f1d.png)

３）プログラムから、以下のような文字列をシリアル出力するとグラフが表示される
Y_temp_20sec, Y_ONOFFLevel_20sec
27.17, 20
27.18, 20
27.19, 20
27.20, 20
27.20, 20
27.21, 20
27.22, 21
27.21, 21
27.21, 21
・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/ca523c0b-20f4-b1b6-4e93-3c00446634c3.png)
 グラフの色はIDEシリアルプロッタが自動で設定します。一つ目は青、二つ目は赤、、、となります。
　時間を意味するX軸は、周期x５００点がフルスケールとなる。2０秒周期なら2時間46分、１分周期なら約8.3時間を一画面に表示することができる。周期ごとのデータとして、今回は温度、ON/OFFレベルの2つを出力した。

##プロット表示プログラム
　指定されたページをシリアルプロットするプログラムを説明します。ソースコードは、GitHubから取り出すことができます。 

```ruby:シリアルプロット処理前半
//EEPROMから値を取り出しシリアルプロット出力する
void sub_EEPROM512_plot(char pPage) {
  unsigned long p_s;
  unsigned long p_e;
  int16_t p_c;
  int16_t p_n;
  int16_t p_t;
  int16_t p_h;
  int16_t p_b;
  int8_t p_count;
  int wmax;
  int w_start;
  int w_end;
  int w_page;
  //提供関数を使ってヘッダー読み取り
  sub_EEPROM512_ReadLogHeader(gPlotNumber,&p_s, &p_e, &p_c, &p_n);
  //
  Serial.println("Y_temp_10sec, Y_ONOFFLevel_10sec, Y");  //Y軸出力 
  wmax = p_n;
  w_page = pPage - 48; //数字に変換。 char to int
  //page オーバー
  if (((wmax - 1) / 500 + 1) < w_page) {
    Serial.printf("sub_EEPROM512_plot page over ");
    Serial.println("");
    return;
  }
  //500区切り
  w_start = w_page * 500 - 500;
  w_end =  w_page * 500 - 1;
  if (w_end > wmax) w_end = wmax;
  int w_kari = 20; //初期値はOFF
  int j;
```
提供関数を使ってヘッダーを読み、該当ページに含まれるデータ範囲を把握する。１ページ目は1-500,２ページ目は501-1000 。。。　となる。

```ruby:シリアルプロット処理後半
//ページ内の値を時系列で取り出す
  int w_kari = 20; //初期値はOFF
  int j;
  for (j=w_start; j<w_end; j++) {
    //提供関数を使ってch値を取得
    sub_EEPROM512_ReadLogData(j, &p_t, &p_h, &p_b, &p_count);
    //今回は気温と制御値のみ使用する
    float ft = float(p_t/100.0);
    Serial.print( ft, 2);
    Serial.print(", ");
    //制御値を　エッジからレベルに変換
    if (p_count == 1) { w_kari = 21;
    } else if (p_count == 2) { w_kari = 20:  }
    Serial.printf("%d", w_kari);  //edge ではなく、レベルで表示
    Serial.println("");
  }
  if (j >= wmax) {
    //最後のページは wmaxまでダミー出力
    // Y軸の中心付近の値（20)が見栄えばいい
    p_t = 20;
    w_kari = 20;
    for (int k= (w_end - w_start); k<(500 - 1); k++) {
      Serial.printf("%d",p_t);
      Serial.print(", ");
      Serial.printf("%d", w_kari);
      Serial.println("");
    }
    return ;
  }
}
```
ページに該当する点数分、ログデータをEEPROMから読み出し、必要なch値を Serial.print 関数で出力れば、グラフが描画されます。グラフを見やすくするため、制御値のON/OFFを　２０/２１という値のレベル表示に加工しています。

##ch追加例
②で紹介したように、INFO情報という仕組みを導入したことで、　データの追加／変更が簡単にでき、EEPROMの物理アドレスを気にすることなくアプリ実装ができるメリットがあります。その具体例を説明します。 
　BME280で取得した　気圧データを追加するには、以下の修正をおこないます。
１）Info定義
 気圧データをラベル名 "LP" として追加する

```ruby:気圧を追加したinfo情報
//初期化にて、設定する情報の例
//BME280で取得した温度、湿度、気圧を共有する
#define MAPDT     0x0040
#define MAPDH     0x0042
#define MAPDP     0x0044  //気圧追加
#define MAPDC     0x0046
#define MAPDY     0x0047
//周期データは、温度、湿度、制御値
#define MAPLOGK     0x0000
#define MAPLOGL     0x0002
#define MAPLOGC     0x0003
#define MAPLOGP     0x0004  //気圧を追加
//個別データのchごとの情報
  strcpy(tableInfo[0].label , "Dtemp");
  tableInfo[0].address = MAPDT;
  tableInfo[0].len = 2;
  strcpy(tableInfo[1].label , "Dhum");
  tableInfo[1].address = MAPDH;
  tableInfo[1].len = 2;
  strcpy(tableInfo[2].label , "Dpress");　  //気圧追加
  tableInfo[2].address = MAPDP;           //気圧追加
  tableInfo[2].len = 2;                   //気圧追加
  strcpy(tableInfo[3].label , "Dctl");
  tableInfo[3].address = MAPDC;
  tableInfo[3].len = 1;
  strcpy(tableInfo[4].label , "Dyobi");
  tableInfo[4].address = MAPDY;
  tableInfo[4].len = 1;
  tableInfoCount = 5;
//周期データのchごとの情報
  strcpy(tableLogInfo[0].label , "LK");
  tableLogInfo[0].address = MAPLOGK;
  tableLogInfo[0].len = 2;
  strcpy(tableLogInfo[1].label , "LL");
  tableLogInfo[1].address = MAPLOGL;
  tableLogInfo[1].len = 1;
  strcpy(tableLogInfo[2].label , "LC");  //controll値　エアコン on/off
  tableLogInfo[2].address = MAPLOGC;
  tableLogInfo[2].len = 1;  // int8_t とする 
  strcpy(tableLogInfo[3].label , "LP");  //気圧追加
  tableLogInfo[2].address = MAPLOGP;    //気圧追加
  tableLogInfo[2].len = 2;              //気圧追加
  //tableLogInfoCount = 4
  tableLogInfoCount = 6  //気圧追加
//周期データの管理情報
  tableLogRepeat[0] = 2048; //最大個数
  tableLogRepeat[1] = 2048;
  tableLogRepeat[2] = ２０４８;
  tableLogStartAddr[0] = 0x4000; //先頭アドレス
  tableLogStartAddr[1] = 0x8000;
  tableLogStartAddr[2] = 0xc000;
```

２）ロギング実行
提供されている sub_SetLogVal()関数を使って、周期ごとのデータをEEPROMに書き出す。lib_EEPROM512.inoファイルのsub_EEPROM512_WriteLogData()関数内を修正する。

```ruby:気圧データをログに書く
//周期=gCount番目のchデータを書く
  int16_t p_p;  //気圧
  char* p = (char*)&p_p;
  sub_SetLogVal(gCount, "LP", p); //気圧
```

３）プロット出力用LOGDATA取り出し関数修正
 気圧データも同時に取り出すため、lib_EEPROM512.inoファイルの　sub_EEPROM512_ReadLogData(j, &p_t, &p_h, &p_count);を流用して、sub_EEPROM512_ReadLogData２(j, &p_t, &p_h, &p_b, &p_count);　を作成する。

4)プロット出力処理
　lib_EEPROM512.inoファイルの　sub_EEPROM512_plot(char pPage) 関数内に気圧データを追加する。

```ruby:sub_EEPROM512_plotの一部
    //気温を真似て気圧を追加する
    float ft = float(p_t/100.0); //気温
    Serial.print( ft, 2);        //気温
    Serial.print(", ");          //気温
    float fb = float(p_p);       //気圧追加
    Serial.print( fb, 2);        //気圧追加
    Serial.print(", ");          //気圧追加
```

##工夫した点
①ツールがシンプルで凝った表示ができないため、凡例を見ただけで、データ種類／実行周期がわかるように命名ルールを工夫した。データの内容がわかる名称+周期を名称とした。例えば、Y_temp_20sec は、温度_20秒周期を意味する。
②X軸：500以上は右に追加でながれていくので１ページ500点として、ページ指定で出力するようにした。
③Y軸：自動スケールになるため、かけ離れた値はほとんど同じ値となりわかりにくい。例えば気温(25度前後)と気圧（1000-1020)をプロットする場合は、気温の値に近くなりように、1/100などすると見やすくなる。今回は,ON/OFFと温度変化をみやすくするため、Y軸目盛りが10から30におさまるように、本来　ON=1,OFF=0 という値をとるところを、２０、21といった値に嵩上げした。

##実行結果
7月3日の昼間実行。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/4c86e0ac-6c0b-abe8-e591-8ac02d89baea.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/f64cf4ad-4b48-2443-6c68-5a203b5c4c74.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/95c7199d-9074-3b6f-4949-23eb9a2dba12.png)

##まとめ
　IDEのシリアルプロッターは機能が基本的なものに限られるが、簡単なプロットがあれば、手軽に実現できることが確認できた。描画の仕組みとしては Processing / ウェブ上のサービスなども興味がある。
　ArduinoをUSB/HIDデバイス（仮想キーボード）として活用する一連の事例によって、自動でエアコンの温度調整するところまで、具体的に実現してきた。ロギングや結果プロットなど、IOTシステム開発に欠かせないツールも紹介することができた。

##参考
赤外線リモコンの通信フォーマットについては、以下を参考にしました。
　http://elm-chan.org/docs/ir_format.html

音量調整ボタンについて、以下を参照にしました。
　「ArduinoでUSB接続のPC音量調整ボタンを作る方法」
　https://qiita.com/kwbt/items/0c9f930a236ca989e402

Trinket M０の開発環境については、以下を参考にしました。
　https://www.denshi.club/cookbook/arduino/trinketm0/trinket-m01arduino-ide.html

２つのライブラリが必要です。以下を参考にしました。
　・ZeroTimer.h
　　　https://ehbtj.com/electronics/arduino-zero-timer-interrupt-library/
　・SparkFunBME280.h
　　　BME280のライブラリーは豊富にでているので、GitHubなどで探してみてください。

以上