お薬飲んだ記録をするものを作りたい
M5Stackを入手したので、画面とボタンが付いているメリットを最大に活かせる投薬管理アプリを作りました。
M5Stackでお薬飲んだ履歴を記録·確認するアプリ作ってみた。 pic.twitter.com/D4Fwq5OA42
— とりてん (@s51517765) 2021年5月1日
しろいとり子さんのブログを大いに参考にさせていただきました。
siroitori.hatenablog.com
siroitori.hatenablog.com
VS codeのPlatformIOを使う
ESP32のときも感じましたが、M5StackをArduino IDEでコンパイル・書き込みをするととても遅いです。
ぐぐるとVS codeのPlatformIOという拡張を使うと速い、という情報を見つけたのでこれをインストールします。
↓を参考にVS codeにPlatformIOという拡張をインストールします。
qiita.com
シリアルモニタが文字化けするのでPlatformIOのmonitor_speedを変更します。
qiita.com
プロジェクト内のplatform.iniで以下のようにmonitor_speedを追記します
monitor_speed = 115200
変更内容やコードの規模にもよると思いますが、ほぼほぼ40~60秒程度で書き込みまでできているようです。
Arduino IDEでは数分かかることもあることを考えると、ESP32やM5Stackでは完全にこちらに移行しようと思います。
しかしながら、PlatformIOにはBtnA
が誤作動するBugがあるようです。
VS Codeの拡張のPlatformIOにBugがあるらしい。同じコードをArduino IDEで書き込みすると問題ないことが分かった。
— とりてん (@s51517765) 2021年4月29日
github.com
Issueに上がっているので、いずれ直ることを期待しますがvoid setup()
内でWiFi.setSleep(false);
することで回避できました。
I am using ESP32 library v. 1.0.6 and the "black" M5Stack with the same issue.
Even if I did not noticed the issue on the M5ez demos, I started to have it recently when I decided to start working on the M5 and reinstalled the libraries. Probably I got the new one.
I have added the
WiFi.setSleep(false);
in the Setup() sequence just after my Wire.Begin(); instructions, and it seems to work.
The battery consumption is not an issue for me, since my final device will have to work always connected to the power, and so I will remove the battery at the end of the development prior to install.
PaltformIOではプロジェクトごとにInitializeが必要です。初回はその他インストールが走るのかこれには数分かかります。
M5Srack Core ESP32、Arduinoを選択しプロジェクトを作成します。(Board選択でM5と入力するとジャンプします)
コンパイル・ビルドは画面下のチェック
と→
、Serial Monitorはプラグ?
のアイコンです。
実装していく
基本的にはしろいとり子さんのブログを踏襲していきますが以下の改良を行いました。
・ボタンをM5Stack Basicで3つ使えるようにする
・前回飲んでからの時間を表示できるようにする
・現在時刻を表示する
・バッテリー残量を表示する
・自分好みにリファクタリング
◆画面の回転
M5Stack Basicはボタンが3つあるのでこれを活用したいのですが、このボタンにそれぞれお薬を対応させます。
このため、デフォルトの向きでは都合が悪いので、90°回転させます。
void setup()
内でLCDの向きを指定します。引数が0, 1, 2, 3, 4 …で90°ごとに回転し、4からはミラー反転します。
ここで、デフォルトが1の90°回転した状態のようで、欲しい角度は0でした。
M5.Lcd.setRotation(0); //90°回転
◆GAS(Google app script)とGoogle Spreadsheetとの連携
スプレッドシートを使うときは、どこかで↓のような権限を求める画面が出てくるはずなのですが、なぜか出てこなくて迷いました。
デプロイの管理画面から権限を指定してデプロイしなおすことで権限設定ができました。
スプレッドシートにデータを投げたり取得したりするには、返り値のあるPostで実行します。
GASではどのボタンが押されたかを引数で取得して、お薬ごとに列を分けて日時を書き込み最後の服薬日時と経過時間を算出してJSON形式でreturnします。
M5Stackの電源オンのときは、前回の時刻と経過時間だけが欲しいので、引数を0~2以外(実装では-1)をいれると、書き込みをせずにreturnのみします。
function doPost(e) { var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1'); var params = JSON.parse(e.postData.getDataAsString()); var id = params.id; if(id=="0"){ // データをシートに追加 var range = sheet.getRange("A2"); range.insertCells(SpreadsheetApp.Dimension.ROWS); sheet.getRange(2, 1).setValue(new Date()); // 受信日時を記録 } else if(id=="1"){ var range = sheet.getRange("B2"); range.insertCells(SpreadsheetApp.Dimension.ROWS); sheet.getRange(2, 2).setValue(new Date()); // 受信日時を記録 } else if(id=="2"){ var range = sheet.getRange("C2"); range.insertCells(SpreadsheetApp.Dimension.ROWS); sheet.getRange(2, 3).setValue(new Date()); // 受信日時を記録 } // 最新行の日時セル内容を取得してフォーマット var targetDate0 = sheet.getRange(2,1).getValue(); var dateString0 = ""; if (targetDate0 != null){ dateString0 = Utilities.formatDate(targetDate0,"JST","MM/dd HH:mm"); } var targetDate1 = sheet.getRange(2,2).getValue(); var dateString1 = ""; if (targetDate1 != null){ dateString1 = Utilities.formatDate(targetDate1,"JST","MM/dd HH:mm"); } var targetDate2 = sheet.getRange(2,3).getValue(); var dateString2 = ""; if (targetDate2 != null){ dateString2 = Utilities.formatDate(targetDate2,"JST","MM/dd HH:mm"); } // レスポンス var response = { data: { "0": dateString0 ,"1":dateString1,"2":dateString2}, past:{ "0": get_history(0) ,"1":get_history(1),"2":get_history(2)} }; return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON); } function get_history(num){ var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1'); var range; if(num==0) range = sheet.getRange("A2"); else if(num==1) range = sheet.getRange("B2"); else if(num==2) range = sheet.getRange("C2"); var value = range.getValue(); var lasttime =Utilities.formatDate(value,"JST","MM/dd HH:mm") var timeDelta = new Date()-value; Logger.log(lasttime); var h =parseInt(timeDelta/1000/60/60); var d = parseInt(h/24); var res; if(d>0) res = lasttime.toString() + "("+d.toString()+"days before)"; else res = lasttime.toString() + "("+h.toString()+"h before)"; Logger.log(res); return res; }
GASの動作確認は以下のようなcurlコマンドをコマンドプロンプトで実行します。
このようにすると、GASのdoPost(e)
が呼ばれるようです。
curl -H "Content-Type: application/json" -d '{"Alice":"1", "Bob":"2" }' -L "https://script.google.com/macros/s/****/exec
なのですが、思ったように返ってこなくて悩んでいましたがdoPost(e)
がBugっていたようです。
例外が発生したときに、GASは何も発さずに止まってしまうのでわかりませんでした。
Try~Catchとかを入れておくか、事前に単体テストで確認しておくべきでした。
GASがバグって止まっていたらしいことが判明。
— とりてん (@s51517765) May 1, 2021
前回飲んでからの時間の計算はM5Stack側でやるかGAS側でやるか両方可能ですがここではGASで行うことにしました。
◆現在時刻の表示
あったら便利ぐらいの位置づけです。
ntpサーバーにアクセスして時刻を取得、表示します。
void getTime() { const long gmtOffset_sec = 3600 * 9; //UTC + 9 const int daylightOffset_sec = 3600 * 0; //Summer Timeなし const char *ntpServer = "pool.ntp.org"; configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); struct tm timeinfo; if (!getLocalTime(&timeinfo)) { Serial.println("Failed to obtain time"); return; } Serial.println(&timeinfo, "%m %d %A %Y %H:%M:%S"); M5.Lcd.setCursor(0, LineProgress); M5.Lcd.setTextSize(2); M5.Lcd.setTextColor(CYAN); M5.Lcd.print(&timeinfo, "%m/%d %a %H:%M"); M5.Lcd.setTextSize(3); return; }
◆バッテリー残量の表示
M5Stackはバッテリー駆動が出来るので残量表示はあったほうがいいと思い探してみると、残量をパーセントで出力する機能があります。
これをスマホなどのバッテリー残量表示のようにゲージを#####
のように文字列で作成し表示するようにしました。
void showBattery() { int BatteryLv = M5.Power.getBatteryLevel(); if (BatteryLv < 40) M5.Lcd.setTextColor(RED); else M5.Lcd.setTextColor(GREEN); M5.Lcd.setCursor(0, 0); M5.Lcd.print(BatteryLv); M5.Lcd.print("["); for (int i = 0; i < 5; i++) { if (i * 20 < BatteryLv) M5.Lcd.print("#"); else M5.Lcd.print(" "); } M5.Lcd.println("]"); }
しかし、M5Stack basicのバッテリー容量は標準で110mAhで一方、消費電力が200mAほどあるらしいので、せいぜい30分しか持ちません。
なので実質的にはバッテリー駆動は現実的ではない、と思っています。
USB電源をつないで使用するならバッテリー残量は意味がないですが、モバイルバッテリーで駆動するなら少し意味があるかな、と思っています。
認証情報は別ファイルに記載する
GitHubでソースは公開したいので、認証情報などは.gitignore
したいです。
このため、auth.h
というファイルを作成し、認証情報はこの中に書き込みます。
extern char *ssid = "**********"; extern char *password = "**********"; // GoogleスプレッドシートのデプロイされたウェブアプリのURLを設定 extern char *published_url = "https://script.google.com/macros/s/********/exec";
メインのコードでは↓のようにして読み込ませると、auth.h
の中で宣言した変数がグローバル変数として使えるようになります。
#include "auth.h"
これが最適な手法かどうかはまだよくわかっていません。