ドアモニターがないだと？代わりを作ればいいじゃない。

#我が家はポンコツ設計
我が家はショボ賃貸なので、ドアモニターがなくドアスコープでしが外の確認ができないのですが、設計がポンコツでドア前に人が立てず、ドアスコープから人の存在を確認するのがとても難しい…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122228/3110bb6b-3c6f-cecf-03ce-33a0a5bea2a6.png)
ついでに、インターホンが鳴ってドアを開けたのに人がいないという、ピンポンダッシュ（4階）が発生したり、胡散臭い勧誘が来たりするため、ドアを開けずに人がいるのか確認し、あわよくばドアを開けずにお帰りいただけけるツールが欲しい！
防犯のためにもむやみにドアを開けたくないし！！

#それなら作ってしまえ

https://twitter.com/momoiku3/status/1453233682022547464

![Screenshot_20211027-150706.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122228/1f1b65b7-8856-c814-a33c-2c3fbd38ff06.png)
LINE Botで部屋にいながら、人の存在を確認し、早く帰ってもらえるよう「蛍の光」を流しながら赤色LEDを点滅させて警告するというツールを作成しました！

#作り方
##使用環境・部品
・Node.js
・JavaScript
・LINE　Messaging API
・obniz
・スピーカー
・超音波測距センサ
・LED
![DSC_0103.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122228/2e8b9136-7676-21e2-7e9e-2494393fc587.jpeg)

##組み立て
部品を画像のようにセット！
丁度いいサイズのお菓子の空き箱があったので、セットした部品を箱にIN！
それにしても大きい:sweat_smile:
![DSC_0104.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122228/8fd614d5-bc5b-d702-8f30-77130b78ac04.jpeg)
![DSC_0105.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2122228/1702aea7-4196-e6e7-35ce-003b8b513013.jpeg)


##プログラム

<details><summary>プログラム全文</summary><div>

```javascript
// ########################################
//          obniz処理部分
//  Obniz_ID：自分のobniz ID（XXXX-XXXX）
// ########################################
const Obniz = require('obniz');
const obniz = new Obniz('XXXX-XXXX');
const { exit } = require('process');


// obnizと接続確立したとき
obniz.onconnect = async () => {
    obniz.display.clear();
    obniz.display.print('obniz Ready');
}
// 位置を取得し、指定した距離以内に人がいないか
const getObnizDistance = async () => {

    let LINEmsg = ""  //LINEへの返答


    const hcsr04 = obniz.wired('HC-SR04', {
        gnd: 3,
        echo: 2,
        trigger: 1,
        vcc: 0,
    });

        // obnizディスプレイ（初期表示）
        obniz.display.clear();
        obniz.display.print('obniz Ready');
    
        // setIntervalで定期実行
        //setInterval(async () => {
            // 距離を取得
            let distance = await hcsr04.measureWait();
            // 小数点以下がたくさんあるのでここでは整数にします
            distance = Math.floor(distance);
            // 距離をコンソールに表示
            console.log(distance + ' mm');
            // 距離をobnizディスプレイに表示
            obniz.display.clear();
            obniz.display.print(distance + ' mm');
            // 距離によって判定
            if (distance > 100.0) {
                // 50mm = 5cm 指定距離より遠い場合はいないと返答
                obniz.display.clear();
                obniz.display.print('ORANU');
                LINEmsg='いないようなのだ'
                return LINEmsg;

            } else {
                obniz.display.clear();
                obniz.display.print('ORUDE');
                LINEmsg='いるのだ！\nドアからチラ見するのだ\n追い払う時は「お帰り」と言うのだ '
                return LINEmsg;

            }

}

//LED光らせる
function led(){
    const rLED = obniz.wired("LED", {anode:11, cathode:10});

    rLED.blink(); // 100msec
    
    setTimeout(() => {
        rLED.off();
    }, 28000);

}

//蛍の光
const music2 = async () => {

    const sleep = (msec) => new Promise(res => setTimeout(res, msec));
    const speaker = obniz.wired('Speaker', { signal: 5, gnd: 6 });
    //音符設定
    const sp1 = 3200                    //全音符・全休符
    const sp2 = sp1 / 2                 //2分音符・2分休符
    const sp4 = sp1 / 4                 //4分音符・4分休符
    const sp8 = sp1 / 8                 //8分音符・8分休符
    const sp16 = sp1 / 16               //16分音符・16分休符
    const sp2_34 = sp2 + sp4            //付点2分音符・付点2分休符  
    const sp4_34 = sp4 + sp8            //付点4分音符・付点4分休符
    const sp8_34 = sp8 + sp16           //付点8分音符・付点8分休符
    const sp16_34 = sp16 + (sp16 / 2)   //付点16分音符・付点16分休符

    //音色設定
    const c = 261.626 //ド
    const d = 293.665 //レ
    const e = 329.628 //ミ
    const f = 349.228 //ファ
    const g = 391.995 //ソ
    const a = 440.000 //ラ
    const b = 466.164 //シ♭
    const c1 = 523.251 //ド
    const d1 = 587.330 //レ
    const e1 = 659.255 //ミ
    const f1 = 698.456 //ファ

    speaker.play(c); await sleep(sp8); speaker.stop(); await sleep(0); //ド
    speaker.play(f); await sleep(sp8_34); speaker.stop(); await sleep(0); //ファ
    speaker.play(f); await sleep(sp16); speaker.stop(); await sleep(0); //ファ
    speaker.play(f); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(g); await sleep(sp8_34); speaker.stop(); await sleep(0); //ソ
    speaker.play(f); await sleep(sp16); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8); speaker.stop(); await sleep(0); //ソ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(f); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(f); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(c1); await sleep(sp8); speaker.stop(); await sleep(0); //ド
    speaker.play(d1); await sleep(sp4_34); speaker.stop(); await sleep(sp16); //レ
    speaker.play(f1); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(c1); await sleep(sp8_34); speaker.stop(); await sleep(0); //ド
    speaker.play(a); await sleep(sp16); speaker.stop(); await sleep(0); //ラ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(f); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8_34); speaker.stop(); await sleep(0); //ソ
    speaker.play(f); await sleep(sp16); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8); speaker.stop(); await sleep(0); //ソ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(f); await sleep(sp8_34); speaker.stop(); await sleep(0); //ファ
    speaker.play(d); await sleep(sp16); speaker.stop(); await sleep(0); //レ
    speaker.play(d); await sleep(sp8); speaker.stop(); await sleep(0); //レ
    speaker.play(c); await sleep(sp8); speaker.stop(); await sleep(0); //ド
    speaker.play(f); await sleep(sp4_34); speaker.stop(); await sleep(sp16); //ファ
    speaker.play(d1); await sleep(sp8); speaker.stop(); await sleep(0); //レ
    speaker.play(c1); await sleep(sp8_34); speaker.stop(); await sleep(0); //ド
    speaker.play(a); await sleep(sp16); speaker.stop(); await sleep(0); //ラ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(f); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8_34); speaker.stop(); await sleep(0); //ソ
    speaker.play(f); await sleep(sp16); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8); speaker.stop(); await sleep(0); //ソ
    speaker.play(d1); await sleep(sp8); speaker.stop(); await sleep(0); //レ
    speaker.play(c1); await sleep(sp8_34); speaker.stop(); await sleep(0); //ド
    speaker.play(a); await sleep(sp16); speaker.stop(); await sleep(0); //ラ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(c1); await sleep(sp8); speaker.stop(); await sleep(0); //ド
    speaker.play(d1); await sleep(sp4_34); speaker.stop(); await sleep(sp16); //レ
    speaker.play(f1); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(c1); await sleep(sp8_34); speaker.stop(); await sleep(0); //ド
    speaker.play(a); await sleep(sp16); speaker.stop(); await sleep(0); //ラ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(f); await sleep(sp8); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8_34); speaker.stop(); await sleep(0); //ソ
    speaker.play(f); await sleep(sp16); speaker.stop(); await sleep(0); //ファ
    speaker.play(g); await sleep(sp8); speaker.stop(); await sleep(0); //ソ
    speaker.play(a); await sleep(sp8); speaker.stop(); await sleep(0); //ラ
    speaker.play(f); await sleep(sp8_34); speaker.stop(); await sleep(0); //ファ
    speaker.play(d); await sleep(sp16); speaker.stop(); await sleep(0); //レ
    speaker.play(d); await sleep(sp8); speaker.stop(); await sleep(0); //レ
    speaker.play(c); await sleep(sp8); speaker.stop(); await sleep(0); //ド
    speaker.play(f); await sleep(sp4_34); speaker.stop(); //ファ

}


// ########################################
//          LINEBot イベント処理部分
//  channelSecret：LINE Developers → チャネル基本設定 → チャネルシークレット
//  channelAccessToken：LINE Developers → Messaging API設定 → チャネルアクセストークン（長期）
//  ターミナルで `ngrok http 3000` 実行後、発行されたURLをWebhook URLとして設定するのを忘れずに
//  「検証」ボタンをクリックするとターミナルにエラーが出ますがここでは問題ありません（検証イベントのハンドリングをしていないため）
// ########################################
const config = {
    //マル秘：チャネルシークレットを記載
    channelSecret: '自分のチャネルシークレットを記載',
    //マル秘：アクセストークンを記載
    channelAccessToken: '自分のアクセストークンを記載'
};
const line = require('@line/bot-sdk');
const client = new line.Client(config);
// ExpressからMessaging APIイベントを渡されて処理するところ
const handleEvent = async (event) => {
    // テキストメッセージ以外を受信したときは何も行わずresolveを返す
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // テキストメッセージを受信したとき
    if (event.message.text === 'いるか') {
        // 待ってねというメッセージを「リプライ」で先に返す
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '.oO（確認してくるのだ）'
        });
        // obnizの距離センサから値をとってくる（ブロッキング・時間のかかる処理で一旦ここで止まる）
        const LINEmsg = await getObnizDistance();
        console.log(LINEmsg);
        // tempが取得できたらそれを含めたメッセージを「プッシュ」で送信する
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: LINEmsg,
        });
    } else if(event.message.text === 'お帰り'){
        led();
        music2();

        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '早く帰ってもらうのだ！',
        });


    } else {
        // メッセージの中身が「いるか」と「お帰り」以外だったとき
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: '人がいるか確認するには「いるか」と話すのだ。\n追い払う時は「お帰り」と話しかけるのだ。'
        });
    }
    // resolveを返す
    return Promise.resolve(null);
}

// ########################################
//          Expressサーバー部分
// ########################################
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
// 「(サーバーURL)/webhook」にアクセス（LINEサーバーからのWebhook）があったとき
app.post('/webhook', line.middleware(config), (req, res) => {
    // 受信したイベントをターミナルに表示
    console.log(req.body.events);
    // イベントをhandleEventに渡して1つずつ処理
    Promise.all(
        req.body.events.map(handleEvent)
    ).then(
        result => res.json(result)
    );
});
// PORT番号のポートでサーバーを開始
app.listen(PORT);
console.log('express runnning: PORT =', PORT);

```

</div></details>

###近くにいるか確認したい
超音波測距センサで指定した距離の中に人がいるかをチェックする仕様にしました。

```javascript
// 位置を取得し、指定した距離以内に人がいないか
const getObnizDistance = async () => {

    let LINEmsg = ""  //LINEへの返答


    const hcsr04 = obniz.wired('HC-SR04', {
        gnd: 3,
        echo: 2,
        trigger: 1,
        vcc: 0,
    });

        // obnizディスプレイ（初期表示）
        obniz.display.clear();
        obniz.display.print('obniz Ready');
    
        // setIntervalで定期実行
        //setInterval(async () => {
            // 距離を取得
            let distance = await hcsr04.measureWait();
            // 小数点以下がたくさんあるのでここでは整数にします
            distance = Math.floor(distance);
            // 距離をコンソールに表示
            console.log(distance + ' mm');
            // 距離をobnizディスプレイに表示
            obniz.display.clear();
            obniz.display.print(distance + ' mm');
            // 距離によって判定
            if (distance > 100.0) {
                // 50mm = 5cm 指定距離より遠い場合はいないと返答
                obniz.display.clear();
                obniz.display.print('ORANU');
                LINEmsg='いないようなのだ'
                return LINEmsg;

            } else {
                obniz.display.clear();
                obniz.display.print('ORUDE');
                LINEmsg='いるのだ！\nドアからチラ見するのだ\n追い払う時は「お帰り」と言うのだ '
                return LINEmsg;

            }

}
```

###光で警告したい
警告といえば赤色LEDだろう、という事で、LEDを音楽が鳴っている間点滅している設定にしました。
音楽が鳴っている間は点滅していて欲しいので、「setTimeout」を使って、消灯の処理実行までの時間を設定しました。

```javascript
//LED光らせる
function led(){
    const rLED = obniz.wired("LED", {anode:11, cathode:10});

    rLED.blink(); // 100msec
    
    setTimeout(() => {
        rLED.off();
    }, 28000);

}
```

###帰りたくなる音楽を流したい
閉店時間の音楽といえば、「蛍の光」でしょう！
警告の点滅と一緒に蛍の光を流して、帰りたくなる気分を演出しましょう。
音を鳴らす部分は超大作なので、一音目のみの記載です。

音を連続で流すのは、単純に「speaker.play(); speaker.stop()」を連続で記述しても、うまく音が鳴らなず…。
こちらの[参考記事](https://qiita.com/dsvvpxgcseldie/items/009642d189a289228783)をもとに作成しました！
音色の設定は[こちら](https://tomari.org/main/java/oto.html)を参考にしています。


```javascript
//蛍の光
const music2 = async () => {

    const sleep = (msec) => new Promise(res => setTimeout(res, msec));
    const speaker = obniz.wired('Speaker', { signal: 5, gnd: 6 });
    //音符設定
    const sp1 = 3200                    //全音符・全休符
    const sp2 = sp1 / 2                 //2分音符・2分休符
    const sp4 = sp1 / 4                 //4分音符・4分休符
    const sp8 = sp1 / 8                 //8分音符・8分休符
    const sp16 = sp1 / 16               //16分音符・16分休符
    const sp2_34 = sp2 + sp4            //付点2分音符・付点2分休符  
    const sp4_34 = sp4 + sp8            //付点4分音符・付点4分休符
    const sp8_34 = sp8 + sp16           //付点8分音符・付点8分休符
    const sp16_34 = sp16 + (sp16 / 2)   //付点16分音符・付点16分休符

    //音色設定
    const c = 261.626 //ド
    const d = 293.665 //レ
    const e = 329.628 //ミ
    const f = 349.228 //ファ
    const g = 391.995 //ソ
    const a = 440.000 //ラ
    const b = 466.164 //シ♭
    const c1 = 523.251 //ド
    const d1 = 587.330 //レ
    const e1 = 659.255 //ミ
    const f1 = 698.456 //ファ

    speaker.play(c); await sleep(sp8); speaker.stop(); await sleep(0); //ド
```

###スマホ片手で操作したい
ピンポンダッシュに対して玄関まで無駄に移動したくないので、座りながら人の有無が確認できるよう、LINE Botを使って指示を出すようにしました。
人が「いるか」確認するため、「いるかbot」です。
イメージは昔のoffice製品にいたイルカちゃんです:dolphin:

LINEでの指示では、お帰りいただく際の動作順が「スピーカー（music2()）」→「LED（led()）」だと、うまく動かない事があったので、「LED（led()）」→「スピーカー（music2()）」の順番としました。
なぜうまく動かなかったのかは分からなかったです…。


```javascript

const config = {
    //マル秘：チャネルシークレットを記載
    channelSecret: '自分のチャネルシークレットを記載',
    //マル秘：アクセストークンを記載
    channelAccessToken: '自分のアクセストークンを記載'
};
const line = require('@line/bot-sdk');
const client = new line.Client(config);
// ExpressからMessaging APIイベントを渡されて処理するところ
const handleEvent = async (event) => {
    // テキストメッセージ以外を受信したときは何も行わずresolveを返す
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // テキストメッセージを受信したとき
    if (event.message.text === 'いるか') {
        // 待ってねというメッセージを「リプライ」で先に返す
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '.oO（確認してくるのだ）'
        });
        // obnizの距離センサから値をとってくる（ブロッキング・時間のかかる処理で一旦ここで止まる）
        const LINEmsg = await getObnizDistance();
        console.log(LINEmsg);
        // tempが取得できたらそれを含めたメッセージを「プッシュ」で送信する
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: LINEmsg,
        });
    } else if(event.message.text === 'お帰り'){
        led();
        music2();

        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '早く帰ってもらうのだ！',
        });


    } else {
        // メッセージの中身が「いるか」と「お帰り」以外だったとき
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: '人がいるか確認するには「いるか」と話すのだ。\n追い払う時は「お帰り」と話しかけるのだ。'
        });
    }
    // resolveを返す
    return Promise.resolve(null);
}
```

#最後に
ドアモニター代わりとして作成してみましたが、センサー類が大きいため、我が家の玄関付近には置けなさそうでした…。
もう少し小型の機器になったら、インターホン付近に貼り付けたいな、と思います。
今のサイズ感でしたら、たまにベランダに来る鳥の鳥避けに活用できそうです。