スイッチIoTで服薬管理 〜M5Stack × AWS〜 [Part1] | 服薬カウンター(4/4)

M5Stackのプログラミング

M5Stack側のプログラムを作成します。
今回は、M5Stackの開発プラットフォームである「UIFlow」を使って、ビジュアルプログラミングをしてみました。
プログラムのブロックを繋げていくことで、裏側でMicroPythonのコードを生成してくれます。
  1. 💡 UIFlowの基本的な使い方や導入方法は、公式サイトが詳しいのでご確認ください。
     ➡ https://docs.m5stack.com/ja/quick_start/core2_for_aws/uiflow

まず、起動時にWifiに接続し、RTC(リアルタイムクロック)という内蔵時計の時刻合わせをしましょう。
日本のNTPサーバに接続し、日本の時刻を求めるためにtimezoneはプラス「9」時間とします。
  1. 💡 Wifi接続での注意点は、2.4GHz帯のWifiを使うということです。
    5GHz帯のWifiには、M5Stackの通信ユニットが対応していませんのでご注意を。
    (私はこれに気づかず、しばらくハマりました)

時刻合わせができたら、RTCから今日の日付を取得して変数display_dateにセットします。
月と日の表示はゼロパディングしておくことにしましょう。
続けて、服薬情報取得のWebAPIを呼び出し、今日の服薬記録を取得して画面に表示します。
引数にoffsetという位置を表す変数を設けている理由は、「前日」及び「翌日」のデータを取得するためです。
服薬情報取得関数を呼び出す際に、以下のようにoffset引数を設定します。
  • 物理ボタン[A]を押した時は、前日のoffsetである『-1』
  • 物理ボタン[B]を押した時は、当日のoffsetである『0』
  • 物理ボタン[C]を押した時は、翌日のoffsetである『1』
画面上に配置した[プラス]ボタンでは、服薬記録登録のWebAPIを呼び出します。
そして[マイナス]ボタンでは、服薬記録削除のWebAPIを呼び出します。
誤作動防止のために、[使用可能]スイッチがONでないと、プラスボタン及びマイナスボタンが作動しないようにしています。
登録及び削除が成功した場合に、表示日付当日の服薬情報取得して画面表示を更新します。
ここまで出来れば基本動作は完成なのですが、最後に見た目をよくする工夫を追加しましょう。
起動時の画面の情報が更新される前の状態を見せないように、初期状態のラベルやボタンを「非表示」にし、情報が取得できてからラベルやボタンを「表示」するようにします。
日付表示とカウンタ表示は、表示データによっては中心がずれて見えてしまうので、表示データが変わるたびにラベルを中央寄せします。
UIFlowでのプログラミングが完成したら、M5Stackが接続された状態で[ダウンロード]ボタンを選択し、M5Stackにプログラムを送ります。

最後に、UIFlowのビジュアルプログラミングで作成したコードを「MicroPython」に変換したソースを載せておきます。
これらのコードがブロックを組み合わせることで作成できます。面白くないですか?
medication_counter.py
from m5stack import *
from m5stack_ui import *
from uiflow import *
import wifiCfg
import urequests
import time
import json



screen = M5Screen()
screen.clean_screen()
screen.set_screen_bg_color(0x000000)


num = None
offset = None
update_enabled = None
display_date = None
numString = None
api_url = None



touch_button_plus = M5Btn(text='+', x=210, y=45, w=50, h=50, bg_c=0x5ba6d2, text_c=0xffffff, font=FONT_MONT_48, parent=None)
labelCount = M5Label('00', x=130, y=45, color=0xffffff, font=FONT_MONT_48, parent=None)
touch_button_minus = M5Btn(text='-', x=60, y=45, w=50, h=50, bg_c=0xf1492b, text_c=0xffffff, font=FONT_MONT_48, parent=None)
labelTitle = M5Label('Medication Counter', x=86, y=4, color=0x86ea50, font=FONT_MONT_14, parent=None)
switch_enabled = M5Switch(x=125, y=100, w=70, h=30, bg_c=0xCCCCCC, color=0x0089ff, parent=None)
labelLastestEntry = M5Label('yyyy-mm-dd hh-mi-ss', x=140, y=150, color=0xfffcfc, font=FONT_MONT_14, parent=None)
labelCaption1 = M5Label('Lastest Entry:', x=20, y=150, color=0xfbf8f8, font=FONT_MONT_14, parent=None)
labelDate = M5Label('YYYY/MM/DD', x=55, y=190, color=0xf6f1f1, font=FONT_MONT_34, parent=None)
labelPrevDay = M5Label('<<', x=45, y=220, color=0xd5b175, font=FONT_MONT_14, parent=None)
labelToday = M5Label('Today', x=140, y=220, color=0xd5b175, font=FONT_MONT_14, parent=None)
labelNextDay = M5Label('>>', x=260, y=220, color=0xd5b175, font=FONT_MONT_14, parent=None)


# 関数定義:現在日付を表示日付変数にセット
def setCurrentDate():
  global num, offset, update_enabled, display_date, numString, api_url
  display_date = rtc.datetime()[0]
  display_date = (str(display_date) + str(((str('/') + str(zeroPadding(rtc.datetime()[1]))))))
  display_date = (str(display_date) + str(((str('/') + str(zeroPadding(rtc.datetime()[2]))))))

# 関数定義:数値のゼロパディング
def zeroPadding(num):
  global offset, update_enabled, display_date, numString, api_url
  numString = str(num)
  if num <= 9:
    numString = (str('0') + str(num))
  return numString

# 関数定義:コントロール非表示
def controlHide():
  global num, offset, update_enabled, display_date, numString, api_url
  labelTitle.set_hidden(True)
  labelCount.set_hidden(True)
  labelDate.set_hidden(True)
  labelCaption1.set_hidden(True)
  labelLastestEntry.set_hidden(True)
  labelDate.set_hidden(True)
  labelPrevDay.set_hidden(True)
  labelToday.set_hidden(True)
  labelNextDay.set_hidden(True)
  touch_button_plus.set_hidden(True)
  touch_button_minus.set_hidden(True)
  switch_enabled.set_hidden(True)

# 関数定義:コントロール表示
def controlShow():
  global num, offset, update_enabled, display_date, numString, api_url
  labelTitle.set_hidden(False)
  labelCount.set_hidden(False)
  labelDate.set_hidden(False)
  labelCaption1.set_hidden(False)
  labelLastestEntry.set_hidden(False)
  labelDate.set_hidden(False)
  labelPrevDay.set_hidden(False)
  labelToday.set_hidden(False)
  labelNextDay.set_hidden(False)
  touch_button_plus.set_hidden(False)
  touch_button_minus.set_hidden(False)
  switch_enabled.set_hidden(False)

# 関数定義:コントロール位置調整
def setControlPositon():
  global num, offset, update_enabled, display_date, numString, api_url
  labelCount.set_align(ALIGN_CENTER, x=0, y=(-50), ref=screen.obj)
  labelDate.set_align(ALIGN_CENTER, x=0, y=80, ref=screen.obj)

# 関数定義:服薬情報取得
#  - 服薬情報をWebAPIから取得する
# 引数: offset (0=表示日付の当日, 1=表示日付の翌日, -1=表示日付の前日)
def getData(offset):
  global num, update_enabled, display_date, numString, api_url
  rgb.setColorAll(0xffff66)
  rgb.setBrightness(10)
  # GET APIにHTTPリクエストする
  # GET /items/{year}/{month}/{day}/{offset}
  try:
    req = urequests.request(method='GET', url=(str(api_url) + str(((str('/items/') + str(((str(display_date) + str(((str('/') + str(offset))))))))))), headers={})
    # 成功時、返却されたJSONデータを読みこんで、画面情報を更新
    display_date = (json.loads((req.text)))['display_date']
    labelDate.set_text(str(display_date))
    labelCount.set_text(str((json.loads((req.text)))['count']))
    labelLastestEntry.set_text(str((json.loads((req.text)))['lastest_entry']))
    # コントロールの位置調整
    setControlPositon()
    rgb.setColorAll(0x33ff33)
    wait(1)
    rgb.setBrightness(0)
  except:
    rgb.setColorAll(0xff0000)
    wait(2)
    rgb.setBrightness(0)

# 関数定義:服薬情報登録
#  - 服薬情報をWebAPIで登録する
def putData():
  global num, offset, update_enabled, display_date, numString, api_url
  rgb.setColorAll(0x3366ff)
  rgb.setBrightness(10)
  # PUT APIにHTTPリクエストする
  # PUT /items/{year}/{month}/{day}
  try:
    req = urequests.request(method='PUT', url=(str(api_url) + str(((str('/items/') + str(display_date))))),json={}, headers={})
    # 登録成功時、服薬情報取得をコールして画面情報を更新
    if (json.loads((req.text))) == 'put ok':
      # 関数コール:服薬情報取得
      # 引数:offset = 0(表示日付の当日)
      getData(0)
  except:
    rgb.setColorAll(0xff0000)
    wait(2)
    rgb.setBrightness(0)

# 関数定義:服薬情報削除
#  - 服薬情報をWebAPIで削除する
def deleteData():
  global num, offset, update_enabled, display_date, numString, api_url
  rgb.setColorAll(0xff99ff)
  rgb.setBrightness(10)
  # DELETE APIにHTTPリクエストする
  # DELTE /items/{year}/{month}/{day}
  try:
    req = urequests.request(method='DELETE', url=(str(api_url) + str(((str('/items/') + str(display_date))))),json={}, headers={})
    # 削除成功時、服薬情報取得をコールして画面情報を更新
    if (json.loads((req.text))) == 'delete ok':
      # 関数コール:服薬情報取得
      # 引数:offset = 0(表示日付の当日)
      getData(0)
  except:
    rgb.setColorAll(0xff0000)
    wait(2)
    rgb.setBrightness(0)


# 使用可能スイッチをONにした時のイベント
def switch_enabled_on():
  global update_enabled, display_date, numString, num, api_url, offset
  update_enabled = True
  pass
switch_enabled.on(switch_enabled_on)

# 使用可能スイッチをOFFにした時のイベント
def switch_enabled_off():
  global update_enabled, display_date, numString, num, api_url, offset
  update_enabled = False
  pass
switch_enabled.off(switch_enabled_off)

# プラスボタンを押した時のイベント
def touch_button_plus_pressed():
  global update_enabled, display_date, numString, num, api_url, offset
  if update_enabled == True:
    power.setVibrationEnable(True)
    wait(0.2)
    power.setVibrationEnable(False)
    # 関数コール:服薬情報登録
    putData()
  pass
touch_button_plus.pressed(touch_button_plus_pressed)

# マイナスボタンを押した時のイベント
def touch_button_minus_pressed():
  global update_enabled, display_date, numString, num, api_url, offset
  if update_enabled == True:
    power.setVibrationEnable(True)
    wait(0.2)
    power.setVibrationEnable(False)
    # 関数コール:服薬情報削除
    deleteData()
  pass
touch_button_minus.pressed(touch_button_minus_pressed)

# 本体のAボタン(左)が押された時のイベント
def buttonA_wasPressed():
  global update_enabled, display_date, numString, num, api_url, offset
  power.setVibrationEnable(True)
  wait(0.2)
  power.setVibrationEnable(False)
  # 関数コール:服薬情報取得
  # 引数:offset = -1(表示日付の前日)
  getData(-1)
  pass
btnA.wasPressed(buttonA_wasPressed)

# 本体のBボタン(真ん中)が押された時のイベント
def buttonB_wasPressed():
  global update_enabled, display_date, numString, num, api_url, offset
  power.setVibrationEnable(True)
  wait(0.2)
  power.setVibrationEnable(False)
  # 関数コール:現在日付を表示日付変数にセット
  setCurrentDate()
  # 関数コール:服薬情報取得
  # 引数:offset = 0(表示日付の当日)
  getData(0)
  pass
btnB.wasPressed(buttonB_wasPressed)

# 本体のCボタン(右)が押された時のイベント
def buttonC_wasPressed():
  global update_enabled, display_date, numString, num, api_url, offset
  power.setVibrationEnable(True)
  wait(0.2)
  power.setVibrationEnable(False)
  # 関数コール:服薬情報取得
  # 引数:offset = 1(表示日付の翌日)
  getData(1)
  pass
btnC.wasPressed(buttonC_wasPressed)

# ここから起動時処理スタート

# 関数コール:コントロールを非表示
controlHide()
# Wifiと接続する
wifiCfg.doConnect('wifi-ssid', 'wifi-password')
# Wifiの接続が完了するまで待機する
while not (wifiCfg.wlan_sta.isconnected()):
  wait(1)
# NTPで時刻合わせ
rtc.settime('ntp', host='jp.pool.ntp.org', tzone=9)
# 関数コール:現在日付を変数にセット
setCurrentDate()
# WebAPIのURLを変数にセット
api_url = 'https://xxx.execute-api.ap-northeast-1.amazonaws.com'
# 関数コール:服薬情報取得
# 引数:offset = 0(当日)
getData(0)
# 関数コール:コントロール表示
controlShow()


あとがき

今回は、M5StackというIoTデバイスを使って、AWS上のデータベースに服薬記録をとるという、シンプルな仕組みでIoT開発をしてみました。
服薬記録は3か月に1回の定期診察の際に、主治医に報告しなければいけないという事情もあって。

ぶっちゃけ、人によっては「これくらい、紙にメモをとれば事足りるんじゃない?」と思うような機能しかありませんよね。
確かに、病院からも服薬記録をとる用紙をいただいているので、紙にメモすることは可能です。
しかし、昔からペンで紙にメモをとる習慣がない私にとっては、ガジェット化することに意味があるのですよね。
ペンでメモをとることにはストレスを感じるけれど、ガジェットを触っている分にはストレスを感じないので。
実際、このガジェットを作ったおかげで、服薬記録のとり忘れがなくなりました。
何が最適解かは、人それぞれということですね。

さて次は、さらに便利になるように、「服薬記録カレンダー」と「飲み忘れ防止の通知機能」を作ってみましょうかね!



前へ
1
2
3
4

あなたへのおすすめ記事