MESHの動きタグとWordPressで1日のアクティビティを(自分だけに)可視化してみた


みなさんこんにちは。今日も結婚できなかったぼくはぼっちなクリスマスイヴを過ごす予定です。この投稿は、 さくらインターネット Advent Calendar 2017 、2017/12/24の投稿です。

ぼっちなクリスマスを過ごす我が家にミニスカサンタなおねぃさんが来るわけがないので、戸締まりはしっかりしておきたいものです。数年前からMESHの動きタグとSwarmのチェックイン履歴をロギングしていますが、WordPressでこれらを可視化してみました。弊社の社屋も兼ねているので、現状大家さんに怒られない範囲においては万全のセキュリティです01)MESHだけじゃ心もとないとか言わないでください!エントランスはオートロックなんですから!><

MESHとは

MESHとは、Make(つくって)、Experience(体験して・楽しんで)、SHare(シェアしよう)という意味が込められたIoTデバイスで、難しいプログラミングや電子工作の知識は必要ありません。

私はMESH「ボタン」「LED」「動き」3タグセットを購入しましたが、今回使用するのは「動き」タグです。

MESHで実現したこと

MESHの動きタグは玄関のサムターンに両面テープで接着し、解錠と施錠をトリガーにして

  • ロギング
  • 通知

を実現しています。

また、同じログファイルにSwarmのチェックインをロギングしているので、1日のアクティビティを時系列で追うことができます02)チェックイン漏れをSwarmから指摘されて後日チェックインした場合、デイリーでローテーションされたログファイルを編集する必要があります

処理全体のフローを簡単な絵にするとこんな感じになります。

準備するもの

MESHアプリの設定

MESHアプリはこんな感じに設定しています。

MESHアプリではいわゆる if の条件分岐をせず、

  • 動きタグの向きが変わったら
  • 動きタグ->IFTTTの連携が終わったら

の2つのトリガーで音を鳴らすようにしています。何本もスピーカーに線が伸びているのは、単にギターとベースと歓声を同時に鳴らしたかっただけです。スピーカーを鳴らすことで、万が一誰かが合鍵的なものをこさえて侵入したとしても、侵入者がビビるという心理的効果があるので、サウンドを鳴らすのはおすすめです。

MESH 動きタグの設置

こんな風に、玄関のサムターンに両面テープで固定します。モバイルバッテリーもドアにくっついていますが、これは余剰品活用と充電時のインジケーター点灯で通電確認をするためにくっつけているので、あってもなくてもよいです。電源供給元兼バッチ処理のマシンにUPSがなければ、モバイルバッテリーはあったほうがよいでしょう。

ドアまわりの配線

ドアまわりのUSBケーブルの配線ですが、ドア開閉の際に引っ掛けて断線しないように工夫しています。

画像左側がどこの逸般の誤家庭にもある19インチラックもといシューズボックスですが、観音扉の隙間からUSBケーブルをうまいこと伸ばしています。万が一ケーブルが断線したときに交換しやすいよう、延長ケーブルをかましてMESHにはなるべく配線作業で触れないようにしています。

これが電源供給元兼バッチ処理マシンです。ASUSのU24Eが大好きなので、これにFedora27をインストールしています。

ラックを閉じるとこんな感じで、とてもPCが動いているようには見えません。

MESHの動きタグからDropboxへログを吐く

MESHの動きタグからDropboxのテキストファイルへ開閉ログを吐くために、IFTTT連携を使っています。ここまでは特にプログラミングをする必要はなく、淡々と追記しているだけです。

サムターンの動きは横->縦と縦->横の2種類しかないので、ロギングのアプレットは2つのみです。スクリーンショットにはありませんが、開閉のどちらかでPushbyllet通知を行い、スマホにプッシュ通知するようにしています03)かつてはメール通知していましたが、読み返すことはないのでプッシュ通知にしました

通知系の処理は以上ですが、IFTTTからDropboxへのテキスト追記はファイルサイズに制限があるので、デイリーでローテーション(日付変更後に前日のログを切り出して別ファイルに出力)しています。コードはこちらです。CommonLifeLogSorter というシェルスクリプトを書いて、crontabには引数を与えて実行しています。

#!/bin/bash
set -eux
export LANG=C
export LC_ALL=C

DATE=$1

# DVC = DAY VALUE CHECK
DVC=$(echo ${DATE} | egrep "^[[:digit:]]{4}/[[:digit:]]{1,2}/[[:digit:]]{1,2}$" > /dev/null ; echo $?)
[ ! 0 = ${DVC} ] && exit 1

# SDAY = DATE変数の日付をゼロパディングする
SDAY=$(echo ${DATE} | date +%Y/%m/%d -f -)

BASEDIR=${HOME}/Dropbox
LOGDIR=${BASEDIR}/APPS/LOGS
LOGNAME=CommonLifeLog
LOGFILE=${LOGDIR}/${LOGNAME}.txt
OLDLOGDIR=${LOGDIR}/OLD/${LOGNAME}/$(dirname ${SDAY})
OLDLOGFILE=${OLDLOGDIR}/$(echo ${SDAY} | sed -e "s/\//-/g").txt

MONTH=$(echo ${DATE} | date +%B -f -)
INPUTDAY=$(echo ${DATE} | date +%B" "%d," "%Y -f -)
INPUTDAY_NOSUP=$(echo ${INPUTDAY} | sed -e "s/${MONTH} 0/${MONTH} /")

# LINES = Lines of VALUE at Logfile counts
LINES=$(cat ${LOGFILE} | egrep "(${INPUTDAY}|${INPUTDAY_NOSUP})" | wc -l)

# 引数に与えた日付をLOGFILEからgrepしてOLDLOGFILEへ吐き出す関数
LOGGREP() {
  for USHI in {A,P}M
  do
    cat   ${LOGFILE}                               | \
    xargs --null                                   | \
    egrep ${USHI}                                  | \
    egrep "^(${INPUTDAY}|${INPUTDAY_NOSUP}) at"    | \
    sed   -e "s/^${INPUTDAY_NOSUP}/${INPUTDAY}/g"  | \
    sed   -e "s/at 12:/at 00:/g"                   | \
    sort  -n
  done    | tee ${OLDLOGFILE}
}

# 引数に与えた日付をLOGFILEから削除する関数
LOGLINEDELETE() {
  sed -i "/${INPUTDAY}/d" ${LOGFILE}
  sed -i "/${INPUTDAY_NOSUP}/d" ${LOGFILE}
}

mkdir -p ${LOGDIR} ${OLDLOGDIR}
if [ ! 0 = ${LINES} -a ! -f ${OLDLOGFILE} ];
then
  LOGGREP && LOGLINEDELETE
fi
05 00 *  *  * /home/nullpopopo/bin/CommonLifeLogSorter $(date +\%Y/\%m/\%d --date="1 days ago") > /dev/null 2>&1

もう1つ、出かけた後に「そういや鍵閉めたっけ!?」と不安になることはよくあることだと思いますが、鍵開閉の最新ステータスを1つのファイルに抜き出すスクリプト KeyOpenStatusLogger も書きました。

#!/bin/bash
#set -eux
export LANG=C
export LC_ALL=C

BASEDIR=${HOME}/Dropbox
LOGDIR=${BASEDIR}/APPS/LOGS
LOGNAME=CommonLifeLog
LOGFILE=${LOGDIR}/${LOGNAME}.txt
OLDLOGDIR=${LOGDIR}/OLD/${LOGNAME}
OLDLOGFILE=$(find ${OLDLOGDIR}/ -type f | sort -n | tail -n 1)
STATDIR=${BASEDIR}/APPS/STATS
STATFILE=${STATDIR}/${LOGNAME}-LastStat.txt

mkdir -p ${STATDIR}

# 検索ワードを変数に格納する
ROWORD=ShinagawaNOC-KeyOpenStatus #ROWORD="Retrieval Object"

# 異常終了条件判定
# ログファイル、1世代前のファイルの両方がなければ異常終了させる
if [ ! -f ${LOGFILE} -a ! -f {OLDLOGFILE} ];
then
  exit 1
fi

# ログファイル、1世代前のファイルの両方に検索ワードがなければ異常終了させる
if [ 0 = $(cat ${LOGFILE} ${OLDLOGFILE} | egrep ${ROWORD} | wc -l) ];
then
  exit 1
fi

# ログファイル、1世代前ファイルから最新のステータスを検出する
for USHI in AM PM
do

cat ${OLDLOGFILE} | \
xargs --null | \
egrep ${USHI} | \
egrep ${ROWORD} | \
sed -e "s/$(LANG=C date +%B)[[:space:]][1-9],/$(LANG=C date +%B" "%d,)/g" | \
sort -n | \
tail -n 1

cat ${LOGFILE} | \
xargs --null | \
egrep ${USHI} | \
egrep ${ROWORD} | \
sed -e "s/$(LANG=C date +%B)[[:space:]][1-9],/$(LANG=C date +%B" "%d,)/g" | \
sort -n | \
tail -n 1
done | tail -n 1 | tee ${STATFILE}

こちらは引数なしで毎分cronで実行するようにしています。

以上でロギングの処理はできました。なお、Dropboxですが、設定でLAN Syncを止められる上にファイアウォールで遮断されたことをDropboxが検知したら止まるのですが、不用意に有効になって他ホストへ迷惑をかけないよう、自宅でのみ動かし、自宅のFedoraからさくらのVPSへはrsyncで同期しています。

WordPress側の設定 (functions.php)

さくらのVPS上にWordPressをインストールして、ここに1日のアクティビティを公開するために、wp-cliを使います。なお、自分のアクティビティは自分だけが見れればよい(外からも自分で確認したいが他人からは見られたくない)ので、functions.php に記述を追加しています。

ログインしていないクライアントが接続しに来たらGoogleへリダイレクトする

function require_login() {
  if ( ! is_user_logged_in() &&
       ! preg_match( '/^(wp-login\.php|async-upload\.php)/', basename( $_SERVER['REQUEST_URI'] ) ) &&
       ! ( defined( 'DOING_AJAX' ) &&
       DOING_AJAX ) &&
       ! ( defined( 'DOING_CRON' ) &&
       DOING_CRON ) ) {
      wp_redirect('https://www.google.com/');
  }
}
add_action( 'init', 'require_login' );

コードは上記の通りで、もしログインしていないクライアントが接続しにきたらGoogleへリダイレクトすることで、コンテンツを保護します。ただし、wp-login.phpとasync-upload.phpは例外としています。また、Ajaxやwp-cronの邪魔もしないように条件分岐しています。こちらは前職の黄色い上司のコードを参考にさせていただきました04)なお、ログインURLを変えている場合は、ログイン用のファイルを正規表現で追記してください

wp-cliで1日のアクティビティを投稿する

wp-cliで1日のアクティビティを投稿する処理を、さくらのVPS側にシェルスクリプトで書いてあげます。ログのソート処理が終わった5分後くらいを目処に、OS側のcronでこれを実行しています。

このシェルスクリプトの実行ユーザーはkusanagiである必要はありませんが、他ユーザーが実行する場合、 /home/kusanagi/<プロファイル名>/DocumentRoot ディレクトリ以下のファイルが読めることが条件です。

以下コードはKUSANAGI環境を前提としているので、非KUSANAGI環境である場合はDocumentRootのPATH(変数WPDIRの値)をよしなに書き換えてください。

もう1点、WP_POST_ACTIVITY 関数中で実行しているwpコマンドの引数「--post_category」に代入する数字ですが、投稿カテゴリーに対応するカテゴリーIDの数字を与えてあげてください。

#!/bin/bash

WPUSER=wordpress_username
PROFILE=hogehoge.example.com
POST_TITLE="$(date +%Y/%m/%d --date="1 days ago") 行動履歴"
MESSAGE="昨日の行動履歴はありませんでした。"

TODAY=$(date +%Y%m%d)
YESTERDAY=$(date +%Y%m%d --date="1 days ago")
PROGNAME=$(basename ${0})
TXTFILE_DIR=${HOME}/TXT
TXTFILE=${TXTFILE_DIR}/${PROGNAME}_${TODAY}.txt
OLD_TXT=${TXTFILE_DIR}/${PROGNAME}_${YESTERDAY}.txt
WPDIR=/home/kusanagi/${PROFILE}/DocumentRoot

LOGDIR=${HOME}/APPS/LOGS/OLD/CommonLifeLog
YESTERDAY_YEAR=$(date +%Y --date="1 days ago")
YESTERDAY_MONTH=$(date +%m --date="1 days ago")
YESTERDAY_DAY=$(date +%Y-%m-%d --date="1 days ago")
LOGFILE=${LOGDIR}/${YESTERDAY_YEAR}/${YESTERDAY_MONTH}/${YESTERDAY_DAY}.txt

LOCKFILE=${TXTFILE_DIR}/.${PROGNAME}_${TODAY}.LOCK
LOCK_OLD=${TXTFILE_DIR}/.${PROGNAME}_${YESTERDAY}.LOCK

MAIN_FUNCTION(){
if [ ! -f ${LOCKFILE} ];
then
  if [ -f ${LOGFILE} ];
  then
    CREATE_POST_TXT_HISTORY
  else
    CREATE_POST_TXT_NOHISTORY
  fi
  WP_POST_ACTIVITY
  touch ${LOCKFILE}
  rm -f ${LOCK_OLD}
fi
}

WP_POST_ACTIVITY(){
POST_AUTHOR_EXTRACT
/usr/local/bin/wp --path=${WPDIR}  \
  post create ${TXTFILE}           \
  --post_status=private            \
  --post_title="${POST_TITLE}"     \
  --post_category=666              \
  --post_author=${POST_AUTHOR}
}

CREATE_POST_TXT_HISTORY(){
cat << _EOL_ | tee ${TXTFILE}
昨日のアクティビティは以下の通りです。
<pre>
$(cat ${LOGFILE} | cut -b 22-)
</pre>
_EOL_
}

CREATE_POST_TXT_NOHISTORY(){
cat << _EOL_ | tee ${TXTFILE}
${MESSAGE}
_EOL_
}

POST_AUTHOR_EXTRACT(){
POST_AUTHOR=$(
              /usr/local/bin/wp --path=${WPDIR}          \
                user list                                \
                --format=json 2> /dev/null             | \
                sed -e "s/},{/}\n{/g"                  | \
                egrep "(\"user_login\":\"${WPUSER}\")" | \
                sed -e "s/[\[{,]/\n/g"                 | \
                egrep ID                               | \
                awk 'BEGIN {FS=":"} {print $NF}'
             )
}

MAIN_FUNCTION

玄関の開閉があったりSwarmのチェックインがあったりしたらアクティビティログファイルの中身が投稿され、もし1日引きこもっていれば、ブログに「昨日の行動履歴はありませんでした。」と投稿されます。

他にも、1分以上ドアが開きっぱなし(解錠ステータス)の場合、テキストファイルのタイムスタンプとログの最終行を判断して「開きっぱなしだよ!」と検知したらスマホに通知するようにもしています。

いかがでしたでしょうか。シェルスクリプトなIoTでQoLの向上をご提案いたします。ではまた!

References   [ + ]

01. MESHだけじゃ心もとないとか言わないでください!エントランスはオートロックなんですから!><
02. チェックイン漏れをSwarmから指摘されて後日チェックインした場合、デイリーでローテーションされたログファイルを編集する必要があります
03. かつてはメール通知していましたが、読み返すことはないのでプッシュ通知にしました
04. なお、ログインURLを変えている場合は、ログイン用のファイルを正規表現で追記してください