LoginSignup
11
6

More than 3 years have passed since last update.

健康寿命を延ばし、生き生きとした生活を送るためには、歯磨き週間は大変重要です。過去の調査でも、自立した高齢者の多くは、食後に歯を磨き、早寝早起き、1日3食を規則正しく摂り、かかりつけ歯科医をもっていて、喫煙をしないという習慣が身についています。
ここでは、歯磨き意識を高め、また家族や周囲からも歯磨きを通して、高齢者を見守る仕組みとして「IoT歯ブラシスタンド」を作ってみました。
image.png

全体イメージ

Toothbrush.png
IoT歯ブラシスタンドで検知した歯ブラシの動きをSigfoxネットワークで送信し、(今回は)Google Cloud PlatformにCallback。GCPのCloud FunctionsでLINE Messaging APIにPOSTするとともに、BigQueryに蓄積するという流れです。

必要機器

機器 イメージ 価格
M5Stack image.png 4,565円
M5Stack用Sigfoxモジュール image.png 5,500円
M5Stack用PIRセンサユニット image.png 583円
珪藻土ハブラシスタンド image.png 1,212円

あとは、工具やネジなど。珪藻土はキリでも穴を開けれるので便利です。

M5Stack側スケッチ

M5Stackでは、PIRセンサ(ここではPIN36)からのデジタル入力をもって、歯ブラシが移動したことを判定し、Sigfox(M5Stack Com.X)にメッセージ送信のATコマンドを送っています。あわせて、歯磨き時間のタイマ表示も行っています。

m5stack.ino.c
#include <M5Stack.h>

int last_sense = 0;
bool isBrushing = false;
unsigned long brushing_start_time = 0;

void setup() {
  M5.begin();
  M5.Power.begin();
  Serial2.begin(9600, SERIAL_8N1, 16, 17);
  Serial.begin(115200);

  M5.Lcd.clear(BLACK);
  Serial.println("IoT Toothbrush Stand: ");
  pinMode(36, INPUT);
}

void loop() {
  if (Serial2.available()) {
    String ack = Serial2.readString();
    Serial.println(ack);
    if (ack.startsWith("OK")) {
      SendMessageEnd();
    }
  }
  int sense = digitalRead(36);

  if(sense==1 && last_sense == 0) {
    if (isBrushing == false) {
      isBrushing = true;
      brushing_start_time = millis();
      SendMessageStart();
    } else {
      isBrushing = false;
      DrawBrushingEnd();
    }
  }
  last_sense = sense;
  DrawSensing();
  DrawBrushing();
  delay(250);

  M5.update();
}

void DrawBrushing()
{
  if (isBrushing) {
    int t = (int)((millis() - brushing_start_time) / 1000);
    M5.Lcd.drawRect(30, 120, 250, 70, WHITE);
    if (t > 5 * 60) {
      isBrushing = false;
      DrawBrushingEnd();
    } else {
      int m = (int)(t / 60);
      int s = t % 60;
      char d[32];
      sprintf(d, "%d:%02d", m, s);
      M5.Lcd.setCursor(80, 50);
      M5.Lcd.setTextSize(7);
      M5.Lcd.print(d);
      int w = (250*t)/180; if (w>=250) w = 250;
      M5.Lcd.fillRect(30, 120, w, 70, WHITE);
    }
  }  

  //画面上部に"B" or "-" (デバッグ用)
  M5.Lcd.setCursor(10, 0);
  M5.Lcd.fillRect(10, 0, 10, 10, BLACK);
  M5.Lcd.setTextSize(1);
  if (isBrushing) {
    M5.Lcd.print("B");
  } else {
    M5.Lcd.print("-");
  }
}

void DrawBrushingEnd()
{
  M5.Lcd.fillRect(30, 50, 250, 190, BLACK);
}

void DrawSensing()
{
  M5.Lcd.setCursor(0, 0); 
  M5.Lcd.fillRect(0, 0, 10, 10, BLACK);
  M5.Lcd.setTextSize(1);
  if (last_sense) {
    M5.Lcd.print("O");
  } else {
    M5.Lcd.print("-");
  }
}

void SendMessageStart()
{
  Serial.println("AT$SF=01");
  Serial2.println("AT$SF=01");
  M5.Lcd.setCursor(20, 0);
  M5.Lcd.fillRect(20, 0, 10, 10, BLACK);
  M5.Lcd.setTextSize(1);
  M5.Lcd.print("W");
}

void SendMessageEnd()
{
  M5.Lcd.setCursor(20, 0);
  M5.Lcd.fillRect(20, 0, 10, 10, BLACK);
}

SigfoxクラウドでCALLBACK設定

次に、SigfoxクラウドでGCPへメッセージを転送するためCALLBACK設定を行います。
Sigfox Callbackに関しては、こちらを参考にしてください。
今回は、Custom Callbackを作成しています。
image.png
送信するJSON Bodyは、下記の通りとしておきます。{data}のところに、歯ブラシが抜かれたときに01が入るようになります。

callback.json
{
  "device": "{device}",
  "time": {time},
  "data": "{data}",
  "seqNumber": {seqNumber},
  "deviceTypeId": "{deviceTypeId}"
}

Google Cloud PlatformでSigfoxメッセージを受信

GCPのCloud FunctionsでHTTPトリガの関数を作成します。
image.png
今回は、関数のランタイムをPython3.7とし、以下mainのエントリポイントで関数を作成しました。
ここでは、Sigfoxクラウドからのコールバックを受信し、BigQueryに歯磨き時刻を記録するとともに、LINE Messaging APIを用いメッセージを送信しています。

main.py
import os
import sys
import urllib.request
import urllib.parse
import json
import base64
from flask import abort
from rfc3339 import rfc3339
from datetime import datetime, timedelta, timezone
from google.cloud import bigquery

def main(request):
    request_dict = request.get_json()
    dt = datetime.fromtimestamp(request_dict['time'], timezone(timedelta(hours=+9), 'JST'))
    storeDB(dt, request_dict)
    html = sendLineMsg(dt, request_dict)
    print(html.decode('utf-8'))

def storeDB(dt, request_dict):
    PROJECT    = os.environ.get('PROJECT')
    DATASET    = os.environ.get('DATASET')
    TABLE      = os.environ.get('TABLE')

    output_rows = []
    output_rows.append([
        dt,
        request_dict['device'],
        request_dict['seqNumber'],
        request_dict['data']
     ])

    bq_client   = bigquery.Client(PROJECT)
    dataset_ref = bq_client.dataset(DATASET)
    table_ref   = dataset_ref.table(TABLE)
    table_obj   = bq_client.get_table(table_ref)
    errors       = bq_client.insert_rows(table_obj, output_rows)
    if errors == []:
      print("New rows have been added.")
    else:
      print("Encountered errors while inserting rows: {}".format(errors))

def sendLineMsg(dt, request_dict):
    stime = dt.strftime('%m月%d日 %H:%M')
    payload = request_dict['data']
    if payload == '01':
        text = 'はみがきはじまったよ\n' + stime 

    line_msg = {
          "messages":[
                {
                    "type":"text",
                    "text": text
                }
            ]
    }

    h = {'Content-Type': 'application/json', 'Authorization': 'Bearer <YOUR-CHANNEL-ACCESS-TOKEN>'}
    request = urllib.request.Request('https://api.line.me/v2/bot/message/broadcast', data=json.dumps(line_msg).encode("utf-8"), headers=h, method='POST')
    response = urllib.request.urlopen(request)
    print(response.getcode())
    html = response.read()
    return html

LINEチャネルの開発設定方法は省略しますが、以上が成功すると下図のように"はみがきはじまったよ"メッセージがLINE BOTで配信されます。
image.png
今回は、LINEメッセージをブロードキャストしていますので、はみがきチャネルに参加した人すべてに配信されてしまいます。本来は、伝えたい方(家族など)にのみ配信できるようにすべきですね。

振り返ってみて

今回、歯ブラシスタンドで歯ブラシが抜かれたことをPIRセンサを用いました。最初は、感圧センサ(FSR402)を使ってみたのですが、感度特性のためか、歯ブラシ程度の重さ(圧力)をうまく検知できませんでした。
image.png
逆に、PIRセンサを用いることにより、検知はできるようになったのですが、当然のごとく、「抜いた」のか「刺した」のかがわからないという結果。
本当に使えるようにする場合は、軽量なものにも反応するようなハード的なスイッチを用意し、マイコンや通信モジュールのスリープ制御もできれば良いのだと思っています。
image.png

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6