投稿者: admin

  • SwitchBotとTaskerを使って生存確認botを作った

    SwitchBotとTaskerを使って生存確認botを作った

    私は一人暮らしをしているただの引きこもりおじさんですが

    「あんたが倒れてそのまま何週間も気付かず腐っていくなんてヤダ!!!」

    って母親がうるさいので、「スマホのロック解除」と「部屋のドアが開く」のどちらもが一定時間行われなかった時に、自動的にLINEで通知を飛ばすbotを作ることにしました。

    長時間外出していてドアの開閉がしばらくなかったとしても、スマホのロック解除が行われているなら無事に生きている判定をするということです。

    同居人が居たり、出勤したりしている人には異変に気が付いてくれる相手が居るので、関係のない話ですね。


    ChatGPT o3-miniに頼りまくったおかげで自分の頭ではほぼ理解出来ていなかったので、多少なりとも理解をする為にこの記事を書くことにしました。

    なので、自分の中では分かってるつもりな部分は説明がとても雑です。

    それでもヒントになる人も居るかも、と思っての公開です。


    ※画像が一部しか表示されない現象が起きているかもしれません。lazy load機能周りな気がしますが、テーマ側かブラウザ側かおま環かも分からないので、変だと思ったらその辺りを選択するとか、全てを選択してみるとか、ページ再読込とかを試してみてください。

    ※iOS(iPhone)でTaskerが使えるかは知りません。似たようなことが出来るソフトはきっとあると思いますが全然知りません。ごめんなさい。

    ※以下、アフィリエイト広告を利用しています。


    使ったもの

    ・Androidスマホ(Xiaomi 11T、Androidならほぼどの機種でも大丈夫なはず)

    ・Androidアプリ Tasker(380円)

    ・SwitchBot ハブミニ(Matter対応)

    ・SwitchBot 開閉センサー

    ・Androidアプリ SwitchBot

    エックスサーバー(レンタルサーバー、phpが使えてサーバーログが取れるなら何でも良いかも、よく分かってないです)

    LINE Massaging API(LINE公式アカウント)

    ChatGPT o3-mini

    ※今回の用途ではMatter対応版である必要はないという認識ですが、スマートスピーカー等と連携して使う可能性を考えて選択しました。

    ※開閉センサー+ハブミニのセットもあるようですが、このセットのハブがハブミニなのかハブミニ(Matter対応)なのかハブ2なのかよく分からなかったので紹介しません。


    この生存確認botの大まかな流れは、

    SwitchBotのハブミニとドア開閉センサーを連携させ

    ドア開閉を検知した際に出る「ドアが開いた」通知と

    スマホのロック解除の両方をTaskerで検知し

    それらの生存アクションが行われた場合には無反応タイマーをリセットするが

    もしタイマーがリセットされないまま12時間が経過した場合には、そのTaskerから指定したLINEアカウントへメッセージを飛ばして通知する

    というものです。


    今回は全力でチャッピー(ChatGPT o3-mini)に頼って無事完成しましたが、私は何も頑張ってこなかった引きこもりおじさんなので、もしチャッピーが居なかったら3ヶ月は掛かっていたと思います。

    ただ、ドアが開閉される度に通知が飛んでいってもいいのなら、ドア開閉センサーとハブミニとWi-Fi環境(テザリングでも良い)があればSwitchBot一式だけで完結します。たぶん。


    ・SwitchBotのハブミニとドア開閉センサーを連携

    SwitchBotアプリにハブミニとドア開閉センサーを追加して、ちょっと設定を弄るか弄らないかぐらいでドアが開いた通知(Androidの画面上部から出てくるやつ)は届くようになりました。


    ・ドア開閉を検知した際に出る「ドアが開いた」通知と、スマホのロック解除の両方をTaskerで検知

    ・生存アクションが行われた場合には無反応タイマーをリセットする

    これらを実現するプロファイルを作成していきます。


    Taskerのプロファイルとタスクの概念ですが、何をトリガー(発動条件)にするかを設定するのがプロファイル、そのトリガーによって実行するプログラムの中身がタスク、とでも言えばいいでしょうか。よく分かってません。とにかく両方要ります。


    画面のロックが解除されたらタイマーリセットというタスクを実行するプロファイル


    SwitchBotの新着通知が表示されたらタイマーリセットというタスクを実行するプロファイル


    こういうプロファイルを作っていくのですが、各プロファイルによって実行するタスクもその都度同時に作るか、先に作っておく必要があります。


    「タイマーリセット」のタスク


    ・タイマーがリセットされないまま12時間(画像ではテストの為に60秒設定)が経過していないかをチェックする「タイマーチェック」のタスク


    ・タイマーがリセットされないまま12時間(画像では○○時間)が経過した場合に、指定したLINEアカウントへメッセージを飛ばして通知する「HTTPリクエスト送信」タスク


    ですが、この

    “指定したLINEアカウントへメッセージを飛ばして通知する”

    を実現する為には

    Webhook、HTTPリクエスト、PHP、Massaging API

    こんなのが必要になります。


    ・Messaging APIを利用するためにLINE公式アカウントを作成

    Messaging APIによって、公式アカウントが通常のLINEユーザーと自動的にやり取り出来る仕組みを作れます。

    Taskerからの通知を送信したいユーザーが公式アカウントを友だちに追加し、Messaging APIを使える公式アカウントを経由して通知メッセージを受け取る形になります。

    LINE Notifyは2025年3月末で終了するため、代替手段となるMessaging APIを使えるようにするにはLINEビジネスIDとLINE公式アカウントの作成が必要になります。

    作成のフローはよく覚えてないですが、ビジネスIDだからといって特に身構える必要もないぐらい普通に作れます。

    ちなみに、一つのLINEビジネスIDからは100個の公式アカウントを作成出来るようです。


    過程は端折りますが、以下辺りの設定や情報が必要です。

    黒塗りした通り、チャネルシークレットとチャネルアクセストークンが必要な情報で、後々WebhookURLの設定とWebhookの利用の有効化をしましょう。


    ・Webhook

    今回の場合、ユーザーから公式アカウントにメッセージを送信する際、内部的に送信されているユーザーIDを取得するために必要です。

    公式アカウントから、さらに指定したURL(今回の場合は.php)へデータを転送する部分がWebhookです。

    ※このユーザーIDはLINEアプリ内で各ユーザーが設定して友だち追加なんかに使えるものとは別物です。公式アカウントと友だちになる場合は、どの公式アカウントと繋がっているのかも区別する必要があるため、それぞれ専用のユーザーIDが内部的に作られて紐付いているのだと思います。たぶん。


    ・PHP

    上記のWebhookによってLINEから送られてくるデータを受け取り、データを変換したり、データの一部を抽出して記録したり、正当なリクエストかを確認したりするプログラムを書いたファイルです。

    今回の場合、内部的なユーザーIDを取得したり、TaskerからのHTTPリクエストを受け取った時にデータ(POSTパラメータ?)を処理して、LINEのMassaging APIへ再転送する役割を持ちます。

    僕は仕事柄レンタルサーバーが必要なので元々契約していましたが、何らかの形でこのPHPを設置する場所が必要になります。


    以下にPHPのサンプルコードを置いていきますが、セキュリティとかそういうの何にも分からないので、全て自己責任でお願いします。

    また、HTTPS環境が必須なようです。


    LINEからのWebhookイベントの内容を確認して、公式アカウントがメッセージを受け取ったその友だちのユーザーIDをサーバーのログへ出力するもの

    <?php
    // line_webhook.php
    
    // チャネルシークレットを設定(LINE Developers コンソールで確認できる値)
    $channelSecret = 'YOUR_CHANNEL_SECRET';
    
    // POSTされたリクエストボディを取得
    $body = file_get_contents('php://input');
    
    // 署名検証
    // リクエストヘッダー "X-Line-Signature" から署名を取得
    $signature = isset($_SERVER['HTTP_X_LINE_SIGNATURE']) ? $_SERVER['HTTP_X_LINE_SIGNATURE'] : '';
    
    // チャネルシークレットを用いてリクエストボディのハッシュを計算し、Base64エンコードする
    $hash = hash_hmac('sha256', $body, $channelSecret, true);
    $expectedSignature = base64_encode($hash);
    
    // 署名が一致しなければリクエストを拒否する
    if ($signature !== $expectedSignature) {
        error_log("Signature validation failed. Expected: $expectedSignature, Received: $signature");
        http_response_code(400);
        exit('Invalid signature');
    }
    
    // ログに生データを書き出す(エラーログなどで確認できます)
    error_log("Received webhook body: " . $body);
    
    // JSONデータをデコード
    $data = json_decode($body, true);
    
    // JSONのデコードに失敗した場合のエラーチェック
    if ($data === null) {
        error_log("Failed to decode JSON. Raw data: " . $body);
        http_response_code(400);
        exit('Invalid JSON');
    }
    
    // イベントデータが含まれているかチェック
    if (isset($data['events']) && is_array($data['events'])) {
        foreach ($data['events'] as $event) {
            // 例:メッセージイベントの場合
            if (isset($event['type']) && $event['type'] === 'message') {
                // source.userId を取得してログ出力
                if (isset($event['source']['userId'])) {
                    $userId = $event['source']['userId'];
                    error_log("Received message from userId: " . $userId);
                    // 必要に応じて、ここでさらに処理を追加できます
                }
            }
        }
    }
    
    // 正常に処理できたので、HTTP 200 OK を返す
    http_response_code(200);
    echo "OK";
    ?>



    Taskerから送信したHTTPリクエストを受け取って処理してLINEへ再送信のようなことをするもの

    <?php
    // line_push.php
    
    $accessToken = 'YOUR_CHANNEL_ACCESS_TOKEN';
    
    // TaskerからPOSTで送られてくるパラメータ(例:送信先ユーザーIDとメッセージ)
    $toUserId = $_POST['to'] ?? '';
    $text     = $_POST['text'] ?? '';
    
    if (empty($toUserId) || empty($text)) {
        http_response_code(400);
        exit('Missing parameters');
    }
    
    $payload = [
        'to' => $toUserId,
        'messages' => [
            [
                'type' => 'text',
                'text' => $text
            ]
        ]
    ];
    
    $url = "https://api.line.me/v2/bot/message/push";
    $ch = curl_init($url);
    
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $accessToken
    ]);
    
    $result = curl_exec($ch);
    if (curl_errno($ch)) {
        error_log('cURL error: ' . curl_error($ch));
    }
    curl_close($ch);
    
    http_response_code(200);
    echo $result;
    ?>


    サーバーのログを眺めます。こういうの↓

    ログの中身画像は省略しますが、”userId”:が確認出来るはずです。””の中のUから始まる30桁ぐらいのその文字列が要ります。

    これが公式アカウントの友だちに送信したい時に使える固有のユーザーIDで、TaskerからHTTPリクエストを送信する時に指定するものになります。

    上の方で書いた例だと「HTTPリクエスト送信」タスクの中身に使うものですね。


    ここまでで一通り必要なものは揃ってると思いますが、だいぶ雑な認識はあります。

    気が向いたら手入れします。



    詰まったところ

    →SwitchBotアプリへハブミニを追加する際のWi-Fiパスワードを間違えてた(?)時、そのハブミニがハブ扱いにならず、開閉センサーといつまでも連携させられませんでした。

    そのWi-Fiパスワードは後から修正したのですが、結局、一からやり直すまでハブ扱いになりませんでした。


    →XiaomiのOSの仕様(?)でTaskerの動作が意図せず止まったので、Taskerに権限をたくさん渡しました。

    たぶんバッテリーを保たせる為のバックグラウンド絡みの仕組みが要因ですが、バッテリーセーバーを制限なしに設定するとかだけでは変わらず、さらにガチャガチャした結果効いたかもしれない設定変更は

    ・バックグラウンドでの自動起動を許可
    ・アプリがTaskerにユーザー指定のタスクと自身のタスクを実行させることを許可
    ・バックグラウンドで実行中に新しいウィンドウを開く

    この辺でした。

    この辺りはTaskerが悪意を持ち始めたら何らかの被害を被りそうなので自己責任で。


    →TaskerでSwitchBotの通知を認識するプロファイルですが、タイトルや文字に「開閉センサー」「ドアが開いた」などのワードを入力し、部分一致した場合にトリガーを発動するものだと思っていましたが、この部分一致想定が間違っているのか、結局SwitchBotの通知全てをトリガーにする形でないと上手くいきませんでした。


    →Taskerを無効にしてまた有効にした場合、一度プロファイルのどれかをオフ/オンにしたりして出てくるチェックマーク(保存みたいなもの)を押さないとプロファイルの稼働が再開しないことがあるようです。


    →「タイマーチェック」のタスクは「機器本体を起動した」をトリガーにしているため、起動中にTaskerを無効にした場合、手動でタスクを開始する(左下の▶を押す)必要があります。