10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LINEを「ラズパイのターミナル化&スマートホームのフロントエンド化(ChatOps風)」してみたよ♪

Last updated at Posted at 2018-10-17

#■はじめに
ラズパイをスマートホームHubにして GoogleHome/AmazonEcho、黒豆、スマートスイッチなど色々組み合わせておうちハックを楽しんでいるのですが [紹介記事]、自宅内では声で操作すれば良いとして、外出先から自宅のスマートホームに対して何かやりたいとき、例えば各機器の動作ログを見るとか、自宅に誰がいるかステータス見るとか、遠隔で玄関のドアを開けるとか、クーラーを消すとか。色々やろうと思うと、機器個別の公式アプリなどあればそれを使っても良いのですが、それも種類が増えると煩雑で・・ついついスマホから自宅ラズパイに VPN 張って SSH で入ってコマンド打っていました(笑)

が・・・さすがにスマホの画面でキャラクタ端末だと文字も小さく使いずらいので・・最近はやりの ChatOps 風に♪ LINE から遠隔でスマートホームを操作できるようにしてみました。

※ついでに LINE を疑似的なラズパイのターミナルに見立てて任意のコマンドを遠隔実行できる「おふざけ機能」も入れてみました。(結局 LINE でもコマンド打ってるし&はっきり言ってセキュリティーホール以外の何者でもありません・・笑)

#■全体図
line-firebase2.png

LINE Messaging API で LINE と firebase を繋ぎ、LINE のメッセージを firebase からラズパイに通知しています。逆にラズパイから LINE へのメッセージは直接ラズパイから LINE API を叩いています。

#■動作画面
以下が実際に使用しているときの画面です。
line-snap.png

#■前提:LINE/firebase/ラズパイの接続
今回のこの記事では LINE から通知を受け取ったあとにラズパイ側でどういった実装を行ったのか? ChatOps の BOT サーバー側開発のお話とでも言えば良いでしょうか?にフォーカスした内容をメインにしました。

と言う事で、LINE からラズパイへの通知経路の実装については別記事でご紹介していますのでそちらのリンクを参照してください。

詳細説明:LINE と Firebase とラズパイを繋いでみたよ☆

#■今回実装している機能:利用例
今回手始めに主に以下のような機能を LINE から実行できるようにしています。
遠隔でLINEからスマートホーム・ラズパイが制御できるようになると色々やりたい事も増えてくると思いますので都度この記事も拡充していきたいと思います。

☆自宅の様子をカメラで撮影
 自宅内カメラは人感センサーで自動で撮影されますが、手動でも撮影する機能です。撮影した画像は GoogleDrive に転送して画像 URL を LINE のメッセージで返します。
☆カメラの自動撮影ログの表示機能
 実質的に自宅のどこに?いつ人が居たか?のログになります。
☆スマートホーム各機器の動作ログ参照
 ログを全部送ると重いので1日分程度のログだけをGoogleDriveに上げてメッセージで URL を送ります。
☆家族の在宅状況表示
 家族の誰が自宅にいるか?のステータス表示です。
 自宅にいるかどうかは各自スマホの位置情報やWIFI接続情報をラズパイ側で保持しています。
☆玄関ドアの状況確認と制御
 スマートロックの sesame を玄関ドアに設置しているのでそのステータス確認や遠隔制御です。公式アプリもありますが、重いので基本制御は WEB API で、公式アプリは使っていません。
参考 [スマートロック SESAME の WEB API が便利だった!]
☆サービス再起動
 たまにサービスの一部が動作不安定になるときがあるので、遠隔でサービスの再起動用です。
☆ラズパイ再起動
 ずっとラズパイをつけっぱなしにしているとたまに動作が重くなったりおかしくなるので、遠隔で再起動します。緊急用です。
※任意のコマンド実行機能
 任意の linux コマンドを実行し、結果を LINE のメッセージで返します。操作が必要なコマンドは無理ですがコマンド一発のものでちゃんと標準ストリームに結果を吐くコマンドなら大抵動きます。(簡単な機能まで BOT 用シェルスクリプトとかいちいち用意するのが面倒になったのでそのままコマンド実行しちゃえ!が正直なところです・・・ハイw)

※最後の機能ははっきり言って**セキュリティー的にとても危険**ですので、冗談だと思ってくれぐれもご自身の環境には入れないでくださいね(笑) LINE アカウント乗っ取られた瞬間に自宅まで入って来られて、LAN 内でやりたい放題ですから・・汗 もしどうしても使いたいなら、都度 LINE のトーク履歴くらいは削除しましょう。

#■コード
##■BOT 本体
上記の LINE ~ firebase ~ ラズパイで受け取った通知を渡す BOT のメイン部分です。
上記の LINE Firebase ラズパイに接続記事のコードの最後 // 好きな処理 と言う箇所で commandLine( 'node lineCmdMode.js "' + msgText + '"' ) みたいにメッセージを引数にして渡して呼び出してください。

lineCmdMode.js
logBackup_cmd = 'logBackup.sh ';
linePush_cmd = 'node line-push.js ';
lineExec_cmd = 'lineExecCmd.sh ';

const execSync = require('child_process').execSync;
commandLine = function( cmdLine ) {
   const result = execSync( cmdLine ).toString();
   console.log(result);
   return this;
}

//----------------------------------------------------------------
//  コマンド判別&実行
var lineCmdMode = function( execCmd, param1, param2, param3, param4, param5  ) {
    var replyMsg = 0;

    //----------------------------------------------------------------
    //  自宅内のスナップショット撮影
    if( execCmd == "snap" )
    {
        replyMsg = "snap コマンドを実行しました";
        // 自宅内を撮影して画像を GoogleDrive にアップロード(ソースは割愛)
        commandLine( 'bash snap_cmd.sh' );
        // URL を LINE に通知
        commandLine( linePush_cmd + ' "https://drive.google.com/drive/folders/xxxx"' );
    //----------------------------------------------------------------
    // スナップショット撮影ログ表示
    } else if( execCmd == "snaplist" ){
        replyMsg = "snaplist コマンドを実行しました";
        // スナップショット出力ディレクトリの ls 結果を LINE に返す
        commandLine( lineExec_cmd + ' ls -lt /tmp/sensorImg'; );
    //----------------------------------------------------------------
    // 機器動作 log の直近部分だけ GoogleDrive にコピー
    } else if( execCmd == "log" ){
        replyMsg = "log コマンドを実行しました";
        // log を切り出して GoogleDrive にアップロード
        commandLine( logBackup_cmd );
        // URL を LINE に通知
        commandLine( linePush_cmd + ' "https://drive.google.com/drive/folders/xxxx"' );
    //----------------------------------------------------------------
    //  在宅チェック
    } else if( execCmd == "athome" ){
        replyMsg = "atHome コマンドを実行しました";
        // 在宅フラグのチェックコマンド実行して出力を LINE に返す
        commandLine( lineExec_cmd + "xxxxxxx" );
    //----------------------------------------------------------------
    //  ドア状態確認
    } else if( execCmd == "door" ){
        replyMsg = "door コマンドを実行しました";
        // 玄関のスマートロックのステータス確認コマンドを実行して結果を LINE に返す
        commandLine( lineExec_cmd + 'xxxx'; );
    //----------------------------------------------------------------
    //  linux コマンド実行モード LINE で「exec "ls -la"」 の様にコマンド指定
    } else if( execCmd == "exec" ){
        replyMsg = param1 + ' を実行しました';
        // コマンドを実行して結果出力を LINE に返す
        commandLine( lineExec_cmd + ' ' + param1 );
    //----------------------------------------------------------------
    // help 表示
    } else if( execCmd == "help" ){
        replyMsg =
"【コマンド一覧】\n" +
"snap - カメラ撮影\n" +
"snaplist - 撮影記録一覧\n" +
"log - ログをGoogleDrvに上げる\n" +
"athome - 在宅状態を確認\n" +
"door - ドア状態確認\n" +
"reboot - 再起動";
    //----------------------------------------------------------------
    //  再起動
    } else if( execCmd == "reboot" ){
        replyMsg = "reboot コマンドを実行しました";
        commandLine( 'sudo reboot' );
    //----------------------------------------------------------------
    //  その他はおおむ返し
    } else {
        replyMsg = execCmd;
    }

    // 最後にメッセージ送信
    commandLine( linePush_cmd + ' "' + replyMsg + '"' );
}

// -----------------------------------------------
// 初期起動処理

// 起動したら最初に引数チェック
var argExe = process.argv[0];
var argScr = process.argv[1];
if (process.argv.length < 3) {
    console.log('use: ' + argExe + ' ' + argScr + ' exec-command');
    return;
}

console.log('lineCmdMode start');

// 実行処理関数の呼び出し
lineCmdMode(
    (process.argv[2] ? process.argv[2] : 0 ),
    (process.argv[3] ? process.argv[3] : 0 ),
    (process.argv[4] ? process.argv[4] : 0 ),
    (process.argv[5] ? process.argv[5] : 0 ),
    (process.argv[6] ? process.argv[6] : 0 ),
    (process.argv[7] ? process.argv[7] : 0 )
);

##■ログの分割とGoogleDriveへのアップロード
上記 BOT 本体から呼び出すサブ機能です。
GoogleDrive へのアップロードには rclone を採用しています。
参考:rsyncのクラウド対応版 rclone

logBackup.sh
#!/bin/bash

gdriveCopy() {
    if [ "$1" = "" -o "$2" = "" ]; then
      echo "arg error"
    else
      if [ "$1" = "-1" -o "$1" = "1" -o "$1" = "2" -o "$1" = "3" ]; then
         sudo rclone copy -v --max-depth $1 $2 gdrive:Backup/RaspberryPi/autoBackup/log
      else
         echo "depth arg bad = $1"
      fi
    fi
}

sudo mkdir /tmp/log
sudo chmod 777 /tmp/log
sudo tail /tmp/webhook.log -n 16000 >/tmp/log/webhook-cut.log
gdriveCopy 1 /tmp/log

##■コード:LINE への PUSH メッセージ送信
上記 BOT 本体から呼び出すサブ機能です。
LINE Messaging API の機能の一つ PUSH メッセージを使って任意のタイミングでラズパイから LINE にメッセージを送信しています。LINE 内に遠隔操作用の専用チャンネルを作ってそこに LINE BOT と自分のアカウントが参加していて to で宛先を変えると任意のチャンネルにメッセージを送れます。
テキストメッセージ以外にも色々なデータを送れるみたいなのですが今回は使っていません。
参考:LINE Messaging APIを試してみた
参考:LINE Messaging APIでgroupIdを取得してPush APIでグループにメッセージを送る

line-push.js
var request = require('request');

// 起動したら最初に引数チェック
var argExe = process.argv[0];
var argScr = process.argv[1];
if (process.argv.length < 2) {
    console.log('use error' + argExe + ' ' + argSrc + 'message');
    return;
}

var options = {
  uri: "https://api.line.me/v2/bot/message/push",
  headers: {
    "Content-type": "application/json",
    "Authorization": "Bearer xxxxxxxx",
  },
  json: {
    "to": "xxxx", // 任意のチャンネルID
    "messages": [
        {
            "type": "text",
            "text": process.argv[2]
        }
    ]
  }
};

request.post(options, function(error, response, body){});

##■任意コマンドを実行して結果をLINEに返す
上記 BOT 本体から呼び出すサブ機能です。
任意の linux コマンドを実行して標準ストリームの結果を LINE に返します。
LINE へのメッセージは大体 2000 Byte が上限サイズなので、コマンドの出力結果ログはそのサイズで分割して LINE に送信しています。これで多少長い出力のコマンドでも LINE から実行できます

lineExecCmd.sh
#!/bin/bash

cd /home/pi
shopt -s expand_aliases

# 前回の結果を削除
sudo mkdir /tmp/splitLog
sudo rm /tmp/splitLog/*.*

# 任意のコマンドを実行して結果をログに出力
$@ >/tmp/execCmd.cor

# カラーエスケープシーケンスを sed で削除
cat /tmp/execCmd.cor | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" >/tmp/execCmd.log

# コマンドの出力を 2000 Byte 単位で分割(1回分のメッセージサイズの大体の上限です)
split -b 2000 -d /tmp/execCmd.log /tmp/splitLog/log.

# 分割したファイルを順に LINE に送信
for file in `\find /tmp/splitLog -maxdepth 1 -type f | sort`; do
    echo $file
    node line-push-textfile.js $file
done

最後の line-push-textfile.js コマンドは、上記 line-push.js の亜種で、テキストの代わりに引数のファイルを開いて内容をそのまま LINE メッセージとして送信するコマンドです。ソースは割愛しますがコツとしては fs.readFileSync でファイルを開いて同期処理にしているところくらいです。(そうしないと node みたいな非同期処理系ではファイルを開く前に先に LINE に空メッセージが送られてしまうので・・・)

#■最後に
実際に作ってみると ChatOps って便利で・・こりゃ流行る訳だ!と言う感じです。もっと言語処理のフレームワークとか入れて対話できるような BOT にして、色々機能増やしたら家族にも公開しようと思ったけど、面倒なのでやらないかな?

どうせ家族はログとか見ないし(笑) 家族向けには別途いろんな情報を LINE で通知する BOT を用意しているのでそれで十分かな・・・

ということで・・・・

LINE BOT で遠隔制御出来るようにしたにもかかわらず・・いまだに意味もなくスマホから自宅ラズパイに VPN 張って SSH 端末画面見てニヤニヤしてしています(笑)

##※投稿記事一覧
他にも 自分で「おうちハック」した内容を以下に投稿しているので、よろしければ合わせてご覧ください☆

・GoogleHome/AmazonEcho とラズパイでやった事・やりたい事一覧[LINK]
・Google Home でちょっと未来風のスマート TV を作ってみたよ☆[LINK]
・黒豆 (Broadlink RM Mini 3) の IR 信号解析してみたよ♪ [LINK]
・スマートロック SESAME の WEB API が便利だった![LINK]
・Amazon Echo の沢山ある面白スキルを気軽に遊ぶ方法[LINK]
・LINEを「ラズパイのターミナル化&スマートホームのフロントエンド化(ChatOps風)」してみた♪[LINK]
・LINE と Firebase とラズパイを繋いでみたよ☆[LINK]
・GoogleHome と Chromecast でキーワード&画像&Wikipedia検索~TV表示[LINK]
・スマホ ハック&スマートホーム連携 [LINK]
・ラズパイとサーモセンサーで自宅のガスコンロ監視(火の用心) [LINK]
・トニー・スタークの家にありそうなスマート・テーブル(笑) [LINK]

10
10
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
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?