2019年12月14日土曜日

卒業研究発表会

今日は卒業研究発表会でした。1年半にわたる研究成果を発表しました。


2019年11月27日水曜日

実証実験5.6.7日目

だいぶGoogleHomeにも慣れてきて、毎日問題なく使えている。
が、たまに「お薬飲んだよ」などと正しい言葉でGoogle Homeに話しかけておらず反応しないことが最終日にもあった。
少し待っての機能があるためその機能がとてもいいと被験者が言っていた。
被験者は高血圧の薬を飲んでいるため、音声で喋って数値を記録できたらいいと言っていた。

2019年11月26日火曜日

ngrokがURLを払い出していなかった!?

久しぶりにngrokをチェックしていたらhttpのURLが払い出されていないことが発覚した。そこで、example.jsのログ(~/pill-reminder/log/example*)を見てみると リスト1のようになっていた。

2019-11-20 12:26:53.814:     undefined/google-home-notifier
2019-11-20 12:26:53.814: GET example:
2019-11-20 12:26:53.815: curl -X GET undefined/google-home-notifier?text=Hello+Google+Home
2019-11-20 12:26:53.815: POST example:
2019-11-20 12:26:53.816: curl -X POST -d "text=Hello Google Home" undefined/google-home-notifier
2019-11-20 12:26:53.820: sendNgrokUrl(undefined/google-home-notifier)
2019-11-20 12:26:55.369: ****** sendNgrokUrl 成功 ******
2019-11-20 12:26:55.446: Google HomeのIPアドレス:
リスト1.example.jsのログ

なんと、11/20の時点でngrokはURLの払い出しに失敗していたのだ!
それに今日の今日まで気が付かなかった。設定ファイル(pill-reminder)も ngrokのURLが
undefined/google-home-notifier
のままになっている。おかしいな・・・。被験者宅に設置したときチェックしたはずなのに・・・。

foreverコマンドを入力してexample.jsをいったん止めて、再び起動した。ngrokダッシュボードではhttpのURLが表示されたが設定ファイルはundefinedのまま。example.jsのログを見てもちゃんとURLが払い出されているし・・・。そこで、もう一度exampleを停止してから起動。今度は最初からundefined。ngrokのダッシュボードで残っていたhttpのURLをクリックしてゾンビを消してから再びexample.jsを落として立ち上げると今度はうまくいった。ngrokのダッシュボードにも設定ファイルにもログにも払い出されたhttpのURLが出ている。

一体何が悪かったのだろう?
今回のトラブルを整理すると次のようになる。
  1. 被験者宅に機器を設定していた段階でngrokはhttpのURLを払い出していなかった
  2. したがって設定ファイルはundefinedのままだった(と思われる)
  3. それに気づいた今日、一度example.jsを落として再び立ち上げたが設定ファイルに反映されなかった
  4. 2度目の停止・立ち上げではundefinedのままだった
  5. ngrokダッシュボードでURLゾンビをけしてから3度目の停止・立ち上げを行うと今度はうまくいった
これはこれまでにも経験的に感じていたことだが、何かのタイミングでngrokがURLを払い出してくれないことがある。
その一つとしてngrokダッシュボードにゾンビが残っているケースがある。ダッシュボードからURLをクリックしてゾンビを消すと次からは払い出しがうまくいくような気がする。もしかして今回も第2クールの時のURLがゾンビとして残っていたのだろうか?それを見てうまく払い出されたと勘違いしていたのだろうか?
とにかくngrokは要注意である。





2019年11月24日日曜日

実証実験4日目

実証実験4日目

この日は日曜日ということでいつもよりも起きる時間が遅いため、前日にカレンダーの時間をずらしておいた。
平日と休日では起床時間が違うという人も多いと思うので毎回手直しするのは少し面倒だと感じた。

2019年11月23日土曜日

実証実験3日目

今日は祝日のため、いつもは6:15に設定していた朝食後の服薬アナウンスを7:30に変更していた(開始時刻:7:30、終了時刻:8:00)。しかし、応答がなく30分を経過した。ところが30分経過した後に応答したため「時間外で無視する」となってしまった。これは、応答時刻がGoogleカレンダーの予定(イベント)の開始~終了時刻の範囲外にあるときにこうなるようにしていたためである。AIスピーカーは5分おきに催促するようになっているので、このような問題が発生しないようにするためには、予定は1時間枠で取る必要がある。

暫定的に手作業で予定の時間枠を1時間に変更した。


この日は家族で泊まりの旅行があったため、起床時間を1時間程度ずらしていた。
しかし、その時間よりも薬を飲む時間が遅くなってしまい時間外になってしまった。
泊まりということでGoogleHomeを持っていけないため、私のGoogleassistantのアプリで服薬の回答をした。

今回のように予定の時間よりも薬を飲む時間がずれていて、それがわかっている時にはわざわざカレンダーの時間を変更しておいたほうがいいのかが分からないためそのままにした。

2019年11月22日金曜日

実証実験2日目

実証実験2日目
今日はご飯を食べる時間が少し遅く、ご飯を食べ終わる前に服薬を促す形になった。
5分に1回いわれるのがうるさく感じたらしい。
そのため、「少し待って」の機能を使用し、時間をずらした。この機能はとても便利でいいと言っていた。

2019年11月21日木曜日

実証実験1日目

実証実験1日目

用法:朝食後
設定時刻:6:15

今日から実証実験がスタートした。
音声一つでできるため簡単に問題なくできた。
だが、最初に「OK,Google」というのを忘れていて、反応しないため「あれ?」となったそう。
天気や他のことも聞くことができるためいいねと言っていた。
今のところ、不満や問題点はない。

2019年11月20日水曜日

実証実験第3クール

明日から実証実験の第3クールを11/27までの1週間開始する。そのために本日被験者宅にシステム一式を設置した。もっとも手間取ったのはGoogleHomeの設定だった。高齢者の誰もがGoogleHomeに精通しているわけではないので、これについては高齢者でも設置・設定できるような簡単なマニュアルが必要と感じた。

次に、ベアボーンの設定については今回はトラブルなく接続できた。その後の設定作業は
  1. GoogleHomeのIPアドレスをスマホアプリで調べてGoogleスプレッドシート(pill-reminder)へ記入する
  2. 発話依頼プログラム(pill-reminder.js)を起動する
の2点である。
ところがこれらを行っても、時刻になったにもかかわらずGoogleHomeは喋らなかった。原因は発話PUSHサーバがスプレッドシートに設定したGoogleHomeのIPアドレスを読み込んでいなかったからである。
本来は先に上記1をやってからベアボーンサバを起動すべきだった。今回は、いったん発話PUSHサーバを停止後、再度起動することによって対応した。
# cd /home/mtanaka/pill-reminder
# forever stop example.js
# forever start example.js

2019年11月19日火曜日

実験6日目、7日目

間が空いてしまったが結果である。  

6日目

・結果としてはすべて済になっていた。
しかし、朝ゼミの教員から連絡がなければ
ベアボーンのコンセントが抜けていたことに気づけなかったと思う。

7日目

・結果としては夜以外は済になっていた。
ただ、「寝る前の薬飲んだよ」を「夜の薬飲んだよ」と何度も話しかけていたため
夜は工夫する必要があるかと思われる。
・また、「ちょっと待って」と話しても10分延ばされなかったので
3回目の実証実験で確認できたらと思う。

【コメント】


「ちょっと待って」ではなく「少し待って」です。こういった表現の揺らぎに対してIFTTTでは3パターンしか登録できないので問題がありますね。
入力された音声を自然言語処理で近い表現に変換できるといいですね。今後の課題に加えましょう。

2019年11月16日土曜日

瀬戸内医療情報ネットワーク勉強会

瀬戸内医療情報ネットワーク勉強会に参加して研究成果を発表しました。


発表を終えて、その日はホンマチバルで打ち上げです。











料理はどれも彩り豊かでおいしかったです。極めつけはこれです。



2019年11月13日水曜日

11/12

今日は、瀨戸ネット前最後のゼミということで最終調節を行った。
実際に発表の時間を測り、少し長かったため不要な部分を削り、時間内に収めれるように修正した。本番の質問対策のプリントを先生に作っていただいたため、それらにも目を通し、自分たちでもほかの質問が来た場合に備える準備をした。

本番は成功するように頑張りたいと思う。

発話PUSHプログラムのログ

発話PUSHプログラム example.js のログが取れていなかった。ログをとるシェルスクリプトにおいて、ログファイル名を取得すべく forever list コマンドを実行しているが、絶対パス指定をしていなかったのが原因だったのかもしれないと思い、絶対パス指定に修正した。これで様子を見てみることにした。
#!/bin/sh

log_file1=`/usr/local/bin/forever list | grep root | /usr/bin/awk '{print $8}'`
log_file2=$(/usr/bin/printf $log_file1 | /bin/sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g")
cp $log_file2 /home/mtanaka/pill-reminder/log/example_`date "+%Y%m%d"`.log
echo '' > $log_file2
リスト1.発話PUSHプログラムのログ取得シェル/root/log-backup.sh

この修正でもダメだった。crontab -e の設定がまずいのかと思い、以下のようにシェルスクリプトの前に/bin/bashを付けてみた。 
0 1 * * * /bin/bash /root/log-backup.sh
これで様子を見てみる。

この方法でもダメだった。そこで、「Cronの使い方とテクニックと詰まったところ」にしたがって /etc/cron.d/内に下記の内容のファイルlog-backupを作成してcronを再起動した。
# /etc/cron.d/log-backup: crontab entries for the pill-reminder package

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 1    * * *   root    /root/log-backup.sh >> /root/log-backup.log 2>> /root/log-backup.err
リスト2./etc/cron.d/log-backup

これで成功した。

2019年11月12日火曜日

プログラムの修正


無応答時の処理変更


  1. 現在、薬を飲んでくださいの呼びかけに対して応答がない場合、LINEへの通知を5回おこなっているが、1回のみとする。
  2. また、薬を飲んでくださいの呼びかけ間隔が1分になっているが5分にする。
上記1の修正はGASの「A3」、上記2の修正はpill-reminder.jsで、いずれもマイナー修正である。

トラブル発生

【現象】


  • 午前5時半ごろ、Teratermでベアボーンサーバに接続したところ、接続できない。
  • ngrokのダッシュボードを確認したところtunnelsが何も表示されていない。
  • 昨夜の午後10時に就寝前のお薬通知は行っている(GoogleカレンダーとGASのログで確認)。

【考えられる原因】


  • 昨日、example.jsのログを取得するスクリプト/root/log-backup.shを修正した。その影響か?
  • ベアボーンサーバが落ちている。

【確認事項】


①ベアボーンサーバーの電源は入っているか(LEDは点灯しているか)?
②入っていなければ電源を入れる。
③入っていたら
・朝のお薬の通知はあったか?
・「昼の薬を飲んだよ」と話しかけたら、「時間外なので無視する」と返答があったか?
④電源を長押しして電源を切り、再度電源を入れて
・「昼の薬を飲んだよ」と話しかけ、「時間外なので無視する」との返答があることを確認する。

【復旧】


午前8時9分にサーバー起動で復旧。ログを見ると前日午後9時17分を最後にログが途絶えている。10時にお薬服用の通知は出しているようなので、その後に何かあったものと思われる。

【原因判明】


電源が抜け落ちていたため。

2019年11月11日月曜日

実験5日目

所感

・結果は昼の薬のみ(応答無)であとは(済)になっていた。
これは家に被験者がいなかったためなので問題はない。
LINEにもそれぞれ通知がきていたので動きに問題はないと思われる。
・GoogleHomeが5回以上催促を始めると10回目まで毎回LINEに通知がきているが10回目だけでいいと思う。
被験者の家族も頻繁にLINEに通知がくるとうっとおしく、通知オフにしてしまう可能性があるのでつ通知は最低限にしたほうがいいのではないだろうか。

実験4日目

所感

結果は全て(済)に変更されLINEにも通知がきており問題なかった。

2019年11月10日日曜日

実験3日目

所感

・結果は朝と夜は(済)に変わっていたが昼と就寝前は(応答無)であった。
どちらも服用者が家にいなかったことが理由で、「昼の薬の時間です」などは2階の自室まで響いてきた。(設置場所は1階)
そのため催促は問題なく行なわれていると思われる。
・服用者が催促を聞き取れていなくても、家族がGoogleHomeの声を聞いていることで薬を飲んだか気にすると思うので、そうしたところもAIスピーカーが催促をするこのシステムのメリットなのかと思った。

2019年11月9日土曜日

実験2日目

所感

・8日の結果は全て(済)になっていた。問題なく服薬が行われたことがわかる。
・被験者が寝ぼけていた時、「OKGoogle薬飲んだよ」などと「夜の」を短縮して呼びかけていた。
もちろんGoogleHomeは理解できないので「わかりません」と返ってくる。

このような呼びかけが異なって上手くいかなかった時、お年寄りは再度、「OKGoogle薬飲んだよ」と呼びかけるかもしれない。
そうするとまた「わかりません」と返されので、GoogleHomeが壊れたのではないかなど勘違いされるケースもでてくるのではないかと思った。

今回は自分が被験者の近くにいたため呼びかけが異なっていることを説明できたが、お年寄りの場合は説明できる家族が近くにいるとは限らない。
そのためもっとGoogleHomeが反応してくれるように言葉を登録しなければいけないと思った。

11月10日追記---
「OKGoogle薬飲んだよ」からカレンダーを書き変えることは困難なので、「OKGoogle薬飲んだよ」と呼びかけがあったときにはその時間帯を読み取って、「もう一度OKGoogle夜の薬飲んだよと言ってください」と再度服用者に呼びかけをするように促せばいいのではないかと思った。

2019年11月8日金曜日

今日から2回目の実証実験を開始します

はじめに


原因は分かりませんがベアボーンの接続が上手くいったので本日から服薬管理システムの実証実験を行っていきます。

GoogleHomeはリビングの机の上に音量最大で設置しています。

 

実験の設定


以下の設定で実験を行う。
  • 処方内容:
    • クラビット細粒10% 100mg(レボフロキサシンとして) 3g 1錠
      用法:1日3回毎食後と就寝前
  • 投与期間:毎日
  • 服用時間:朝=7:00、昼=12:00、夜=19:00、就寝前=22:00

所感


・ 結果としては朝のみ(済)になっていましたが昼から就寝前までは(応答無)でした。
これは被験者が昼は外出、夜は料理中、就寝前の時間は駅まで家族を迎えに行っていたためです。
リビングとキッチンは近いのですが揚げ物を揚げていたため音は一切聞こえていなかったそうです。

薬を催促するタイミングに利用者がGoogleHomeの近くにいるとは限らないため問題であると感じました。

この問題に対しての解決策としては現在はしておりませんがやはりスマホにも通知をし、薬を飲んでから、Googleアシスタントに「薬飲んだよ」と話かけて(服用済)に変更することがいいと思います。
また、現在は5分おきに催促して5回繰り返したら終わりですが、1時間後にもう1度催促してはどうかと思いました。(ただし服薬するのが食後から1時間後でもいいのかは分からない。)

2019年11月6日水曜日

ベアボーンがつながった!実証実験第2弾スタート

ベアボーンサーバをチェックして、もう一度被験者宅に設置したところ、今度はつながった!
ngrokで外部からsshもできるし、ngrokダッシュボードでURLも確認できた。
さらに、時間外に薬を飲んだ旨話しかけるとちゃんと時間外なので無視するとの返答もあった。これで間違いなくすべてがうまく行っている。
昨日つながらなかった原因は不明。明日から1週間実験を行う。

実証実験でベアボーンサーバがネットにつながらない

実証実験のため,2番目の被験者宅にベアボーンサーバを設置し,WiFiルータに接続したが,サーバにpingが通らない。

図1.サーバにpingが通らない

LANケーブルを交換したり,WiFiルータのポートを替えてみたり,ベアボーンサーバの電源のオン/オフをしたり,考えられることはいろいろやってみたがダメだった。あとはベアボーン自体が動いていないか,Wifiルータが特殊な設定になっているかしか考えられない。

図2.Wifiルータ(Aterm WG1800HP2)


諦めてベアボーンを撤収して確認することにした。

2019年11月5日火曜日

本日の作業

11/16(土)の瀬戸内ネットワーク学会で発表するパワーポイントを作成した。
前回のゼミの時にデモンストレーションの動画を撮ったが3分以上あったので撮り直して1分30秒程に短縮して、良いものが出来たと思う。



同時進行で卒業論文も作成した。
AIスピーカーに薬を飲んだことを言うとAIスピーカーが応答するのだがゼミ員の1名だけ声の反応が鈍かった。GoogleHomeの声認識に問題があるのかよく分からなかった。

次回のゼミは、岡山・倉敷の薬局100件にお薬手帳にQRコードが記載されているのかどうかの調査結果をゼミ員で合わせて。統計を取る。
瀬戸内ネットワーク学会に向けて発表の通しも出来たらと思う。

2019年11月3日日曜日

ベアボーンが再起動しない

ベアボーンサーバを
shutdown -r now
で再起動したが,立ち上がらない。

電源長押しで強制的に落とし,再度電源を投入して起動後,再び上記コマンドを入力して再起動した。今度は無事に再起動できた。

長時間起動させたあとで上記コマンドを入力した場合に再起動しないような気がする。あるいは,再起動に時間がかかっているだけなのかもしれない。

2019年11月2日土曜日

発話PUSHプログラム(example.js)がngrokからURLを取得できない

発話PUSHプログラム(example.js)起動時、ngrok.connect関数で下記のエラーが発生した。
Error: connect ECONNREFUSED 127.0.0.1:4041
リスト1.ngrok.connectで発生したエラー

このエラーはこれまでもしばしば遭遇してきたもので、原因はよくわからない。今回は何度再起動してもエラーが解消しないので、パラメタの
region: 'ap'
を削除して再起動すると無事URLが払い出された。
これまでも、ngrokのStatus画面に残っていたURLのゾンビを消すことでこのエラーを解消できることがあったが、今回のは初めてである。

原因がわからないとまた同じような現象に遭遇することがあるかもしれない。

ベアボーンサーバがrebootできない


Teratermからベアボーンサーバに接続して"sudo reboot"したが、再起動できないという現象が発生した。仕方なく電源を強制的に落として再度スイッチを入れた。以前にも"sudo halt"がだめで"sudo shutdown -h now"を使ってシャットダウンしたことがあった。
調べると、再起動は"shutdown -r now"と入力するらしい。 しかし、この記事によれば、"halt"も"reboot"も問題ないとのことだが・・・。タイミングでおかしくなったりするものだろうか?

カレンダーが済にならない


現象


AIスピーカーの「昼の薬を飲んでください」の発話 に対して「昼のお薬飲んだよ」と回答し,AIスピーカーから「カレンダーに記録する」との応答があったにもかかわらず,カレンダーの色は赤のまま(「未」のまま)で,かつ,AIスピーカーが「昼の薬を飲んでください」を言い続ける。

考えられる原因


IFTTTでWebhooksが失敗しているか,GASが正しく機能しなかった。
またまた慌ててログを消したため,調査ができない。次回からログは消すのではなく,必ずバックアップを取ること!

2019年10月31日木曜日

実証実験第2クールに向けての準備

実証実験の第2クールは一般の利用者に対して行う。ここで「一般の利用者」とは、本システムの開発関係者ではないということを意味する。
したがって、システムは設定を含めて自動化をしなければならない。トラブルの発生の都度、システムの設定やプログラムのバグ取りをすることはできない。また、システムの起動停止も自動化しなければならない。
ただし、遠隔メンテナンスができるように外部からssh接続ができることが望ましい。

そのために以下の修正を行った。
  1. OS起動とともに発話PUSHプログラム(~/pill-reminder/example.js)を自動起動した。これは/etc/rc.localに起動コマンドを追加することによって実現した。
  2. 外部からsshでサーバにアクセスできるようにした。そのために、サーバにngrokをインストールし、 /etc/rc.localにngrokを使ってsshをトンネリングするコマンドを追記した。これは/etc/rc.localに起動コマンドを追加することによって実現した。
  3. 起動時に発話依頼プログラム(~/pill-reminder/pill-reminder.js)が自動起動するようにした。※注意点としてこれはngrokの前に記述しなければ実行されない。
  4. 発話PUSHプログラム(~/pill-reminder/example.js)が、自身のIPアドレスを自動取得できるようにgetLocalAddress()関数を追加した。
  5. 発話PUSHプログラム(~/pill-reminder/example.js)が、Google HomeのIPアドレスをGoogleスプレッドシートから取得できるようにした。そのためにWebAPI(A2)にdoGetエントリーを付け加え、Googleスプレッドシートから読み込んだGoogle HomeのIPアドレスを返すようにした。なお、Google HomeのIPアドレスは手作業で設定する。
  6. WebAPI(A2)にGoogle HomeのIPアドレスを返す機能を追加した。
  7. 発話PUSHプログラム(example.js)が、ngrokによって払い出されたURLをGoogleスプレッドシートに記入できるようにした。Googleスプレッドシートはpill-reminder.sheetのシート「設定」である。そのためにWebAPI(A2)のdoPostエントリーを修正してngrok_urlパラメタを受け取るとsetNgrokUrl関数を使って受け取ったURLをスプレッドシートに書き込むよう修正した。
  8. WebAPI(A2)では、GOOGLE_HOME_notify関数で使用する発話PUSHプログラムのURL(ngrokによって払い出されたURL)をGoogleスプレッドシートから取得するように修正した。これによって、発話PUSHプログラムを再起動して払い出しURLが変わっても自動追随ができる。
  9. 発話依頼プログラム(~/pill-reminder/pill-reminder.js) が、自身のIPアドレスを自動取得できるようにgetLocalAddress()関数を追加した。
図1.Googleスプレッドシート

2019年10月29日火曜日

10/16 ビデオ撮影

今後の日程について話し合い、発表に向けてスライド作成、デモのビデオ撮影を行った。ビデオ撮影のみで本日は終了したため、来週はスライド制作を行う。

実証実験の被験者が2人増えたため、そちらについても今後テスト実験を行い、評価してもらおうと思う。

2019年10月28日月曜日

実験9日目

LINEに通知が来ない


現象


朝の薬のアナウンスに対して「朝のお薬飲んだよ」と回答し、AIスピーカーからそれに対する応答(おはようございます。お薬手帳に記録しておきます。)があったにもかかわらず、LINEに通知がこない。

調査


ログ(~/pill-reminder/pill-reminder.log)を調べたところ、次のようになっていた。
2019-10-28 05:34:07.037: "朝の食後の薬"の発話時刻になりました(5回目), id=4bq23ffsj7e6t1d9koos3iegml@google.com
2019-10-28 05:34:08.433: 当該服薬スケジュール(id=4bq23ffsj7e6t1d9koos3iegml@google.com):
{
 "drug_notifies": [
  {
   "title": "朝の食後の薬(済)",
   "description": "ディオバン錠20mg 1錠\nメバロチン錠10 1錠",
   "id": "4bq23ffsj7e6t1d9koos3iegml@google.com",
   "location": "自宅",
   "startTime": "2019-10-27T20:30:00.000Z",
   "endTime": "2019-10-27T21:00:00.000Z",
   "color": "9"
  }
 ],
 "num_reminder": "5",
 "taiking_status": "1",
 "status": "0"
}
2019-10-28 05:34:08.433: 済になったか催促上限オーバーになったので催促を終了します。
リスト1.ログ

5回目のアナウンス時点でtaiking_statusが1になっているので、WebAPI (A2)は正常にIFTTTからのWebhooksを受け付けたものと考えられる。うかつにも、GASのデバッグ用ファイル(debug.gdoc)を消してしまったため確証はない。あくまでもtaiking_statusが1に変わっているという状況証拠のみ。

WebAPI (A2)がIFTTTからのWebhooksを正常に受け付けると、必ずLINEへ通知を送るはず。しかし、通知はなかった。

原因


不明。もし、また同じ現象が発生したら、まずdebugの退避をして後で調査できるように取っておくこと。



2019年10月26日土曜日

実験7日目

想定される問題点


  1. TVなど生活音があるなかで正しく利用者がしゃべる声を聞き取ることができるか。
  2. 複数の利用者が1台のAIスピーカーを共用できるか。ボイスマッチを行うことによって発話者の当日のスケジュールを回答させることができるが、これと同じように1台のAIスピーカーで個人ごとの服薬管理を実現できるか。
  3. QRコードがない場合、どうやって処方内容を入力するか。①手入力、②OCR

上記3については
などのOCRソリューションがある。


2019年10月25日金曜日

実験6日目

問題とその解決策


予定を変更する


WebAPI (A2) の修正


実験2日目のブログで「予定を変更する」機能があるといいのにと書いたが、それを実装した。その方法はGoogle Homeから服用のお知らせがあったのち「後でお薬飲む」と伝えると、Googleカレンダーの開始日・終了日を指定した時間(今回は10分間)だけ遅らせるというものである。

修正対象のプログラムは、WebAPI (A2) である。まず、HTTPリクエストのエンドポイントとなる関数 doPost は以下のように修正する。
/**
 * IFTTTからのリクエストに応じてGoogleカレンダーを服用済みに更新する
 * @param  {Object}   e     POSTメソッドで送られてくるデータ
 *  delay: 10 (開始を10分間遅らせる)
 *  timing: [朝|昼|夜|就寝前]
 *
 */
function doPost(e) {
  var params = JSON.parse(e.postData.getDataAsString());  // ※
  
  if (params.timing) {
    //カレンダーのタイトルと色を変える
    var timing = params.timing;  // => [朝|昼|夜|就寝前]が取れる
    NotifyNotTaking(timing);
  } else if (params.delay) {
    //開始時刻を遅らせる
    var delay = parseInt(params.delay);  // => 遅らせる時間[分]
    delayStartTime(delay);
  }
  
  var out = ContentService.createTextOutput();
  
  //Mime TypeをJSONに設定
  out.setMimeType(ContentService.MimeType.JSON);
  
  //JSONテキストをセットする
  result = {
    status: 'OK'
  };
  out.setContent(JSON.stringify(result));
  
  return out;
}
リスト1.WebAPI (A2) のエントリポイント

パラメータにtimingがなくdelayがある場合、リスト2に示す関数delayStartTimeを呼び出す。
/**
 * 現在時刻の予定を指定した時間だけ遅らせる
 * @param  {Integer}   delay     遅らせる時間(分)
 */
function delayStartTime(delay) {
  debug('delayStartTime:delay=' + delay);
  // 当日の服薬予定(未)をすべて取得する
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(
    new Date(),
    {search: '(未)'}
  );  

  // 取得した予定から現在時刻が開始~終了時刻に含まれる予定を探してdelay(分)だけ遅らせる
  var current_time = new Date();
  events.forEach(function(event,i,array){
    var start_time = event.getStartTime();
    var end_time = event.getEndTime();
    if (start_time <= current_time && current_time <= end_time) {
      event.setTime(delayTime(start_time, delay), delayTime(end_time, delay));
    }
  });
}

// 引数に指定した日時target_timeをdelay[分]だけ遅らせる
function delayTime(target_time, delay){
  var delay_time = new Date(target_time.getYear(), target_time.getMonth(), target_time.getDate(), target_time.getHours(), target_time.getMinutes() + delay, target_time.getSeconds() );
  return delay_time;
}
リスト2.予定時刻を遅らせる関数delayStartTime

当日の予定から'(未)'を含む予定を探し、delayだけ開始・終了時刻を遅らせる。

IFTTTの登録


次に、「後でお薬飲む」とスピーカーに向かって話すと「分かりました。予定を10分遅らせます。」と返答して、Webhooks機能を用いて WebAPI (A3) を呼び出すIFTTTのアクティビティを登録する。

トリガーはGoogle Assistantで、下記のような設定にする。
What do you want to say?
 -> 後でお薬飲む

What's another way to say it? (optional)
 -> 後で飲む

And another way? (optional)
 -> 少し待って

What do you want the Assistant to say in response?
 -> 分かりました。予定を10分遅らせます。

Language
 -> Japanese
リスト3.Trigger

続いて、下記はWeb requestの設定である。
URL
 -> https://script.google.com/macros/s/AKfy..............................nIbMw/exec

Method
 -> POST

Content Type (optional)
 -> application/json

Body (optional)
 -> {"delay": 10}
リスト4.Web request

ここで、URLは WebAPI (A3) のURLである。

ベアボーンサーバ(発話依頼プログラム pill-reminder.js)


ベアボーンサーバ上で稼働する発話依頼プログラム pill-reminder.js は、起動時に読み込んだGoogleカレンダーの予定通りに服薬通知を発話するようプログラムされている。
しかし、服用開始時刻を変更できるようにしたため、それに同期して発話の時刻を変更しなければならない。そこで、予定の開始時間になったら呼び出される関数dosing_remindを以下のように修正した。
function dosing_remind(speech, counter, id) {
 counter++;
 putLog('"' + speech + '"の発話時刻になりました(' + counter + '回目), id=' + id);
 if (counter > 10) {
  putLog('催促回数が上限を超えました');
  return;
 }

 // WebAPI A3 をid指定で叩いて服用済みかどうかを確認する
 webclient.get(
  {
   url: data + "?id=" + id + "&num_reminder=" + counter
  }, 
  function (error, response, body) {
   if(!error) {
    var json = JSON.parse(body);
    putLog('当該服薬スケジュール(id=' + id + '):\n' + JSON.stringify(json, null, " "));
    if(json.taiking_status == "0" && !isOver(json)){
     message = speech + 'を飲んでください';
     putLog(message + '(' + counter + '回目)');
     // まだ開始時刻(json.drug_notifies[0].startTime)になっていなければ、開始時刻にタイマーを設定する
     var startTime = new Date(json.drug_notifies[0].startTime);
     var now = new Date();
     var df = startTime - now;
     if (df > 0) {
      putLog('予定が変更されています。この予定は' + parseInt(df / 60000) + '分後に発話されます。');
      setTimeout(dosing_remind, df, speech, 0, id);//繰り返し
     } else {
      // Google homeへ発話依頼(開始時刻を過ぎている場合)
      setTimeout(dosing_remind, C_INTERVAL, speech, counter, id);//繰り返し
      webclient.get({
       url: url,
       qs: {
        text: message,
       }
      },
      function (error, response, body) {
       if(!error) {
        putLog('発話依頼成功:' + body);
       } else {
        putLog('発話依頼失敗:' + error);
       }
      });
     }
    } else {
     putLog('済になったか催促上限オーバーになったので催促を終了します。');
    }

   } else {
    putLog('WebAPI A3 呼び出しに失敗しました:' + error + '(id=' + id + ')');
   }
  }
 );
}
リスト5.予定の開始時間になったら呼び出される関数dosing_remind




2019年10月24日木曜日

実験5日目

問題とその解決策


間違った応答による誤操作


「就寝前の薬を飲んでください」に対して間違って「夜のお薬飲んだよ」と答えてしまい、夜の予定が「(済)」に変わってしまった。これは実験2日目に起きたのと同じインシデント。

対応策


WebAPI (A2)のNotifyNotTaking()関数でカレンダーのタイトルと色を変更するが、ここで現在時刻が開始時刻(start_time)~終了時刻(end_time)の間にあるかどうか調べて、もしなければ時間外である旨応答する。

ここで、GASからどうやって Google Home に喋らせるかが問題になる。
Google Homeにプッシュ通知する製品にGHKitというものがあるらしい。「外部イベントに応じたプッシュ通知で、好みのメッセージをタイミングよくGoogle Homeで音声再生します!」という触れ込みである。
また、GAS、firebase、google-home-notifierを連携させて指定した時刻になるとGoogle Spreadsheetに入力したテキストをGoogle Homeに喋らせるアプリの開発例がある。

我々の開発しているシステムは、firebaseは使っていないが、どちらかというと後者に近い。ベアボーン上で稼働している発話PUSHサーバ(example.js)をngrokでインターネットから利用可能なWebAPIにして、GASから発話依頼するのが最も簡単な方法だろう。
手順はこうである。
  1. WebAPI (A2) はIFTTTからのカレンダー更新要求(Webhooksを使ってWebAPI を叩く)を待ち受ける。
  2. IFTTTからカレンダー更新要求が届いたら、現在時刻と開始時刻(start_time)~終了時刻(end_time)を比較し、時間外であればベアボーン上で稼働している発話PUSHサーバ(example.js)へ発話依頼する。

問題点


ここで問題が発生した。example.js起動時に出力されたngrokが払い出す公開URLの期限が8時間に制限されていることが分かった
これを回避するには、ngrokのサイトからアクセストークンを取得すればよい。そして発話PUSHサーバ(~/pill-reminder/example.js)のHTTPリクエストをListenするプログラムで下記のようにアクセストークンを指定してngrok.connect()関数を実行する。
/**
 * serverPortをlisten
 *
 */
app.listen(serverPort, function () {
  // トークンを設定
  ngrok.authtoken(token);
  // 現状のセッションを一旦終了
  ngrok.kill();
 
  ngrok.connect({authtoken: token, addr: serverPort, region: 'jp'}, function (err, url) { //修正
    console.log('Endpoints:');
    console.log('    http://' + ip + ':' + serverPort + '/google-home-notifier');
    console.log('    ' + url + '/google-home-notifier');
    console.log('GET example:');
    console.log('curl -X GET ' + url + '/google-home-notifier?text=Hello+Google+Home');
    console.log('POST example:');
    console.log('curl -X POST -d "text=Hello Google Home" ' + url + '/google-home-notifier');
  });
})
リスト1.HTTPリクエストのlisten部分


ここで、tokenはngrokのサイトから取得したAuthtokenである。

※リスト1はngrokのconnectメソッドを利用して公開URLを払い出している。これはv2.3.0の使用方法である。
一方,ngrokを非同期で起動して公開URLを払い出すサンプルがネット上にいくつかある。これはv3.2.5の書き方である。それに従ってリスト1を書き換えたものを以下に示す。

app.listen(serverPort, function () {
 putLog('Endpoints:');
 putLog('    http://' + ip + ':' + serverPort + '/google-home-notifier');
   
 connectNgrok(serverPort).then(url => {
  putLog('    ' + url + '/google-home-notifier');
  putLog('GET example:');
  putLog('curl -X GET ' + url + '/google-home-notifier?text=Hello+Google+Home');
  putLog('POST example:');
  putLog('curl -X POST -d "text=Hello Google Home" ' + url + '/google-home-notifier');
 });
})

/** 
 * ngrokを非同期で起動
 */
async function connectNgrok(port) {
 try {
  let url = await ngrok.connect({
   addr: port,
   region: 'jp', 
   configPath: '~/.ngrok2/ngrok.yml',
   authtoken: token
  });
  return url;
 } catch (err) {
  putLog(err.name + ': ' + err.message);
 }
}
リスト1b.

最初、ngrokのurl払い出しがなかなかうまくいかなかった。試行錯誤しているうち、ngrok.connectメソッドのパラメタに
region: 'jp',
configPath: '~/.ngrok2/ngrok.yml',
を追加すると払い出しができるようになった。regionに'jp'(日本)を設定したのが功を奏したのではないだろうか。近くのサーバを使用することでレスポンスが改善され、ngrok.connectが正しく動作したものと思われる。

ここで教訓になったことは、ngrok v2.3.0とv3.5.0では公開URLの払い出し方法が異なるということだ。

次にWebAPI (A2) を以下のように修正する。
/**
 * 「[朝|昼|夜|就寝前]のお薬飲んだよ」に対して服用済みに変えるファンクション
 *
 * @param  {String}   timing     [朝|昼|夜|就寝前]
 *
 */
function NotifyNotTaking(timing) {
  //var timing = e.parameter.timing;
  //「の薬」とつくイベントの取得  
  //var timing =e.parameter.timing;
  debug('NotifyNotTaking: timing=' + timing);
  
  // 当日の予定の中から timing (朝|昼|夜|就寝前)を含む予定を検索
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(
    new Date(),
    {search: timing}
    // {search:'夜の食後の薬'}
  );  

  // 取得した予定からターゲットの予定を探してタイトルと色を変更
  events.forEach(function(event,i,array){ 
    var title = event.getTitle();
    debug('NotifyNotTaking: title=' + title); 
    if(title.match(/^.の食.の薬\(.+\)$/) || title.match(/就寝前の薬\(.+\)$/)) {
      // 現在時刻と開始時刻(start_time)~終了時刻(end_time)を比較し、時間内であればタイトルを(済)にし、色を変え、LINEへ通知する
      if (withinTime(event.getStartTime(), event.getEndTime())) {
        // タイトルの変更
        // ”(”の文字の位置を取得する(http://cya.sakura.ne.jp/js/string.htm#indexOf)
        result = event.getTitle().indexOf("(");
        debug('NotifyNotTaking: result=' + result);
        // "("以降の文字を切り取る。
        results = event.getTitle().substr(0,result);
        debug('NotifyNotTaking: results=' + results);
        // タイトルを○○の薬(済)に変更する。  
        event.setTitle(results+'(済)');
        // 色を青に変更する。
        event.setColor(CalendarApp.EventColor.BLUE);
        //LINEに送るメッセージ
        LINE_notify(timing+'のお薬を飲みました。');
      } else {
        // 現在時刻と開始時刻(start_time)~終了時刻(end_time)を比較し、時間外であればベアボーン上で稼働している発話PUSHサーバ(example.js)へ発話依頼する。
        var message = '時間外に「' + timing + 'のお薬をのみました」とのメッセージを受け取りました。この要求を無視します。';
        debug('NotifyNotTaking: ' + message);
        LINE_notify(message);
      }
    }
  });
};

/**
 * 現在時刻が開始時刻(start_time)~終了時刻(end_time)の間にあるかどうかをチェックする
 * @param  {Date}   start_time     日付
 * @param  {Date}   end_time       日付
 * @return {Boolean}               true:現在時刻が開始時刻(start_time)~終了時刻(end_time)の間にある
 *                                 false: 〃 間にない
 */
function withinTime(start_time, end_time) {
  var current_time = new Date();
  var result = (start_time <= current_time && current_time <= end_time) ? true : false;
  return result;
}

// google-home-notifyにメッセージを送るファンクション
function GOOGLE_HOME_notify(message){
  var options =
  {
    "method"  : "post",
    "payload" : "text=" + message
  };

  // ngrokで払い出された公開用URLxxxxxxxxを書き直す
  UrlFetchApp.fetch("https://xxxxxxxx.ap.ngrok.io/google-home-notifier",options);
}
リスト2.WebAPI (A2) の修正

関数NotifyNotTaking()では、現在時刻が服用開始時刻~終了時刻の間かどうかwithinTime()関数を用いてチェックして、もし外れていたらGOOGLE_HOME_Notify()関数を使って発話PUSHサーバへ発話要求を行う。

 GOOGLE_HOME_Notify()のUrlFetchApp.fetchメソッドのurlはベアボーン上で稼働する発話PUSHサーバのurlである。これはngrokによって払い出されたものを使う。したがって,発話PUSHサーバが異なれば,当然このurlも書き換えなければならない。

2019年10月23日水曜日

実験4日目

問題とその解決策


Google Home Mini が発話しない


現象


朝食後の服用を促すメッセージが発話されなかった。

原因


前日、プログラムの修正を行っていたが、それにバグがあったため、crontabでスケジューリングされていた発話依頼プログラムが起動時に異常終了していた。

対応


プログラムを修正して再起動した。
$ cd pill-reminder
$ ./pill-reminder.sh &

トラブルシューティング


今回は、Google Home Mini から発話がないにもかかわらず、LINEで応答がないと通知されたため異常に気が付いた。その原因は、大学のベアボーンサーバ上では発話依頼プログラムが正常に動いていたため。

問題発見手順は以下のとおり。
  1. pill-reminderのログ(~/pill-reminder/pill-reminder.log)を確認
  2. forever listと打ち込んでforeverのログファイル名を確認
  3. foreverのログを確認
  4. crontabのログ(/var/log/cron.log)を確認
  5. プロセスが動いているかどうかを確認($ ps aux | grep pill)
今回は上記手順5でpill-reminder.jsが動いていないことから原因が判明した。プログラムを修正して再起動した。



2019年10月22日火曜日

実験3日目

問題とその解決案


応答無になってしまう


現象


AIスピーカーが「朝の食後の薬を飲んでください」と服薬を促し、それに対して「朝のお薬飲んだよ」と答えれば、Googleカレンダーに「朝の食後の薬(済)」が設定される。ところが、これが「朝の食後の薬(応答無)」に上書きされる場合がある。

考えられる原因


Google Home が「朝のお薬飲んだよ」を捉えたタイミングでWebhookによってカレンダー更新WebAPI (A2)が実行され、「朝の食後の薬(未)」から「朝の食後の薬(済)」に変更される。
一方、発話依頼プログラムの要求に応じて指定したidの予定を取り出して返すWebAPI (A3) は、催促回数が5回のとき、タイトルを「朝の食後の薬(応答無)」に書き換え、黄色にしてしまう。
その結果、折角「朝の食後の薬(済)」になったものが「朝の食後の薬(応答無)」で上書きされてしまう。

対応策


WebAPI (A3)において、「朝の食後の薬(済)」になっている場合はたとえ催促回数が5回以上になっていても「朝の食後の薬(応答無)」で上書きしないように修正する。

結果


上記の修正の結果、問題が解決された。

2019年10月21日月曜日

実験2日目

問題とその解決案


タイミング


「昼の食後の薬を飲んでください」に対して間違って「夜のお薬飲んだよ」と答えると、夜の予定が「(済)」に変わってしまう。

解決策とさらなる課題


回答を各々の予定の開始時間~終了時間の間にしか受け付けないようにする。たとえば「昼の食後の薬を飲んでください」に対して「夜のお薬飲んだよ」と回答があった場合でも、夜の服用時間帯から外れていたら「夜の薬をのむ時間ではありません。間違っていませんか?」と回答する。

しかし、そうすると、たとえば昼の薬を飲み忘れ、昼の時間帯を過ぎて飲んだ時、昼の薬が未服用のままとして記録に残る。

予定を変更する


あらかじめ設定した服用時刻を変更したい場合があるかもしれない。たとえば食事の時刻が遅くなってしまって服用時刻をずらしたい場合などである。
この場合、「30分後にもう一度伝えて」と言って予定を遅らせることができるといい。

解決策とさらなる課題


発話依頼サーバ(pill-reminder.js)は、id指定でWebAPI (A3)を叩いて得た服薬イベントの開始時刻を確認して、もしその時間になっていない場合は発話要求はせず、その時間になったら通知するようタイマーを設定する。
開始時刻を経過している場合はこれまで通り一定時間(C_INTERVAL)経過後にタイマー通知を設定して発話要求する。

WebAPI (A3) は、POSTデータに開始時刻の延期時間(delay)が入っている場合、イベント開始時刻を延期時間だけずらして書き換える。
そうでなければ今まで通りカレンダーのタイトルと色を書き換える(済、青)。

IFTTTで「後でお薬飲む」と言ったらWebAPI (A3) で開始時刻の延期をするよう設定する(Google AssistantとWebhooksの連携)。

問題は、どのようにして対象となるイベントを特定するか。直近の(未)のイベントを対象にする?





2019年10月20日日曜日

今日から実証実験を開始します

はじめに


やっと自宅での実験環境が整いました。ベアボーンサーバは昔買った安物のマシンで代用しています。ESCというメーカーが開発しているLIVAというマシンです。仕様は以下のとおりです。
  • Processor: Intel(R) Celeron(R) CPU  N2807  @ 1.58GHz
  • Memory: 2GB (DIMM DDR3 65535 MHz)
  • Storage: eMMC 64GB
  • OS: Ubuntu 16.04 LTS
また、使用するソフトウェアは以下のとおりです。
  • node 10.16.3
  • npm 6.9.0
  • google-home-notifier 1.2.0
  • ngrok 3.2.5
  • GAS (Google Apps Script)
  • Monaca
  • IFTTT

図1.ベアボーンサーバとGoogle Home Mini

実験の設定


以下の設定で実験を行う。
  • 処方内容:
    • ディオバン錠20mg 1錠
    • メバロチン錠10 1錠
  • 用法:1日3回毎食後と就寝前
  • 投与期間:毎日

所感


  • 服用状況がカレンダーに残るので、これを集計して定期的に服用記録をメールでかかりつけの病院(医師)や薬剤師(薬局)へ送ってはどうか
  • 薬品名がカレンダーのdescriptionに書かれているので患者からの問い合わせに応じて薬の名前や効用・副作用の情報などについて回答したらどうか
  • 薬局でもらった薬が切れかかったら事前にそのことを伝えて受診を促す機能を付けたらどうか

トラブル1


催促が止まらない


さっそくトラブル発生。AIスピーカーの呼びかけに対して薬を飲んだと伝えたあとも催促が続く。原因は、発話依頼プログラム(pill-reminder.js)がWebAPI A3から受信したデータ内のtaiking_statusが0のままになっているから。
発話依頼プログラム(pill-reminder.js)の起動時は、その日のGoogleカレンダーに登録されている予定を正しく取り込んでいるが、id指定で取り込んだ場合は、古い予定(WebAPI A2による更新前)を取り込んでいる。

原因判明


WebAPI A3では、下記のようにしてid指定でカレンダーからイベントを取得しようとしている。
var event = CalendarApp.getEventById(id)
ところが、eventはnullになり、予定を取得できなかった。

CalendarAppのgetEventByIdメソッドを完全マスター」を読むと、getEventByIdを使えばid指定でイベントを取得できるはずだったのだが・・・。
開発中にもid指定でイベントを取得しようとすると古いイベントのままだったという現象が起きていた。きっとこれが原因だったのだろう。

そこで、id指定でイベントを取得するCalendarApp.getEventById関数の使用は諦めて、下記のようにその日の全イベントを取得して、その中から指定したidに一致するイベントのみ取り出す方式に変えた。
/**
 * IDを指定してイベントを取得する
 *
 * @param  {String}   id            イベントid
 * @param  {Integer}  num_reminder  催促カウント
 *
 */
function getEventsById(id,num_reminder) {
  debug("getEventsById(" + id + "," + num_reminder + ")");
  
  //「の薬」とつくイベントの取得  
  //var event = CalendarApp.getEventById(id);
  var event = null;
  
  //「の薬」とつくイベントの取得  
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(
    new Date(),
    {search:'の薬'}
  );  
  
  events.forEach(function(e, i, array){
    if(e.getId() == id) {
      event = e;
    }
  });  
  
  //ジェイソン形式にする 
  var pills = [{
    "title":event.getTitle(),
    "description":event.getDescription(),
    "id":event.getId(),
    "location":event.getLocation(),
    "startTime":event.getStartTime(),
    "endTime":event.getEndTime(),
    "color":event.getColor()
  }];

  var response = {
    "drug_notifies": pills,
    "num_reminder": num_reminder,    // 催促回数
    "taiking_status": event.getTitle().indexOf("(済)") < 0 ? '0' : '1',    // 服用状況 (0:未服用, 1:服用済)
    "status": "0"        // 0=成功, 1:失敗
  }
  
  debug('response=\n' + JSON.stringify(response,null,' '));
  
  // 催促の回数が5回以上になったらタイトルに「(応答無)」に変更し、色を黄色に変える
  if(num_reminder >= 5){
    result = event.getTitle().indexOf("(");  
    //"("以降の文字を切り取る。
    results = event.getTitle().substr(0,result);
    //タイトルを○○の薬(応答無)に変更する。  
    event.setTitle(results+'(応答無)');    
    //色を黄に変更する。
    event.setColor(CalendarApp.EventColor.YELLOW); 
    // LINEにメッセージを送る
    LINE_notify('お薬を飲むように' + num_reminder + '回催促しましたが応答がありません。');
  }
  return response;
}
リスト1.指定したidのイベントを取得する関数(WebAPI A3)

この変更によって今回のトラブルは解消した。

2019年10月19日土曜日

10/15 役割に分かれて作業

谷口・勇

IFTTTが動作しなくなって、時間がたったらLINEに来るものはあった。
おにぎりテストはできたが、カレンダーを介しているものは来たり来なかったりして、不安定になるので、小山さんのGASですべてLINEに送るようにした。
IFTTTを使用するのは、薬を飲んだ時に行ったらGASに送られることだけになった。

今日は全体の流れのテストを実際にしてみた。
①QRコードを読みこむ→②カレンダーに書き込まれる→③「飲んだ」と言ったらカレンダーが書き換わる→④飲み忘れていたらLINEに通知(GAS)

内田

iphoneでの送信時エラーが出ていたものの実際には送信できていた。次回までに時間設定をできるようにしたい。

小山

タイトルが(応答無)のみになる問題が出た解決したが原因が分からない。
一通り5回の催促でタイトルの変更を黄色に変更できたので次回のテストで上手くいったか確認。

平松

前回まではプログラム起動したときすでに
過ぎた時間のお知らせもしゃべっていたがif文でマイナスの時間の薬をしゃべらないようにしたためこの問題はなくなった。

2019年10月10日木曜日

10/8 役割に分かれて作業

谷口・勇

LINEがこない問題をどうにかする

~のお薬飲んだよと言ってもカレンダーが変わらない。LINEもこない
→5~10分後にテスト用のおにぎり応答LINEに来た。(時差)
カレンダーは変更されたがLINEは来ず。
IFTTTの(済)の()をはずして更新したら朝はできた。
今までは済というカレンダーの文字に反応するようにしていたが
反応されなかったり反応したりだったので
朝(済)や昼(済)など時間帯ごとのAppletを作ったらうまくいった

卒論の構成を先輩のを参考にして皆で考えてそれに沿って作っていく。

内田

完成したと思ったが最後の最後でトラブルが起きた。
詳細はこれを参照。
来週までにはどうにかしていきたい。

小山

卒論の3.3.1(仕様)を書き中


平松

今のままではプログラムが一通り完了するとそのまま終了してしまうのでcronを使って1日のはじめにプログラムを自動で起動させることにした。
はじめエラーがあり起動することができなかったがエラーを消すと起動するようになった。
これで一日のはじめにプログラムが自動で起動するようになった。

2019年10月1日火曜日

10/1 役割に分かれて作業

勇・谷口

AIスピーカーに呼びかけても朝と昼のカレンダーが書き換わらなかった。
→原因はURLが変更されていなかったからだった。
また、お薬飲んだよのLINEが9月13日のゼミの日以来来ていないことに気づいた。
夜と寝る前の薬のぶんはLINEが来るけど朝と昼のぶんのLINEがこない。
→原因はわかっていないので次回治す。
(応答なしのはしていないのでわからない)

小山

A1が多分完成
来週は内田君から送られてきたデータが書き込めるか試してもらう。
A2がうまく動いているのか不安なのでこれも次回する。

平松

カレンダーの内容を取得して服薬を促すプログラムがほとんど完成した。
プログラムが一度終了すると終了したままなので次回はその問題を解決させていきたい。

内田

大体のレイアウトは完成した。正常に読み込めているかデバック用に新たにタブを作成。
次回は誰でも使えるようにアプリ化したい。

【TIPS】 

定時になったら起動する 

定時になったら処理を起動したい場合がある。このシステムの場合、ベアボーンから時間が来たら発話依頼をするプロセスを毎日立ち上げる必要がある。なぜならば、このプロセスは起動時、一日のスケジュールを読み込んで、薬の服用を促すが、すべての予定を終えると停止する。そこで、翌日の朝一に起動してその日のスケジュールを読み込む必要がある。それを実現するためにいくつかの方法がある。

プログラムで対応する場合

すべての予定を終えると停止するのではなく、翌日の朝一に通知が届くようにタイマーをセットする。その通知が届いたら、起動時にその日のスケジュールを読み込む処理を実行する。

ジョブスケジューリングcrontabを用いる場合

Linux系のOSにはジョブスケジューリングを行う仕組み(crontab)がある。これを使って朝一になったらこのプロセスを起動するように設定する。

課題

薬局でもらったお薬手帳のQRコードを読み込んで、処方内容をGoogleカレンダーへ設定したその当日については、そのスケジュールを読み込むタイミングがない。


2019年9月26日木曜日

9/25 役割に分かれて作業

平松:WebAPIから時間を取得する事が出来た。
   大まかな形が出来たので次回は細かいところを仕上げたい。

内田:前回のエラーは解決した。ons-navigatorを2つ設置した事がエラーの原因だった。
   基本的なパターンは完成したので次回はレイアウトを仕上げていく。

小山:今日はGASのAIの続き。上手く動作しないためどのようにしてカレンダーに
   複数回時間ごとに入力するか考えた。
   次回で作成できるように仕上げたい。

谷口・勇:論文の作成。昨年の先輩の論文を参考に大まかに書いた。
     アプリ構成、システム・ ハードウェア構成、開発ツール、デバイス、
     アプリケーションは入力完了。

2019年9月25日水曜日

9/20役割に分かれて作業

小山:送られてくるテキストデータをJSON成形したものをとりあえず表示しようとする
   も分からない。
   必要な部分を抜き取れるようになれば書き込めるようになると思う。

平松:invalid data表示され時刻が取得できない。

内田:ons-navigatorとons-templateを組み合わせることにより新しくhtmlを作らなく
   ても仮想のページを作ることはできた。
   QRコードを読み取ったタイミングでページを切り替えようとするとエラーになる
   ので次回解決策を考えたい。
   

2019年9月18日水曜日

09/18役割に分かれて作業

谷口・勇
スマホアプリがGoogle Assistantからも「〇の薬飲んだよ」というと変更されるようになった。前回できなかった原因は不明。
現在は卒論を少しずつ進めている。

小山
作成していたWebAPIをゼミのアカウントに移動。debug先のIDを変更していなかったせいで時間ロス。現在はA1, A2, A3それぞれ実行できるようになった。
A3が完成した。カレンダーの全件イベント、IDで指定した個別のイベントそれぞれ返すようになった。また5回催促されたとき(num_reminder=5)イベントタイトルを「〇の食〇の薬(応答無)」を変更、イベントの色も黄色に変更するようになった。
残るはA1のテキスト情報をカレンダーに書き込むところである。9月末までに完成させれるよう頑張りたい。

平松
薬の時間になると発話し、その後一定時間ごとに繰り返すようなプログラムが完成した。
次回は完成したA3と連携し、実際にカレンダーのデータをみてしゃべるようにしていきたい。

内田
レイアウトが思った通りに行かず苦戦中。
スマホの種類問わず、見やすい画面を作りたい。

仕様書を改訂しVer.1.1としてリリース
主な変更内容
・A3の引数に催促回数を追加
・GASのアカウント変更に伴ってWebAPIのURLを変更
・改訂記録を追加


IFTTTでWebhookとGoogleHomeの連携方法

IFTTTでWebhookとGoogleHomeの連携方法は以下のリンクに表示してます。

https://drive.google.com/open?id=1lXoR1BZ2k_vh2I78JYmOdb2WLzhQ0wje

2019年9月13日金曜日

09/13役割に分かれて作業14

勇・谷口 IFTTTできることはすべて終わったため、webnockとGoogleHomeとの連携の方法をまとめた。
外出しているときにgoogle homeに話しかけてカレンダーを書き換えることができないためどうするか考える。←スマホアプリで話しかけるとgoogle homeとの連携ができていないからかカレンダーには変更されなかった。

小山 RPで薬の情報を返すようになった
JSON形式を扱えるようになった。
次回は用法や数量の入力方法を考える。

内田 QRコードのレイアウトが想像できるようになった。
背景や画像を入れてわかりやすくしていきたい。

平松 朝の薬、夜の薬の時間を取得しその時間になると飲んだかどうか喋らすことに成功した。
少しずつ完成にちかづけていきたい。

2019年9月12日木曜日

9/11役割に分かれて作業

谷口・勇
前回、IFTTTで「夜の薬飲んだよ」バージョンのweb hockとの連携がうまくいったため、今回は朝と昼のバージョンのプログラムを作った。

小山
A2完成(たぶん)指定の時間が変更できるようになった。JSON形式にした処方箋情報を読み取ってカレンダーに書き込めるようにしていく。

内田
実際に使用するにあたりどのようなレイアウトにすれば お年寄りの使いやすいアプリができるのか思ったより時間がかかっているため、シンプルかつ誰もが使いやすいものをで消えるだけ早く完成させたい。

2019年9月6日金曜日

9月4日

勇・谷口

前回はできなかったが、今回はIFTTTでGoogleHomeに『◯◯の薬飲んだよ』と伝えるとWebhookで小山さんのWebAPIに『◯◯』の情報が送られカレンダーの色とタイトルを書き換えることができた。→GoogleHomeとWebhookの連携ができた
Webhookに与えるJSON形式のパラメタは下記の通り

{
    "timing": "朝"
}
リスト1.IFTTTのWebhookに設定するJSONパラメタ

小山さんのWebAPIを修正すると連携できた。
URLのところに何を入れればよいのか迷っていたが小山さんのWebAPIのURLでよかった。

小山

A2を編集してGoogleHomeに話しかけるとGoogleカレンダーの色とタイトルを変更することが出来た。
このサイトによると、doPostで公開したWebAPIでJSON形式で入力データを取得する際は
var parms = JSON.Parse(e.postpat.getDataAsString());
var timing = params.timing;
と書き込む必要があった。
次回は正規表現を用いてスケジュール(のタイトル)を絞り込み、スケジュールの変更ができるかを確認する。

内田

ついにQRコードリーダー作成は終わり、後はレイアウトを整えるだけとなった。
改行の問題もQRクラスを作成することで解決することが出来た。QRクラスは複数回に分割して読み取ったQRコードのデータを一つにまとめるために考案したクラスである。ソースを下記に示す。
    // QRクラス
    class QR {
      constructor(str) {
          this.data = str;
      }
      get() {
        return this.data.split('\r\n');
      }
      append(str) {
          this.data += str;
      }
    }
リスト.1

まず、起動時に、インスタンスを作成する。
var qr = new QR('');
QRコードを読み込んだら、その都度読み取ったデータ(result.text)を追加する。
qr.append(result.text);
読み取ったデータをサーバへ送るときは、getメソッドを使ってお薬手帳の行単位に文字列配列にしたものを取得してAjaxでサーバへ送信する(QRコードで読み込んだデータの改行コードは\r\nであることに注意)。

平松

settimeoutで設定された時刻(ここでは朝の薬を飲む時刻)になったらGoogleHomeにしゃべらせるというNodejsのプログラムをリスト2に示す。Googleカレンダーの時刻はグリニッジ標準時刻で格納されていることに注意。日本時間はグリニッジ標準時刻よりも9時間進んでいる。グリニッジ標準時刻を引数にDate関数を呼び出すとローカルタイム(日本時間)に変換される。

var url = 'http://192.168.11.39:8091/google-home-notifier';
var webclient = require("request");

var data ={
 "drug_notifies": [
  {
   "title": "朝の薬の時間です",
   "description": "クラビット細粒10% 100mg 3錠",
   "id": "t4s1bi3v05bhvcjp41rr89qulg@google.com",
   "location": "5",
   "startTime": "2019-09-04T04:37:14.000Z",
   "endTime": "2019-08-03T01:01:51.000Z",
   "color": "11"
  },
  {
   "title": "夜の薬の時間です",
   "description": "クラビット細粒10% 100mg",
   "id": "v84ho65ceseoja7257s1moj1o8@google.com",
   "location": "0",
   "startTime": "2019-08-03T09:00:00.000Z",
   "endTime": "2019-08-03T09:30:00.000Z",
   "color": "11"
  }
 ],
 "num_reminder": "5",    // 催促回数
 "taiking_status": "0",    // 服用状況 (0:未服用, 1:服用済)
 "status": "0"        // 0=成功, 1:失敗
}
var startTime = new Date(data.drug_notifies[0].startTime);
var now = new Date();
var df = startTime - now;

setTimeout(function() {
    talk(data.drug_notifies[0].title);
  }, 
  df
);

//
// 発話サーバにメッセージを送る
// HTTPリクエスト(GETメソッド)
//
function talk(message){
 console.log(message);
 webclient.get({
  url: url,
  qs: {
   text: message,
  }
 }, function (error, response, body) {
  if(!error) {
   console.log(body);
  } else {
   console.log('Error:' + error);
  }
 });
}
リスト2.タイマを設定して5秒後に発話サーバへ発話リクエストを送る

2019年8月31日土曜日

8/30 仕様書の再確認、

ゼミの初めに仕様書を現段階の進度でまとめたものを確認した。それぞれの担当で何が残っているのか何をしなければいけないのか、それぞれの担当の仕事がどう繋がっているのかの確認をした。

谷口・勇
IFTTTでWehookとの連携をした。しかし、今日改めて作成したトリガーは動かなかった。前回以前に作成したものは動いた。前回移行のを作りなおすと動かなくなった。時間を置いて30分後に動作の確認を行うと動いたが、動く時と動かない時があったので原因は何かよく分からなく、IFTTT側の問題かと、、、
Webhookとの連携をしているがIFTTTは動いているがカレンダーの書き換えが行われていないのでWebAPI側の問題なのかも分からず。

小山
今日はAIの編集を主に行った。Webから送信するとGoogleカレンダーにイベントが追加されるようになった。処方箋の情報から回数を把握し、朝・昼・夕・就寝それぞれ作成できるようにしなければならない。
谷口・勇に渡したURLが間違っているかもしれないので次回直す。

平松
現在の時刻とカレンダーに入力された時刻の差を求めることに成功した。取得したデータがグリニッジ標準時だったが、時間差自体は変わらないのでそのままにすることにした。

内田
先生の協力もありMonacaを使用したQRコードのアプリケーションで読み取ったデータを取り出して複数読み取っても全てのデータが一時的に保存されるようになった。また、先頭で改行もできるようになったがAJAXで送信するときに、 /n が消えずに残ってしまっているので次回までに解決し、使いやすいように全体のレイアウトを変えていきたいと思う。


UTC時刻とローカル時刻

Googleカレンダーは日時をUTC時刻で管理している。 そのため、UTC時刻とローカル時刻の変換が必要になる。
new Dateの引数にUTC時刻を指定するとローカル時刻が得られる。
また、引数なしではnew Dateはローカル時刻で現在時刻を取得する。
以上から、UTC時刻をローカル時刻に変換する特別な関数は必要ない。
以下にサンプルを示す。
var data = {
 "drug_notifies": 
 [
  {
   "title": "朝の薬の時間です",
   "description": "クラビット細粒10% 100mg 3錠",
   "id": "t4s1bi3v05bhvcjp41rr89qulg@google.com",
   "location": "5",
   "startTime": "2019-08-03T00:31:51.000Z",
   "endTime": "2019-08-03T01:01:51.000Z",
   "color": "11"
  },
  {
   "title": "夜の薬の時間です",
   "description": "クラビット細粒10% 100mg",
   "id": "v84ho65ceseoja7257s1moj1o8@google.com",
   "location": "0",
   "startTime": "2019-08-03T09:00:00.000Z",
   "endTime": "2019-08-03T09:30:00.000Z",
   "color": "11"
  }
 ],
 "num_reminder": "5",    // 催促回数
 "taiking_status": "0",  // 服用状況 (0:未服用, 1:服用済)
 "status": "0"         // 0=成功, 1:失敗
}

var startTime = data.drug_notifies[0].startTime;
var localTime = new Date(startTime);
console.log('startTime=' + startTime);
console.log('localTime=' + localTime);
var now = new Date();
console.log('now=' + now);
このプログラムの実行結果は次のようになる。
$ node utc2local.js
startTime=2019-08-03T00:31:51.000Z
localTime=Sat Aug 03 2019 09:31:51 GMT+0900 (GMT+09:00)
now=Sun Sep 01 2019 15:38:59 GMT+0900 (GMT+09:00)

2019年8月30日金曜日

monacaを使ったQRコードリーダー作成


前回まではmonacaの「BarcodeScanner」というCordovaプラグインを使ったところスマホでのQRコードの認識率が悪く実用をあきらめていたのだが、新たにCordovaのver. 7.1 から「PhoneGap BarcodeScanner」というものが追加されており、これを使うことによってAndroidにてQRコードの認識率が大幅に向上していることが確認できた。




iphoneユーザーのみ
iOS 10以降、info.plistに使用に関する説明を設定してやらないとアプリが強制終了するようだ。

NSCameraUsageDescriptionには、アプリがユーザーのカメラにアクセスする理由を記述します。
この設定を info.plist に追加するには、config.xml ファイルの <edit-config> タグに以下のように設定します。

<edit-config target="NSCameraUsageDescription" file="*-Info.plist" mode="merge">
    <string>To scan barcodes</string>
</edit-config>


iphone、androidユーザー共通


monacaは最小限の設定でプラグインを有効化しindex.htmlを下記のように書き換えるだけで使用することが出来る。詳しくはプラグインの名前から公式サイトに飛べます。
<!DOCTYPE HTML>
<html>
<head>
    <title>Barcode Scanner DEMO</title>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <script src="components/loader.js"></script>
    <link rel="stylesheet" href="components/loader.css">

    <script type="text/javascript">

    function scanBarcode() {
        window.plugins.barcodeScanner.scan( function(result) {
                alert("We got a barcode\n" +
                          "Result: " + result.text + "\n" +
                          "Format: " + result.format + "\n" +
                          "Cancelled: " + result.cancelled);
            }, function(error) {
                alert("Scanning failed: " + error);
            }
        );

    }
    </script>
</head>

<body>
    <hr> BarcodeReader DEMO <hr><br>
    <input type="button" onClick ="scanBarcode()" value ="Scan" />
</body>
</html>
                                                                                                                        

参考文献

[1]Monaca Docs

役割に分かれて作業12

全体
どこまで進んだか、何が足りないのか話し合った。
9月末までにそれぞれ完成させることになった 。

勇・谷口
URLをもらってWebAPIをWebhookに繋げたがカレンダーの書き換えができなかった。
(済)(応答無)に反応してLINEに通知がいくようになった。

平松
GETメソッドを使ってAPIを取得・カレンダーの書き換えなどのプログラムを組んだ。

内田
monacaの新しいプラグインを入れることで読み取りの高いアプリを作ることができた。
次回はAJAXを使い、WebAPIと連携させたい。

小山
③④5つのWebAPIを作るということでそれぞれプロジェクトを作成。勇・谷口の書き換えができたかったのは渡したものが間違っていたため次回直したものでできるかどうか確認する。
①②イベントの取得・色の取得はできているが、必要ないものも取得しているため次回添削。

2019年8月22日木曜日

役割に分かれて作業11

谷口・勇:色のやつとかの表を作る。
ドキュメントにIFTTTのやり方を入れた。パワーポイントに流れを整理してイラストを入れた。

平松:google notifire,カレンダーを見て薬を飲んでいなければ5分おきに催促する。飲んだらカレンダーを書き換える。一定時間ごとに催促するサンプルプログラムはかけたのでここからgoogleカレンダーを見たり書き換えたりする作業に取り掛かりたい。

内田:javaでの書き方が分からないため再度monacaで作ることにした。

小山:web api作り。書き換えたら文字化けするようになったので(html)次回直す。

2019年8月13日火曜日

役割に分かれて作業10

谷口・勇:催促したら家族・本人にLINEを送るようにした。(グループLINE)
催促したらタイトルが応答なしになるためそれをサーチしてラインを送れるようにした。
IFTTTとGoogleアシスタントが連携できなくなったがツイッターで検索した結果ただの障害であった。

平松:nodejsのforeverを使ってexample.jsを常時起動させることに成功した。
$ sudo npm install -g forever
$ forever start example.js
$ forever stop example.js
$ forever list example.js
$ forever logs example.js
$ forever config example.js
次回はタイマーをセットし朝昼夜の薬の飲み忘れを伝えるなどしていきたい。

小山:わかるところからスクリプトを引き続き入力中
当日の予定がタイトルに含まれる単語とIFTTTから送られてきたキーがイコールで結ばれた時にタイトルと色を変更するif文考え中。


2019年8月8日木曜日

8.6日

勇・谷口
IFTTTでGoogleカレンダーとWebhook(GAS)との連携できることが分かった。
連携にはGASのURL(Web API)がいるため今回はまだできてない無い。

平松
example.jsが動くようになった。node.jsをrequestモジュールで通信する方法を調べた。

内田
Javaでどのように変更すればイベントの更新が出来るのかがわかった。
が、書き換え方がまだよく分かっていない。

小山
GASにてスクリプト入力中
テストカレンダーは作成できたが色の着いた予定などはまだ入っていないため次週する。

【お薬の時間を通知するプログラム】 

下図はお薬を飲む時間を知らせるプログラムの動きを表したものである。このプログラムはベアボーンサーバ上でNodejsのスクリプトとして実装する。

図1.服用通知プログラムの状態遷移図

①初期化

  1. Googleカレンダーから当日のイベントを取得して,服用時刻になったら服用通知を行うタイマーを設定する。
  2. タイマーは当日分のうち,現在時刻より後で,かつ,未服用分のイベントに対して設定する。
  3. 翌日の早朝に日付変更通知が届くようにタイマーを設定する。
  • 服用通知にはパラメータとしてイベントidと0に初期化したカウンタを渡す。
  • 服用済みかどうかはイベントの色で判断する(赤:未服用,青:服用済み,黄:応答なし)。

②服用通知

  1. Googleカレンダーから当該idの服用イベントを取得して,服用済みかどうかを判定する。
  2. 服用済みでなければGoogle Homeに服用を促すメッセージを送り,事前に設定された時間後に服用確認通知が届くようにタイマーを設定する。
    Googleカレンダーから当該idの服用イベントを取得して,服用済みかどうかを判定する。
  • 服用確認通知にはパラメータとしてイベントidとカウンタを渡す。

③服用確認

  1. Googleカレンダーから当該idの服用イベントを取得して,服用済みかどうかを判定する。
  2. 服用済みでなければ服用通知回数カウンタを1upする。
  3. 服用通知回数カウンタが規定回数以下ならばGoogle Homeに服用を促すメッセージを送り,事前に設定された時間後に服用確認通知が届くようにタイマーを設定する。
  4. 服用通知回数カウンタが規定回数を超えていたらGoogleカレンダーの当該イベントを応答なし(黄)に設定する。
  • 服用確認通知にはパラメータとしてイベントidとカウンタを渡す。

服用時:朝・昼・夜・就寝前
カレンダーの色:未服用=赤,服用済み=青,応答なし=黄
※応答なしの場合,親族にLINEすると同時に利用者にもLINEする


【QRコードから読み込む処方情報】

QRコードから読み込んだ処方情報はJAHIS電子版お薬手帳データフォーマット仕様書Ver.2.2にしたがって記述する。その例を以下に示す。

1,鈴木 太郎,1,S330303,,東京都港区新橋1丁目△番 ○×ビル 5階,03-1234-1234,,B+,63.7,スズキ タロウ
2,1,乳製品,1
2,2,セフェム系(発熱),1
5,20190808,1
11,医療法人 オルカ医院,13,1,1234567,1130021,東京都文京区本駒込2-28-26,03-3946-0001,1
15,工業会 次郎,03-4567-4567,1
201,1,ノルバスク錠2.5mg,1,錠,1,,1
201,1,クラビット細粒10% 100mg(レボフロキシンとして),3,g,2,621925901,1
301,1,【1日3回毎食後に】,1,日分,1,1,,1
201,1,ペンニードル30Gテーパー,14,本,1,,1
201,1,バイアグラ,1,錠,1,,1
301,1,毎食後服用,3,日分,1,1,,1
リスト1.お薬手帳データフォーマット

リスト1に示すようなCSV形式だとプログラムからは扱いにくいので,これを以下のJSONフォーマットに変換する。
{
 "patientInfo": {
  "No1": {
   "name": "鈴木 太郎",
   "gender": "1",
   "birthdate": "S330303",
   "zipcode": "",
   "address": "東京都港区新橋1丁目△番 ○×ビル 5階",
   "phone": "03-1234-1234",
   "emergencyContact": "",
   "bloodType": "B+",
   "weight": "63.7",
   "kana": "スズキ タロウ"
  },
  "No2": [
   {
    "type": "1",
    "content": "乳製品",
    "creator": "1"
   },
   {
    "type": "2",
    "content": "セフェム系(発熱)",
    "creator": "1"
   }
  ]
 },
 "dispensing": [
  {
   "institution": {
    "No5": {
     "date": "20190808",
     "creator": "1"
    },
    "No11": {
     "name": "医療法人 オルカ医院",
     "prefCode": "13",
     "tensuCode": "1",
     "code": "1234567",
     "zipCode": "1130021",
     "address": "東京都文京区本駒込2-28-26",
     "phone": "03-3946-0001",
     "creator": "1"
    },
    "No15": {
     "name": "工業会 次郎",
     "contact": "03-4567-4567",
     "creator": "工業会 次郎"
    }
   },
   "RPs": [
    {
     "drugs": [
      {
       "No201": {
        "rp": "1",
        "name": "ノルバスク錠2.5mg",
        "dose": "1",
        "unit": "錠",
        "codeType": "1",
        "code": "",
        "creator": "1"
       }
      },
      {
       "No201": {
        "rp": "1",
        "name": "クラビット細粒10% 100mg(レボフロキシンとして)",
        "dose": "3",
        "unit": "g",
        "codeType": "2",
        "code": "621925901",
        "creator": "1"
       }
      }
     ],
     "dosage": {
      "No301": {
       "rp": "1",
       "name": "【1日3回毎食後に】",
       "quantity": "1",
       "unit": "日分",
       "formCode": "1",
       "dosageType": "1",
       "dosageCode": "",
       "creator": "1"
      }
     }
    },
    {
     "drugs": [
      {
       "No201": {
        "rp": "1",
        "name": "ペンニードル30Gテーパー",
        "dose": "14",
        "unit": "本",
        "codeType": "1",
        "code": "",
        "creator": "1"
       }
      },
      {
       "No201": {
        "rp": "1",
        "name": "バイアグラ",
        "dose": "1",
        "unit": "錠",
        "codeType": "1",
        "code": "",
        "creator": "1"
       }
      }
     ],
     "dosage": {
      "No301": {
       "rp": "1",
       "name": "毎食後服用",
       "quantity": "3",
       "unit": "日分",
       "formCode": "1",
       "dosageType": "1",
       "dosageCode": "",
       "creator": "1"
      }
     }
    }
   ]
  }
 ]
}
リスト2.JSON形式のお薬手帳データ 

この変換を行うNodejsで書かれたスクリプトを以下に示す。
//-----------------------------------------------
//
// CSVファイルを解析する(構造化)※PR情報は1つのみ
//
//-----------------------------------------------
var csv_data = '';
csv_data += '1,鈴木 太郎,1,S330303,,東京都港区新橋1丁目△番 ○×ビル 5階,03-1234-1234,,B+,63.7,スズキ タロウ\r\l';
csv_data += '2,1,乳製品,1\r\l';
csv_data += '2,2,セフェム系(発熱),1\r\l';
csv_data += '5,20190808,1\r\l';
csv_data += '11,医療法人 オルカ医院,13,1,1234567,1130021,東京都文京区本駒込2-28-26,03-3946-0001,1\r\l';
csv_data += '15,工業会 次郎,03-4567-4567,1\r\l';
csv_data += '201,1,ノルバスク錠2.5mg,1,錠,1,,1\r\l';
csv_data += '201,1,クラビット細粒10% 100mg(レボフロキシンとして),3,g,2,621925901,1\r\l';
csv_data += '301,1,【1日3回毎食後に】,1,日分,1,1,,1\r\l';
csv_data += '201,1,ペンニードル30Gテーパー,14,本,1,,1\r\l';
csv_data += '201,1,バイアグラ,1,錠,1,,1\r\l';
csv_data += '301,1,毎食後服用,3,日分,1,1,,1';

function barcode2json(csv_data) {
 var lines = csv_data.split('\r\l');

 var prescription = {
  patientInfo: {
   No1: {},
   No2: []
  },
  dispensing: [],
 };

 var dispensing = {
  institution: {
   No5: {},
   No11: {},
   No15: {},
  },
  RPs: [],
 };

 var RP = {
  drugs: [],
  dosage: '',
 };

 lines.forEach(function(line){
  var items = line.split(',');
  
  switch(items[0]) {
  // 患者基本情報(レコードNo.1 患者情報レコード)
  case "1":
   prescription.patientInfo.No1 = {
    name: items[1],
    gender: items[2],
    birthdate: items[3],
    zipcode: items[4],
    address: items[5],
    phone: items[6],
    emergencyContact: items[7],
    bloodType: items[8],
    weight: items[9],
    kana: items[10],
   };
   break;
  // 基本情報(レコードNo.2 患者特記レコード)
  case "2":
   prescription.patientInfo.No2.push({
    type: items[1],
    content: items[2],
    creator: items[3],
   });
   break;
  // 調剤-医療機関情報(レコードNo.5 調剤等年月日レコード)
  case "5":
   dispensing.institution.No5 = {
    date: items[1],
    creator: items[2],
   };
   break;
  // 調剤-医療機関情報(レコードNo.11 調剤-医療機関等レコード)
  case "11":
   dispensing.institution.No11 = {
    name: items[1],
    prefCode: items[2],
    tensuCode: items[3],
    code: items[4],
    zipCode: items[5],
    address: items[6],
    phone: items[7],
    creator: items[8],
   };
   break;
  // 調剤-医療機関情報(レコードNo.15 調剤-医師・薬剤師レコード)
  case "15":
   dispensing.institution.No15 = {
    name: items[1],
    contact: items[2],
    creator: items[1],
   };
   break;
  // 薬品情報(レコードNo.201 薬品レコード)
  case "201":
   RP.drugs.push({
    No201: {
     rp: items[1],
     name: items[2],
     dose: items[3],
     unit: items[4],
     codeType: items[5],
     code: items[6],
     creator: items[7],
    }
   });
   break;
  // 用法情報(レコードNo.301 用法レコード)
  case "301":
   RP.dosage = {
    No301: {
     rp: items[1],
     name: items[2],
     quantity: items[3],
     unit: items[4],
     formCode: items[5],
     dosageType: items[6],
     dosageCode: items[7],
     creator: items[8],
    }
   };
   dispensing.RPs.push(RP);
   RP = {
    drugs: [],
    dosage: '',
   };
   break;
  default:
   console.log('default:' + items[0]);
   break;
  }
 });
 prescription.dispensing.push(dispensing);
 return prescription;
}

var prescription = barcode2json(csv_data);
console.log(JSON.stringify(prescription, null, ' '));
リスト3.CSV形式の電子お薬手帳データをJSON形式に変換するNodejsのスクリプト

2019年8月2日金曜日

8/2のゼミ

<<進捗状況の確認>>
平松:AIスピーカーに自発的に喋らすことができるのかどうかをやる
内田:Javaできるのかどうかをやる
小山:薬の種類が3つ上でQRコードが2つになってしまう問題発生
   googleカレンダーの予定の変更をAPIで行う(何が必要か学ぶ)
勇・谷口:googleカレンダーの予定を変更できるのかどうか

<<今日の進捗状況>>

<勇・谷口>
前回できたと思っていたIFTTTのレシピに欠陥があったことが分かった。
欠陥はAIスピーカーに”薬飲んだ”と言ったらGoogleカレンダーに予定が書かれてLINEに”おばあちゃんが薬を飲んだ”という内容が送られてくるというものを作成していたが、
カレンダーに手動で予定を入れてもLINEに”おばあちゃんが薬飲んだ”というのが送られてきたことだ。
これは、IFTTTのGoogleカレンダーのレシピの"search"を使用して、特定のキーワードに反応してLINEに送られるようにした。
ネットで調べると、IFTTTではカレンダーの予定の変更とキャンセル(削除)はできないとのことが載っていた。

<平松>
Googleカレンダーに繋ぐことができた。ここからnoti-fireもつなげて、googleカレンダーの内容をAIスピーカーに喋らせたい。

<内田>
Javaを使用してgoogleカレンダーにアクセスできるようにできるようになった
次回は書き込めるようにしたいと思う。

<小山>
Googleカレンダーの修正の方法を探した。GAS(GoogleAppsScript)を使用すれば中身の書き換えができそうです。
次回はどこの欄にスクリプトを書くのか調べようと思う。

いい感じに進んできていると思うのでみんなで頑張っていきましょう~!



【システム設計】


正常な場合の情報の流れを図1に示す。
図1.正常な場合の情報の流れ
 図中の①でQRコードから服薬情報を読み込んだスマホは,処方された用法と日数に基づいてGoogleカレンダーに服用スケジュールを書き込む。
たとえば,朝・昼・晩・就寝前に4日間処方された場合のカレンダーおよびイベントの情報は次の図のようになる。

図2.Googleカレンダーにおける服用スケジュール

 また,Google Homeからの「○○のお薬の時間ですよ」というリマインドに対して「○○のお薬飲みました」との応答がなかった場合の情報の流れを図2に示す。

図3.応答がない場合の情報の流れ
図中の⑧までは同じであるが,⑨の応答がないので事前に設定した時間だけ待ってからベアボーンサーバから⑦の服用通知依頼をGoogle Homeへ送る。応答があるまでこれを繰り返し,これが事前に設定した回数を超えた場合薬が服用されなかった旨のメッセージを家族のもとへLINEで送る。
図1,図3の情報についてまとめたものを下表に示す。

表1.流れる情報とその詳細
 朝・昼・晩・就寝前など,システムを稼働させる上で必要な値はパラメータとしてスマホに設定できるようにする。必要なパラメータとしては次のようなものがある。

表2.必要なパラメータ

【Google Apps Script:イベントの取得】

図1の「⑫カレンダー更新」は,「⑨服用完了通知」をIFTTTがトリガーにしてWebhookを利用することによって実現する。Webhookで作動するプログラムはGAS (Google Apps Script) で開発し,Webサービスとして公開する。
GASはGoogleドライブから[新規]→[その他]→[アプリを追加]で[Google Apps Script]をインストールすると利用できるようになる。
以降,Googleドライブから[新規]→[その他]→[Google Apps Script]で編集画面が表示されるようになる。

例えば,下記はGoogleカレンダーから当日のイベントを取得するスクリプトである。

//
// Google calendarから当日のイベントを取得する
// webapi_googleCal_getEventsForDay

function doGet(e) {

  var eventData = getEventsForDay();
  responseText = JSON.stringify(eventData);
  
  var out = ContentService.createTextOutput();

  //Mime Typeをapplication/jsonに設定
  out.setMimeType(ContentService.MimeType.JSON);
  
  //JSONPテキストをセットする
  out.setContent(responseText);
  
  return out;
}

//プログラム起動日の予定をすべて取得して予定タイトルをログに表示するコード
function getEventsForDay() {
  var date = new Date();
  var options = {
    'search': 'お薬の時間です',
  };
  var events = CalendarApp.getEventsForDay(date, options);
  var eventData = [];
  events.forEach(function(event){
    var data = {
      'title': event.getTitle(),
      'description': event.getDescription(),
      'id': event.getId(),
      'location': event.getLocation(),
      'startTime': event.getStartTime(),
      'endTime': event.getEndTime(),
      'color': event.getColor(),
    }
    eventData.push(data);
  });
  return eventData;
}
関数doGetはHTTP GETメソッドの実装関数である。これを外部に公開するには,メニューから[公開]→[Webアプリケーションとして導入...]を選択する。
作成したWebサービスは以下のURLで利用できる。

https://script.google.com/macros/s/XXX...X/exec
この結果,次のようなJSON形式のイベントデータが取得できる。
[
    {
        "title": "朝のお薬の時間です",
        "description": "クラビット細粒10% 100mg 3錠",
        "id": "t4s1bi3v05bhvcjp41rr89qulg@google.com",
        "location": "医療法人 川崎病院",
        "startTime": "2019-08-03T00:31:51.000Z",
        "endTime": "2019-08-03T01:01:51.000Z",
        "color": "11"
    },
    {
        "title": "夜のお薬の時間です",
        "description": "クラビット細粒10% 100mg",
        "id": "v84ho65ceseoja7257s1moj1o8@google.com",
        "location": "医療法人 オルカ医院",
        "startTime": "2019-08-03T09:00:00.000Z",
        "endTime": "2019-08-03T09:30:00.000Z",
        "color": "11"
    }
]

【Google Apps Script:イベントの登録】 

 Googleカレンダーにイベントを登録するプログラム例を以下に示す。
//
// Google calendarにイベントを登録する
// webapi_googleCal_createEvent

function doPost(e) {

  var parameter = e.parameter;
  
  var eventData = {
    'title': parameter.title,
    'description': parameter.description,
    'location': parameter.location,
    'color': parameter.color,
 
  }
  
  var id = createEvents(eventData);
  
  var result = {
    'id': id
  }
  
  var out = ContentService.createTextOutput();
  
  responseText = JSON.stringify(result);
  
  //Mime Typeをapplication/jsonに設定
  out.setMimeType(ContentService.MimeType.JSON);
  
  //JSONPテキストをセットする
  out.setContent(responseText);
  
  return out;
}

// イベントの作成
function createEvents(eventData) {
  var calendar = CalendarApp.getDefaultCalendar();
  var title = eventData.title;
  var startTime = new Date();
  var endTime = new Date();
  endTime.setMinutes(endTime.getMinutes() + 30);
  
  var option = {
    description: eventData.description,
    location: eventData.location
  }
  
  // イベントを登録
  var event = calendar.createEvent(title, startTime, endTime, option);

  // 色を設定(赤色)
  event.setColor(eventData.color);

  // idを取得
  var id = event.getId();

  return id;
}

function testCreateEvent() {

    var eventData = {
        'title': '朝のお薬の時間です',
        'description': 'クラビット細粒10% 100mg 3錠',
        'location': '医療法人 川崎病院',
        'color': 11,
    }
    var id = createEvents(eventData);
}
このスクリプトはPOSTメッセージでイベント情報(タイトル,説明,場所,色)を受け取り,イベントを登録している。関数testCreateEventはデバッグ用の関数で,doPostの動作テストに利用する。
イベントを登録するとイベントを一意に識別するイベントidを返すので,それをJSON形式に編集してHTTP POSTリクエストのレスポンスとする。

このスクリプトをWebサービスとして公開し,それをテストするために下記のHTMLファイルを作成して簡易イベント登録ページを作成した。

<!--
  Google Calendar にイベントを登録する
-->
<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title>Googleカレンダーにイベントを登録</title>
</head>
<body>
  <h1>webapi_googleCal_createEvent</h1>
  <!-- webapi_googleCal_createEvent -->
  <form method="post" action="https://script.google.com/macros/s/XXX...X/exec">
  
    <table>
  <tr><th align="left">タイトル:</th><td><input type="text" name="title" value="昼のお薬の時間です"></td></tr>
  <tr>
   <th align="left">お薬の内容:</th>
   <td>
    <textarea name="description" rows="5", cols="80">クラビット細粒10% 100mg 3錠</textarea>
   </td>
  </tr>
  <tr><th align="left">病院名:</th><td><input type="text" name="location" value="医療法人 川崎病院"></td></tr>
  <tr>
   <th align="left">色:</th>
   <td>
    <select name="color">
     <option value=0>0
     <option value=1>1
     <option value=2>2
     <option value=3>3
     <option value=4>4
     <option value=5>5
     <option value=6>6
     <option value=7>7
     <option value=8>8
     <option value=9>9
     <option value=10>10
     <option value=11>11
     <option value=12>12
    </select>
   </td>
  </tr>
    </table>
    <p>
      <input type="submit" value="送信">
    </p>
  </form>
</body>
</html>
このHTMLで作成したWebページを下図に示す。

図4.イベント登録のテスト用ページ

2019年7月31日水曜日

役割に別れて作業9

今日のゼミでは前回に引き続き、それぞれ作業を行った。

小山:とりあえず直接カレンダーに薬の予定を書き込み、表示を確認。
QRコードの情報を直接書き込んで使えば、カンマで区切られているため簡単に使用できるようになると思う。ただし処方される薬の量が増えるとQRコードの数が増え、適当な場所で情報が分けられるため複数のQRコードの情報をきれいにくっつけるように設定する必要があると思われる。

谷口・勇:AIスピーカーに「薬飲んだ」と言ったら、カレンダーに記録された。LINEに飲んだということも一緒に通知されるようになった。

平松:ファイルが混在していたため整理を行った。3つほど同じものが入っていたため、確実にいらないと思われる1つを削除した。
引き続き、googleカレンダーとの連携をしていく。

内田:google api V3を使用するにあたってのセットアップはできた。
しかしgrodeファイルを開くことができず、ファイルの編集を行うことができない。
金曜日までに何とかする予定である。

2019年7月30日火曜日

開発会議




今日はゼミメンバー全員で改めて仕様の話し合いを行った。
それぞれ次のステップに移るためにやることをまとめた。



小山:8月の半ばまでにデータフォーマットをどのようにするか考える。
今日はカレンダーに手打ちで入力したが、どのように表示すると見やすく,またデータを見に来るベアボーンが情報を読み取りやすいか考えていく必要がある。
今日は主にorcaをダウンロードしてお薬手帳のQRコードのフォーマットを確認した。

内田:8月半ばまでにどうすればGoogleカレンダーにデータを書き込めるか調べ、完成させる。9月までにQRコードリーダーとFirebase連携させる。

平松:8月23日までにFirebaseとベアボーンを連携させ情報を読み取れるようにする。
今日はGoogleカレンダーのAPIを取得した。

谷口勇:IFTTTでカレンダーに予定を打ち込むのはできた。
詳細な設定というのがあったが色を変えることができるか次回調べる。
カレンダーの予定からAIスピーカーがしゃべるかどうかも次回。

2019年7月19日金曜日

オープンキャンパスに向けて

いよいよ今週はオープンキャンパスでそれに向けての準備を行った。
予行練習ではケーブルの問題でプロジェクターに正常に映らなかったが、ケーブルを変えることで解決した。
当日も問題なくできればいいなと思う。
また高校生が少しでも興味を持ってくれると嬉しい。

役割に別れて作業8

・機能一覧作成 担当

飲み忘れ通知がくるようにGASを使い元あるテンプレートを変えながら操作してみたがえエラーが起きてしまいLINEに通知が来ない。日付のフォーマットに問題があるとわかったため、IFTTTのスプレッドシートの所を変更してみたがまだエラーが出ている状態。


・google home notifier 

IFTTTを使い天気の情報を取得しその情報を元にAIスピーカーか
自発的に喋らそうとしたがそのためにng〇〇〇でURLを取得しなければならなかったのでそのインストール作業を行った。


・google home スキル開発 担当

AndroidStudioからAndroidにapkファイルのインストールに成功した。
次回は機能をもっと充実させる予定。



次回はオープンキャンパスの話し合い&リハーサルを行う。
なんとしてでもデモの際に5分間持たせるように、また高校生に興味を持ってもらえるためには何をするのかみんなで考える。


【補足】

IFTTTとGoogleスプレッドシートと連携して、指定の時刻が来たらLINEにメッセージを送る処理でエラーが発生した。ここではそのエラーの原因とその対処法について説明する。

原因


Google Homeに薬を飲んだことを伝えると、スプレッドシートにそのときの時刻が書き込まれる。その時刻のフォーマットは「June 28, 2019 at 03:13PM」のような形式になっている。
ところが、この日付フォーマットはスプレッドシートに埋め込まれたJavaScriptで書かれた下記のスクリプトではエラーになる。
//日付フォーマット
function formatDate(date, format) {
  date = new Date(date);
  return Utilities.formatDate(date, 'JST', format)
}
図1. 日付フォーマット関数

これは、JavaScriptでは「June 28, 2019 at 03:13PM」というフォーマットの日時をサポートしていない(解釈できない)ためである。

対策


この問題を解決するために、IFTTTの設定でスプレッドシートに書き込む日時データのフォーマットを変更しようとしたが、そのようなオプションはなかった。
そこで、このフォーマットの日時データを通常のJavaScriptで扱える形式に変換する下記の関数を作成してこの問題を回避した。

// 日付のチェック(June 28, 2019 at 03:14PM → Fri Jun 28 2019 15:22:00 GMT+0900(日本標準時))
// Modified at 2019-7-17
function check_date(target_date) {
  var pattern = /^(\w+)\s(\d+),\s(\d{4})\sat\s(\d{2}):(\d{2})(AM|PM)$/i;
  var regexp = new RegExp(pattern);
  var result = regexp.test(target_date);
  if(result) {
    var matched = target_date.match(regexp);
    var str = matched[0].replace('at ','');
    var str = str.replace(matched[6],'') + ':00';
    var new_date = new Date(str);
    if(matched[6] == 'PM') {
      new_date.setHours(new_date.getHours() + 12);
    }
    return(new Date(new_date));
  }
  return(new Date(target_date));
}
図2. 日時データのフォーマット変換関数

 これを使って図1の日付フォーマット関数を以下のように修正した。
//日付フォーマット
function formatDate(date, format) {
  date = check_date(date);
  return Utilities.formatDate(date, 'JST', format)
}