pagerduty
SORACOM
WioLTE
SORACOMDay 14

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


はじめに

突然ですが、最近Off-Gridをはじめました笑

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

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

※本記事は、アイデアのコンセプト実証の為にやっております。一発芸に近いような感じです。

※技適マークを貼付された感知器は総務大臣の許可なしに改造して使用することはできません!!

※At your own risk!! 決して本番で使っている報知器を改造しないようにお願いいたします!!

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


今回作ったもの


全体像

今回、上記の様なシステムを組んでみました。

IoT化した煙感知器からクラウド上のAPIサーバーにイベント送信を行い、そこからSlack・PagerDutyを通して

チームメイト(?)と自分の携帯に電話で通知するようにしました。ここからはメイキングのお話ができればと思います。

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


材料・必要物

DrKRMc7VYAArUIb.jpg


やったこと


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

煙感知器をIoT化するにあたって、どのようにWioLTEと本体を接続して、どのように煙感知イベントを本体から取り出すのか悩みました。

本体にシリアルポート等の外部機器用I/Fがあればよいのですが...仕様書も何もないのでわからず。

今回はシンプルに、煙感知した際に点滅するLEDの信号をWioLTEのDigitalPortにつなぐ事にしました。

早い話、LEDの点灯状態を見てしまえという感じです。

DrKRLNlU4AAfZAH-2.jpg

LEDがついていない時に駆動用ポートがHigh、ついている時に駆動用ポートがLowLevelとなっていた為、

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

dio2_2.gif

出展:第8回 ハードウェア入門2.DIOの基礎 / Silicon Linux様

DrKRLz-U0AEyIDj.jpg

DraVxxNUwAEpc-b.jpg


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

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

スクリーンショット_2018-12-14_20_57_36.png


APIサーバー作成

ダイブ雑ですが、Flaskを使ってAPIを作っています笑

火災発生イベントの通知用に、/fire_alarmというエンドポイントを作っています。

このエンドポイントがPOSTで叩かれたらSlack・PagerDutyの両方へイベントを流しています。

※余裕ができたら、off-grid側の制御をして電源供給OFFにするロジックを書こうと思いますmm


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


#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++; }
// 3回以上の点滅で、アラーム状態が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);
}


テスト

今回、システムのテストには「蚊取り線香」を使いました。タバコを吸わない自分が手軽に手に入れられる煙源...笑

蚊取り線香の隣において煙を誘導した所、1〜2分で反応しました。 そして、煙感知器LEDが4回以上点滅したタイミングでイベント送信。

無事にSlackと電話連絡が来ました。 (しかも国際電話)


Slack


PagerDuty


まとめ

今回は、OffGridが有事の際に通知ができるように色々試してみました。

今後の展望として、APIの冗長化やイベント検知時に自動電源遮断等、いろいろ試していければと思います。

ありがとうございました!