煙感知器をIoT化して有事の際に通知が来るようにする


# はじめに

突然ですが、最近Off-Gridをはじめました笑
Amazonでソーラーパネル・自動車用バッテリー・チャージコントローラー・インバーターを購入し、ただつなぎ合わせているだけなのですが・・・日中PCを数時間動かすぐらいの電力を発電してくれるので、仕事から帰ってきたら商用電源に頼らない生活を楽しんでおりますmm (まだ、投資したコストをちゃんと回収できていません・・・笑)

内部インピーダンスが低くてめちゃくちゃ電流が流れまくる自動車用バッテリーを、趣味（？）で使うのが初めて＆超スーパーチキンなので、IoT化した煙感知器を近くに設置し、有事の際に連絡＆自動で電源遮断等ができないか？と思い始めたのが今回の謎工作のきっかけです。

※本記事は、アイデアのコンセプト実証の為にやっております。一発芸に近いような感じです。
※技適マークを貼付された感知器は総務大臣の許可なしに改造して使用することはできません!!
※At your own risk!! 決して本番で使っている報知器を改造しないようにお願いいたします!!

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/9a7bc78d-7289-5171-0f54-2f4dbf4278bc.jpeg" width=100%>

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/bd8f9486-fe86-fd34-c5c8-529a0a002352.jpeg" width=100%>

WioLTEを使った前記事：https://qiita.com/xecus/items/b7350111024b5fc2d9ae

# 今回作ったもの

## 全体像

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/5737cdf8-8266-c130-83a1-b93f259261c0.png" width=100%>

今回、上記の様なシステムを組んでみました。
IoT化した煙感知器からクラウド上のAPIサーバーにイベント送信を行い、そこからSlack・PagerDutyを通して
チームメイト（？）と自分の携帯に電話で通知するようにしました。ここからはメイキングのお話ができればと思います。

(追記) そうえいば、PagerDutyからSlackに直接投げられるのを失念しており、WioLTEから普通にpagerDutyのAPI叩いても良かったなと作った後に気づきました...笑

## 材料・必要物

![DrKRMc7VYAArUIb.jpg](https://qiita-image-store.s3.amazonaws.com/0/128123/a59b23e7-de01-faa1-c018-48038eefea5e.jpeg)

- 煙感知器(煙式住宅用火災警報器) [SHK6030P / Panasonic](https://panasonic.jp/fire-alarm/p-db/SHK6030P.html)
  - ホームセンターで3000円くらいで買ってきました

- 通信モジュール [Wio LTE JP Version](https://soracom.jp/products/module/wio_lte/)
  - [秋月電子]でも売っているようです(http://akizukidenshi.com/catalog/g/gM-12855/)
  - [Arduino IDE用ライブラリ](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Home-ja.html)

- [SORACOM Air SIMカード](https://www.amazon.co.jp/SORACOM-Air-SIM%E3%82%AB%E3%83%BC%E3%83%89%EF%BC%88%E3%83%87%E3%83%BC%E3%82%BF%E9%80%9A%E4%BF%A1%E3%81%AE%E3%81%BF%EF%BC%89-%E3%83%8A%E3%83%8E/dp/B015FFCZ02)

- 適当な配線 15cm~30cmくらい
    - 煙感知器の内部回路とWioLTEを接続する為に使う
- はんだごて
  - 煙感知器の内部回路とWioLTEを接続する為に使う
- ドライバー
  - 煙感知器を分解する用



# やったこと

## 煙感知器を分解してWioLTEと接続する

煙感知器をIoT化するにあたって、どのようにWioLTEと本体を接続して、どのように煙感知イベントを本体から取り出すのか悩みました。
本体にシリアルポート等の外部機器用I/Fがあればよいのですが...仕様書も何もないのでわからず。

今回はシンプルに、煙感知した際に点滅するLEDの信号をWioLTEのDigitalPortにつなぐ事にしました。
早い話、LEDの点灯状態を見てしまえという感じです。

![DrKRLNlU4AAfZAH-2.jpg](https://qiita-image-store.s3.amazonaws.com/0/128123/9f7dbd52-5d90-d8e0-16cd-4c6d764f0729.jpeg)


LEDがついていない時に駆動用ポートがHigh、ついている時に駆動用ポートがLowLevelとなっていた為、
下記のような回路になっている事が推測されます。いわゆる負論になっているので、次ステップの点滅パターン検知の時に注意が必要でした。

![dio2_2.gif](https://qiita-image-store.s3.amazonaws.com/0/128123/aa013962-685b-5f6f-f075-1a9ccba24cd6.gif)


出展：[第８回 ハードウェア入門２．ＤＩＯの基礎 / Silicon Linux様](http://www.si-linux.co.jp/catwiki/index.php?%E7%AC%AC%EF%BC%98%E5%9B%9E%20%E3%83%8F%E3%83%BC%E3%83%89%E3%82%A6%E3%82%A7%E3%82%A2%E5%85%A5%E9%96%80%EF%BC%92%EF%BC%8E%EF%BC%A4%EF%BC%A9%EF%BC%AF%E3%81%AE%E5%9F%BA%E7%A4%8E)

![DrKRLz-U0AEyIDj.jpg](https://qiita-image-store.s3.amazonaws.com/0/128123/9b8f3f2f-6a68-edd0-f3f1-b9d759049424.jpeg)

![DraVxxNUwAEpc-b.jpg](https://qiita-image-store.s3.amazonaws.com/0/128123/8198e211-c591-906f-72b7-5bb0b95fef6e.jpeg)



## Slack, PDの設定

### Slackの設定

下記を参考にIncoming Webhook URLを取得しました。

https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008

### Pagerdutyの設定

※ユーザー登録及びユーザーの連絡先携帯電話番号の設定は終わっているモノとします

今回、Python製APIサーバーからPagerDutyのAPIを叩く事で、携帯電話へ電話させます。
Integration Typeを`Use our API Directly`に変更する必要があります。

![スクリーンショット_2018-12-14_20_36_34.png](https://qiita-image-store.s3.amazonaws.com/0/128123/e9239d46-f056-9ca5-db03-c35f789fef44.png)

ここで発行されるIntegration Keyは、次のステップで作成するAPIに組込みます。

![スクリーンショット_2018-12-14_20_57_36.png](https://qiita-image-store.s3.amazonaws.com/0/128123/83aa0cd1-2dfb-2eff-62eb-c53b84447fcc.png)


## APIサーバー作成

ダイブ雑ですが、Flaskを使ってAPIを作っています笑
火災発生イベントの通知用に、`/fire_alarm`というエンドポイントを作っています。
このエンドポイントがPOSTで叩かれたらSlack・PagerDutyの両方へイベントを流しています。
※余裕ができたら、off-grid側の制御をして電源供給OFFにするロジックを書こうと思いますmm

```python

import json
import requests
import pypd
from flask import Flask, request
app = Flask(__name__)
SLACK_URL = "https://hooks.slack.com/services/xxxxx/xxxxx/xxxxx"
PAGERDUTY_INTEGRATION_KEY = 'XXXXXXXXXXXXXXXXXXXXX'

def post_slack():
    payload={
      "channel": "#target_channel",
      "username": "Fire Detector",
      "text": " <@XXXXXXXXX> おまえんち燃えてるで.",
      "icon_emoji": ":fire:"
    }
    data = json.dumps(payload)
    requests.post(SLACK_URL, data)

def post_pagerduty():
    pypd.EventV2.create(data={
        'routing_key': PAGERDUTY_INTEGRATION_KEY,
        'event_action': 'trigger',
        'payload': {
            'summary': 'Your house is on fire!!!!',
            'severity': 'error',
            'source': 'test',
        }
    })

@app.route('/fire_alarm', methods=['POST'])
def fire_alarm():
    print(request)
    print(request.data)
    post_slack()
    post_pagerduty()
    return 'accepted'
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5003, debug=True)
```


## WioLTE上に検知ロジックを書いてAPI連携

この煙感知器には試験モードがあり、試験用ボタンを押すと「正常です」という合成音声と共にLEDが3回点滅します。
一方でガチな煙を検知した場合は、煙を感知している間、約90dbの警報音と共にLEDがずっと点滅します。
この点滅パターンを読み取る事で、煙感知イベントを検出する事を考えました。

下記は、LEDが短期間で4回以上点滅（＝煙感知時）した時に、APIにPOSTするコードです。
※LTEモジュールの初期化部分とかがあるため、少し長めになっておりますmm

```java

#include <stdio.h>
#include <WioLTEforArduino.h>
#include <memory>
#include <queue>
#define POINTS            (300)
#define APN               "soracom.io"
#define USERNAME          "sora"
#define PASSWORD          "sora"
#define WEBHOOK_URL       "http://xxxxx:5003/fire_alarm"
static char data[100];
WioLTE Wio;
void infity_loop() {
  // Do Nothing
  while (1) {}
}

void setup_pin() {
  pinMode(WIOLTE_D38, INPUT);
}

void setup_modem() {
  SerialUSB.println("### I/O Initialize.");
  Wio.Init();
  SerialUSB.println("### Power supply ON");
  Wio.PowerSupplyLTE(true);
  Wio.PowerSupplyGrove(true);
  delay(5000);
  SerialUSB.println("### Turn on or reset.");
  if (!Wio.TurnOnOrReset()) {
    SerialUSB.println("### Device Init ERROR! ###");
    Wio.LedSetRGB(255,0,0);
    infity_loop();
  }
  delay(5000);
  SerialUSB.println("### Connecting to \""APN"\".");
  if (!Wio.Activate(APN, USERNAME, PASSWORD)) {
    SerialUSB.println("### APN Init ERROR! ###");
    Wio.LedSetRGB(255,0,0);
    infity_loop();
  }
  delay(5000);
}

void sync_time() {
  SerialUSB.println("### Sync time.");
  if (!Wio.SyncTime("ntp.nict.jp")) {
    SerialUSB.println("### ERROR! ###");
  }
}

void setup() {
  delay(200);
  setup_pin();
  setup_modem();
  sync_time();  
}

// JSON作って頑張ってAPIにHTTP Postするところ
void sendAlert () {
  int status;
  int p=0;
  p += sprintf(data + p, "{\"message\": \"firing\"}");
  data[p] = 0;
  if (!Wio.HttpPost(WEBHOOK_URL, data, &status)) {
    SerialUSB.println("### Alert SendError! ###");
  } else {
    SerialUSB.println("### Alert Sent!!! ###");
  }
}


int old_sig_status = -1;      // 過去のステップにおける信号レベル (1=High 0=Low)
int curr_sig_status = -1;     // 現在の信号論理レベル (1=High 0=Low)
int cnt_for_sig_low = 0;      // Low Level 継続カウント
int cnt_for_sig_high = 0;     // High Level 継続カウント
int alarming = 0;             // 発報状態管理 (0=正常 1=発報中)

void loop() {
  // 初回実行時
  if (old_sig_status == -1){
    curr_sig_status = digitalRead(WIOLTE_D38);
    old_sig_status = curr_sig_status;
    delay(100);
    return;
  }
  // 現在のシグナル状態を取得する
  curr_sig_status = digitalRead(WIOLTE_D38);
  // 感知機のLEDが点灯していない
  if (curr_sig_status == 1) {
    cnt_for_sig_low++;
    // 立ち上がり検知後に無信号状態が続いて場合 (アラーム状態解除）
    if(cnt_for_sig_low > 100 && cnt_for_sig_high > 0) {
      alarming = 0;
      cnt_for_sig_high = 0;
    }
  } else {
    // 信号パルスのエッジ検出
    if (old_sig_status == 1) { cnt_for_sig_high++; }
    // ３回以上の点滅で、アラーム状態がfalseの場合
    if (cnt_for_sig_high > 3 && alarming == 0) {
      //API側にイベントを送る!!!
      sendAlert();
      alarming=1;
    }
    cnt_for_sig_low=0;
  }
  // ちなみに警報中はWioLTE上のLEDを点灯させる
  if (alarming) {
    Wio.LedSetRGB(255,0,0);
  } else {
    Wio.LedSetRGB(0,0,255);
  }
  old_sig_status = curr_sig_status;
  // 次回のチェックは100msec以降 
  delay(100);
}

```

## テスト

今回、システムのテストには「蚊取り線香」を使いました。タバコを吸わない自分が手軽に手に入れられる煙源...笑
蚊取り線香の隣において煙を誘導した所、１〜２分で反応しました。　そして、煙感知器LEDが４回以上点滅したタイミングでイベント送信。
無事にSlackと電話連絡が来ました。 (しかも国際電話)

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/545d2369-1b60-3ba9-703a-3f4c4da0c9ee.jpeg" width=100%>

### Slack

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/91f88c2e-405b-3eda-6a4e-aa43bb86fc1f.jpeg" width=100%>

### PagerDuty

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/85ca9342-2b5b-d506-7454-14aee08e863e.png" width=100%>

<img src="https://qiita-image-store.s3.amazonaws.com/0/128123/7d04d29c-d8e8-6780-08ba-1e481741f721.png" width=30%>


# まとめ

今回は、OffGridが有事の際に通知ができるように色々試してみました。
今後の展望として、APIの冗長化やイベント検知時に自動電源遮断等、いろいろ試していければと思います。
ありがとうございました！