どーもヘタレエンジニアまる です。
我が家は共働き家庭でして家事分担でよく揉めます、ケンカになることもしばしば。
ということでお互いが何をやったかの可視化を目的に家事botをLineで作ることにしました。
DIYの第1弾は監視カメラを作りました、興味があればそちらもどうぞ。
第二弾は手抜きシステム
ルンバさんハックはこんなこともやってます
システム概要
実際の出来上がったブツはこんな感じです
夏休みD.I.Yで、夫婦の仲を円満にするためにLine botを使った家事管理システムを構築中。。。 pic.twitter.com/64d6RrodfP
— Y.Y (@marutymaruty) August 15, 2019
こんな感じで朝6:50に今日やらないといけない家事をbotからメッセージとして発信されます。(cron-job → LINE1)
で、「やるよ」「やらないよ」を選びます。
そうするとデータが裏で登録されます(LINE→LINE3)
やらないよを選んだ場合、昼13:00ごろに再び聞かれます。「やるよ」を選んでいた場合は「本日の家事は完了してます」となります。(cron-job → LINE1)
※これは嫁が夜勤の日があるのでその分担です。
毎週土曜日にはこんな感じでやったこと・やれていないことがリストとしてメッセージが発信されてその週に出来ていない家事を一気にやります。(cron-job → LINE2)
まとめるとシステム構成はこんな感じです。
botの登録をしましょう
まずはbotの登録のためにLINEのDeveloper登録をしましょう
登録できたら新規プロバイダーを作成します
今回はMessageAPIを使います
プロバイダ > 新規チャンネルの作成
適当にもろもろ埋めていきます
こんな感じで出来ました
ここで作ったチャンネルのChannel Secret と アクセストークンを確認します
LINEにアクセスするための情報はこれでいったんOK、次に関数を作ります。
DataStore を利用できるようにしましょ
今回家事やったよの情報をDataStoreに保存してみることにしました。(初めて使う)
とりあえず雑にやってみます。
エンティティを新規に作成
こんな感じで定義して1レコード作っておきます、これでデータ準備は完了
Cloud Functions を利用できるようにしましょ
最終的にはこんな感じでCloud Functions に関数を登録していく
5分でわかる!Google Cloud Functions の使い方(あぱーブログさん) がめちゃめちゃわかりやすかったので参考にして登録させてもらいました。
まずはLINE1のプログラム(毎日通知する)を書きましょう
line-function-01
#main.py import os import base64, hashlib, hmac import logging import datetime from flask import abort, jsonify from linebot import ( LineBotApi, WebhookParser ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, MessageAction, TemplateSendMessage, ButtonsTemplate, TextSendMessage, URIAction ) from google.cloud import datastore #これで基本的に自動通知用の関数として使う、こちらで定期通知イベント発火をすることとする LINE_CHANNEL_ACCESS_TOKEN = "LINEのチャンネルアクセストークン" LINE_CHANNEL_SECRET = "LINEのチャンネルシークレット" line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN) client = datastore.Client("GCPのPJ_ID") def main(req): user_id = ["LINEに通知するユーザーのuserID1","LINEに通知するユーザーのuserID2"] weekday = datetime.date.today().weekday() #0=月曜日 weekday_list = { 0:"月曜日", 1:"火曜日", 2:"水曜日", 3:"木曜日", 4:"金曜日" } task_list = { 0:"ルンバの掃除オンにして家出てね!", 1:"ごみ捨てをして行ってね!★", 2:"洗濯してね!", 3:"プラスチックごみとカン・ビン・ゴミ捨てていってね!", 4:"洗濯取り込んでね!" } task_image_list = { 0:"https://1.bp.blogspot.com/-huCenw23rLw/Ur1GQELz2mI/AAAAAAAAcaQ/799-0K2TH1E/s400/robot_soujiki.png", 1:"https://1.bp.blogspot.com/-3RwkghjMBIA/W0mF0B6FO2I/AAAAAAABNU0/LqvuIulVyog7jzDiEdtADnAJiWq2h6RRACLcBGAs/s800/gomidashi_man.png", 2:"https://2.bp.blogspot.com/-RgdKG023QZc/W0mF3APVtRI/AAAAAAABNVI/cH8nmRPN5KA-pfMTOZjJC6isMXAECt6egCLcBGAs/s800/jiko_sentakuki_rouden_earth.png", 3:"https://2.bp.blogspot.com/-xhHFpqit9Ds/WdyDbw9vAfI/AAAAAAABHco/EXUKorrGnqE46L3-Ac7OWBu-gdyNbhtSQCLcBGAs/s400/gomi_petbottle_fukuro.png", 4:"https://2.bp.blogspot.com/-oW0tc6eL_98/UrEhmZgntFI/AAAAAAAAb64/IvZPKjIju6c/s450/sentaku_tatamu.png" } #メッセージ作成 url = "https://このあと作る登録用のfunction URL/hogehoge?&question=" + task_list[weekday] + "&date=" + datetime.date.today().strftime('%Y-%m-%d') messages = TemplateSendMessage( alt_text="今日の家事は・・・・?", template=ButtonsTemplate( text=task_list[weekday], title="今日は"+weekday_list[weekday]+"! 本日の家事は・・・?", image_size="cover", thumbnail_image_url=task_image_list[weekday], actions=[ URIAction( uri=url+"&member=" + user_id[0] + "&answer=1", label="はーい!XXがやります!" ), URIAction( uri=url+"&member=" + user_id + "&answer=1", label="はーい!XXがやります!" ), URIAction( uri=url+"&member=" + user_id[0] + "&answer=0", label="すまぬ!XXは本日は出来ぬ!" ), URIAction( uri=url+"&member=" + user_id + "&answer=0", label="すまぬ!XXは本日は出来ぬ!" ) ] ) ) #本日すでに家事実行していたら、実行済のメッセージを送る key = client.key("Task") query = client.query(kind="Task") dt = datetime.datetime.now() dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) # Returns a copy query.add_filter('insert_day', '>=', dt) for item in query.fetch(): if "1" in item['task_execute']: messages = TextSendMessage(text=f"本日の家事は完了してます") line_bot_api.multicast(user_id, messages=messages) if __name__ == "__main__": main()
#requirement.txt # Function dependencies, for example: # package>=version flask line-bot-sdk google-cloud-datastore
ここの通知するためのユーザーIDは別途取得する必要があります。
自分のユーザーIDはLINE Developersで確認できます
奥さんのLINE IDは同じようにdevelopersに登録して確認してもらうか、このへんのAPIを呼んで確認するbotを別途作って取得する必要があります。
ユーザーID取得するためのメッセージに反応するbotを別途作ってみましょう
こんな感じ
line-function-getUserId
import os import base64, hashlib, hmac import logging from flask import abort, jsonify from linebot import ( LineBotApi, WebhookParser, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, MessageAction, TemplateSendMessage, ButtonsTemplate, TextSendMessage ) import datetime #ユーザーのメッセージに反応する関数 def main(request): channel_secret = os.environ.get('LINE_CHANNEL_SECRET') channel_access_token = os.environ.get('LINE_CHANNEL_ACCESS_TOKEN') line_bot_api = LineBotApi(channel_access_token) parser = WebhookParser(channel_secret) body = request.get_data(as_text=True) hash = hmac.new(channel_secret.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest() signature = base64.b64encode(hash).decode() if signature != request.headers['X_LINE_SIGNATURE']: return abort(405) try: events = parser.parse(body, signature) except InvalidSignatureError: return abort(405) for event in events: if not isinstance(event, MessageEvent): continue if not isinstance(event.message, TextMessage): continue #通常リプライ profile = line_bot_api.get_profile(event.source.user_id) user_id = ["自分のuserID","他に通知したい人がいればここに"] messages=TextSendMessage(text=event.source.user_id) line_bot_api.multicast(user_id, messages=messages) return jsonify({ 'message': 'ok'})
# Function dependencies, for example: # package>=version line-bot-sdk flask
こいつを作ったあとトリガーのURLをLINEのWebhookに設定する
これで該当チャンネルで呟けばUserIDが取得できます。
このコードをいじくればなにかつぶやいたらそれに連動してなにかするようなシステムを組めますが、今回はあくまでもユーザーID取得のためのものですので確認した以降は使いません。
家事をやる・やらないをアクションした後通知する部分を作りましょう
続いて やります! やりません!をアクションした後のbotの通知を作りましょう。
こちらはつぶやいたURLにパラメータをつけて渡す形で実現します
line-function-01の以下URL作成部分で渡すURLを構築してます
question = 家事名
date = 日付
member = 自分 or 嫁
answer = 0 やる 1やらない
こんな感じのパラメータを渡して家事登録をします
“https://このあと作る登録用のfunction URL/hogehoge?&question= 家事名 + “&date=” + datetime.date.today().strftime(‘%Y-%m-%d’)&member=” + 自分 or 嫁 + “&answer= 0 やる 1やらない”,
line-function-03
import os import base64, hashlib, hmac import logging import datetime import urllib.parse from flask import abort, jsonify from linebot import ( LineBotApi, WebhookParser ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, MessageAction, TemplateSendMessage, ButtonsTemplate, TextSendMessage, URIAction ) from google.cloud import datastore #これで基本的に自動通知用の関数として使う、こちらで定期通知イベント発火をすることとする LINE_CHANNEL_ACCESS_TOKEN = "LINEのチャンネルアクセストークン" line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN) client = datastore.Client("GCPのPJ名") def main(request): """ ?member=LineユーザーID&question=質問&answer=0:No1:yesで設定&date """ request_json = request.get_json() user_id = ["Lineの通知したいuserID","Lineの通したいuserID"] if request.args and 'member' in request.args: member = request.args.get('member') if request.args and 'question' in request.args: question = request.args.get('question') if request.args and 'answer' in request.args: answer = request.args.get('answer') if request.args and 'date' in request.args: date = request.args.get('date') if "LINE USER ID" in member: member_name = "XX" else: member_name = "XX" return_text = member_name + "は今日の" + question + "を" if "1" in answer: return_text += "やります" else: return_text += "すまぬ・・・できません" messages = TextSendMessage(text=return_text) line_bot_api.multicast(user_id, messages=messages) data_insert(member, question, answer, datetime.datetime.strptime(date, "%Y-%m-%d")) return "<h1>返信が完了しましたブラウザを閉じてください</h1>" def data_insert(member, question, answer, date): # クライアントの設定 key = client.key("Task") entityA = datastore.Entity(key) dataA = dict() dataA['kaji_naiyo']= question dataA['task_execute']= answer dataA['insert_day']= date dataA['member']= member entityA.update(dataA) client.put_multi([entityA])
# Function dependencies, for example: # package>=version flask line-bot-sdk google-cloud-datastore
これで選択肢が出た後のアクションをDataStoreに登録することが出来ました
土曜の家事実施状況通知
最後に1週間のタスク実施状況を出力する関数を作りましょう
line-function-02
import os import base64, hashlib, hmac import logging import datetime from datetime import timedelta from flask import abort, jsonify from linebot import ( LineBotApi, WebhookParser ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, MessageAction, TemplateSendMessage, ButtonsTemplate, TextSendMessage, URIAction ) from google.cloud import datastore #これで基本的に自動通知用の関数として使う、こちらで定期通知イベント発火をすることとする LINE_CHANNEL_ACCESS_TOKEN = "LINEのチャンネルアクセストークン" line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN) client = datastore.Client("GCPのプロジェクトID") def main(req): user_id = ["ユーザーID","ユーザーID"] weekday = datetime.date.today().weekday() #0=月曜日 weekday_list = { 0:"月曜日", 1:"火曜日", 2:"水曜日", 3:"木曜日", 4:"金曜日" } #今週の条件 土曜日発火のため5日前の月曜日からの未実行データを抽出 key = client.key("Task") query = client.query(kind="Task") dt = datetime.datetime.now() dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) # Returns a copy before_dt = dt - timedelta(days=5) query.add_filter('insert_day', '>=', before_dt) query.add_filter('insert_day', '<=', dt) #query.order["insert_day"] messages = "今週の家事実施状況は以下の通りです(土曜定期配信)\n" for item in query.fetch(): messages = messages + item['insert_day'].strftime('%Y-%m-%d') messages = messages + ":" + item['kaji_naiyo'] + '-' if item['task_execute'] == '0': messages = messages + "未実施" else: messages = messages + "実施済" if item['member'] in user_id[0]: messages = messages + "(XX)\n" else: messages = messages + "(XX)\n" line_bot_api.multicast(user_id,TextSendMessage(text=messages)) if __name__ == "__main__": main()
# Function dependencies, for example: # package>=version flask line-bot-sdk google-cloud-datastore
これで今週誰がやる・やらないを選択したかの一覧が通知されます
すべての関数をcron登録をする
最後に cron-job.org で定期実行をするjobを登録します
これでシステムが出来ました。
運用してからの感想
今の所、なかなか順調に運用できてます。
嫁も通知がきて何も考えないで家事のやった、やれなかったが返信出来るからストレス少ないというフィードバックをもらっているのでしばらくこのシステムを使って家事分担をしてみます。
※その先にはどっちが家事やっているかの可視化が出来ることは内緒です・・・・笑