Experiment record

SwitchBot CO₂をGoogle Apps Scriptで判定し家族LINEグループへ換気通知する

約5分周期のSwitchBot CO₂収集系へ、継続判定・ヒステリシス・LINEグループPush通知を追加した。意図的にCO₂を上昇させた実動試験では2812 ppmで家族グループへの警告を確認した。30分再通知と無通知解除は未確認である。

結論

既存のSwitchBot住宅環境収集系に、リビングのCO₂濃度を判定して非公開の家族LINEグループへ換気を促す自動通知経路を追加した。

本番のデータ経路は次のとおりである。

SwitchBot MeterPro(CO2)
SwitchBot OpenAPI v1.1
Google Apps Script
約5分周期の測定トリガー
非公開Google Sheets測定ログ
CO₂警報状態判定
LINE Messaging API Push
非公開の家族グループ

初回警報については、エンドツーエンドの実動確認ができた。 機能試験として人間がCO₂センサー付近で呼気を与え、測定値を意図的に上昇させた。2026年8月16日16:38 JSTに、対象の家族LINEグループへ換気警告が届き、現在値2812 ppm高CO₂継続4分と表示された。

一方、30分後の再警告と、900 ppm未満が10分継続した後の無通知解除は、まだ実動確認していない。この2項目は実装目標として記録し、検証済みとは扱わない。

1. 目的

目的は新しいダッシュボードを作ることではなく、家族が実際に換気行動を取る必要があるときだけ通知することである。

既存の住宅環境収集系ではCO₂を約5分周期で取得している。そのため別の監視トリガーを増やさず、同じ測定経路に通知判定を接続する方針とした。これにより、LINE通知の根拠となる値と非公開Google Sheetsへ保存される測定系列を同じ観測経路にそろえられる。

2. 警報ロジック

設計では、単純な閾値判定ではなく継続判定とヒステリシスを組み合わせる。

NORMAL
  │ 1000 ppm超を2回連続で測定
ALERT
  ├─ 再び1000 ppm超が必要時間連続し、
  │  前回通知から30分以上
  │       → 再警告
  ├─ 900~1000 ppm
  │       → 通知なし、ALERT維持
  └─ 900 ppm未満が10分以上
          → 無通知でNORMALへ復帰

1000 ppmで警報を開始し、900 ppm未満で解除する幅を持たせることで、1000 ppm近傍を上下するだけで警報状態が何度も切り替わることを避ける。

CO₂値の欠測やSwitchBot APIエラーが発生した場合、その欠測区間を「高い状態が継続した」「低い状態が継続した」とみなさない設計とする。

3. LINE通知の方針

初回通知と再通知では文面を変える。

初回通知では、リビングのCO₂濃度が高いこと、現在値、継続時間を示し、換気を依頼する。再通知では、30分以上の高CO₂状態が続いていることを明示し、現在値と継続時間を再度表示する設計とした。

解除時のLINE通知は送らない。内部状態だけをNORMALへ戻す。これにより、家族グループを測定ログのような通知で埋めないことを優先する。

4. 家族グループを送信先にするまで

LINE Messaging APIのPush送信では、公式アカウントが参加しているグループのgroupIdを送信先として利用できる。

今回、groupIdはWebhookイベントから一度だけ取得し、その後Google Apps ScriptのScript Propertiesへ非公開で保存した。チャネルアクセストークンもScript Propertiesで管理する。

一度groupIdを取得した後、通常のCO₂通知はPush送信だけで成立するため、Webhookを常時有効にしておく必要はない。

公開しない情報は次のとおりである。

5. 失敗:GAS Web AppをWebhookに直接使用

最初はGoogle Apps Script Web AppをLINEの一時Webhook受信先として使用しようとした。

設定途中ではLINEのWebhook検証でHTTP 404となった。その後、URLとデプロイを修正した状態では、検証リクエストに対してHTTP 302が返ることを確認した。

今回のgroupId取得用途では、この経路を継続利用せず、別の受信先へ切り替えた。最終的に使わなかった方法も、同じ失敗を将来繰り返さないため実験データとして残す。

6. 回避策:一時的なCloudflare Worker

groupId取得専用として、小さなCloudflare Workerを一時的に作成した。

役割は次の4点だけである。

  1. LINEのWebhook POSTを受信する。
  2. x-line-signatureをチャネルシークレットによるHMAC-SHA256で検証する。
  3. 正常なグループイベントのgroupIdを管理下のログで一度だけ確認する。
  4. LINEへHTTP 200を直接返す。

この受信先ではLINE DevelopersのWebhook検証に成功した。その後、非公開の家族グループでメッセージを送り、groupIdを取得し、Google Apps ScriptのScript Propertiesへ保存した。

目的達成後、Cloudflare Workerは削除し、LINE DevelopersのWebhook利用もOFFにした。したがって、現在のCO₂通知経路はCloudflareへ依存していない。

7. 実動試験

EXECUTED

人間が機能試験として、設置済みCO₂センサー付近で呼気を与え、測定濃度を短時間だけ意図的に高くした。

OBSERVED

2026年8月16日16:38 JSTに、意図した家族LINEグループへ換気通知が届いた。

通知には次が表示されていた。

実際のスクリーンショットには家族グループ情報が含まれるため、公開記事には掲載せず非公開証拠として保持する。

VERIFIED

この観測によって、少なくとも初回警報について次の経路が動作することを確認した。

CO₂測定
→ 自動閾値・状態判定
→ LINE Messaging API Push
→ 意図した非公開グループ

ただし、状態機械の全分岐を検証したことにはならない。

8. 未検証項目

今後の確認項目は次のとおりである。

これらの観測が得られるまでは、実装目標であって完全な実動検証済み事項ではない。

9. プライバシーと公開境界

本実験は私的住宅と家族向け通信グループを扱う。そのため、生の約5分時系列データ、住宅を特定し得る情報、個人名を含む部屋名、deviceId、Spreadsheet ID、LINE識別子、認証情報、アカウント情報、Webhook URL、家族グループのスクリーンショットは公開しない。

公開するのは、再利用可能なアーキテクチャ、警報ロジック、トラブルシューティング経路、匿名化した実動観測だけである。

10. 再現手順の要点

同等のシステムは、今回の非公開値を一切共有しなくても再現できる。

  1. 所有するCO₂センサーから安定した周期で値を取得する。
  2. 通知判定より先に元の観測値を保存する。
  3. 測定値と通知状態を分離して管理する。
  4. 初回通知の前に継続条件を要求する。
  5. 警報開始値より低い解除値を設定し、ヒステリシスを持たせる。
  6. 再通知はトリガー実行回数ではなく経過時間で制御する。
  7. LINE認証情報と送信先IDはソースコードやスプレッドシートではなく秘密情報ストアへ保存する。
  8. 初回警報、再警告、解除、欠測、解除後再発報を別々に試験する。

関連実験

基礎となる約5分周期の住宅環境収集系は SWITCHBOT-HOME-ENV-001 として別途記録している。