ObnizとAWS LambdaとLINEを使って家のエアコンをスマート化した話

# きっかけ
僕の家は最寄駅から徒歩20分くらいありまして、その道を毎日歩いて通勤しています。春先や秋などは良いのですが、真夏日だと蒸し暑い中で家と会社を往復しています。
会社に行く時は電車に乗れさえすればエアコンが効いてて快適です！ただ帰りの場合は、家についても中は暑く、時には外よりも蒸し暑い時もあります。
どうせなら駅に着いた瞬間エアコンをつけて、駅から家まで歩いている間に涼しくなってくれればいいのに・・・

# 作ったもの
LINEから「冷房」などとメッセージを送信すると、家のエアコンの冷房スイッチをONしてくれるbotを作成しました。

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/8b5a576c-a018-be7c-9338-5026622d0820.gif)

LINEなので当然、ローカルネットワークではなくインターネット回線越しです。なので家の外でも会社でもどこでも、家のエアコンをつけたり消したりできます！
これ作ったのほんとは夏頃なのですが、冬用に暖房ON機能も追加することで一年中活躍してくれそうです。

LINEのトーク画面はこんな感じ。
![カルヴィ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/5b269eab-a4ee-8a89-37dc-2fe58c660783.png)
![カルヴィ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/b67eba2b-0057-d9dc-8656-5dc0a195e755.png)
![カルヴィ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/d56153dc-4303-ca1a-03b0-03d48814e00d.png)


# システム構成・技術解説
<img width="814" alt="構成図.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/bf0c45bf-3dd6-b2df-5277-80a8fe2aee1e.png">

システム構成はざっくりこんな感じです。
それぞれ詳細を書いていきます。

## LINE Developersでチャネル作成
LINEをBot化するにあたりLINE Developersにてチャネルを作成します。
<img width="510" alt="LINEDEV1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/e1ae467c-31a2-33a2-b3bf-8e60e0e3db14.png">

せっかくbotにするので、嫁の好きなソシャゲのキャラにアイコンを設定。
（チャネル作成の詳しい手順については割愛します。）
「Messaging API設定」というタブの「Webhook URL」に、エンドポイントとなるAPIを設定する必要があります。今回は、API Gateway & AWS Lambdaで作成します。
また、チャネルアクセストークンとチャネルシークレット(こっちはチャネル基本設定タブの中にあります)も後々必要になるので控えておきます。

## エアコン(リモコン)の赤外線パターンを取得
秋月などで販売されてる、赤外線受信モジュールを使用します。
http://akizukidenshi.com/catalog/g/gI-00622/
これをObnizに直接、こんな感じで接続します。
![infrared_rec.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/746b4f9a-6181-4378-8d4a-68822cf6588e.jpeg)

あとは赤外線モジュールに向けてリモコン操作することで、その操作の赤外線点灯パターンを取得します。そのためのプログラムを書く必要があるのですが、Obniz公式が赤外線受信モジュール取り扱いのためのページを公開してくれており、そのページの中で、なんとプログラム実行ができてしまいます！
https://obniz.io/ja/sdk/parts/IRSensor/README.md
まずブラウザでObnizにログインしているかどうかをご確認ください。
ログインしているならば、「start(callback(array))」という欄に自身のobnizIDが表示されているはずです。そのボックス内のプログラムを、「TEST RUN」ボタン押下で実行することができます。
<img width="856" alt="obniz_rec.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/4ac57182-fce1-6a65-24d4-373dfb771001.png">

「TEST RUN」実行後、エアコンのリモコンを赤外線受信モジュールに向けて操作することで、赤外線点灯パターンの配列が表示されます。冷房・暖房をつけるパターンと停止するパターン、それぞれの配列パターンを取得・控えておきましょう。

## ブレッドボードに基盤作成
まず下記のものを購入しましょう。

- 5mm 赤外線LED (http://akizukidenshi.com/catalog/g/gI-03261/)
- 光拡散用LEDキャップ (http://akizukidenshi.com/catalog/g/gI-01120/)
- 50Ω抵抗
- ジャンパワイヤx2本
- ブレッドボード

購入部品は全体的に下記記事を参考にさせていただきました。
https://qiita.com/KAKY/items/55e6c54fa2073cdc0bbe
上記部品たちを下記のように接続！
<img width="465" alt="基板.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/bda7376e-3326-5e6a-25b3-89b6bd04124b.png">
![IMG_1623.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/9db2bbd0-aeb0-4a03-88a2-0fd8b1348fd5.jpeg)


見えづらいかもですごめんなさい。
そこまで複雑な配線でもないですが、LEDの足が長い方(アノード:+)はObnizの0番(上記写真でいう赤いジャンパワイヤ)、足が短い方(カソード:-)はObnizの1番(上記写真でいう黒いジャンパワイヤ)に接続するように気をつけてください。
また当たり前ですが、その間に抵抗を挟んでおいてくださいね。(僕は開発中に一度抵抗を挟むのを失念し、LEDを一つ爆発させました。)
LEDキャップは、指向性のあるLEDの光を拡散させる役割で、これをつけておくとわざわざLEDをエアコンに向ける必要がなくなります。これも忘れずにつけておきましょう。

## Lambdaへのプログラムアップロード
上記のやり方でゲットした「冷房」「暖房」「停止」それぞれの赤外線パターン配列を控えて、Lambdaに記載するプログラムを書いていきます。
Lambda上でObniz APIをキックするために、またLINE Messaging APIを使用するために、それぞれNode.js用のSDKが必要になりますので、まずローカルPCにインストールしましょう。（僕はMacユーザーなので開発端末はMac想定でいきます。）
※ObnizのLambda連携は、下記公式のドキュメントもありますので、参考にしてみてください。
https://obniz.io/ja/lessons/server_side/lessons_lambda

```terminal
$ cd /path/to/the/work/dir/
$ npm install obniz
$ npm install @line/bot-sdk
```

そして同じ階層にindex.jsファイルを用意します。

```index.js
'use strict';
const line = require('@line/bot-sdk');
const crypto = require('crypto');
const client = new line.Client({channelAccessToken: process.env.ACCESSTOKEN});
const Obniz = require("obniz");
 
exports.handler = function (event, context) {
    var obniz = new Obniz("your-obnizID");
    let signature = crypto.createHmac('sha256', process.env.CHANNELSECRET).update(event.body).digest('base64');
    let checkHeader = (event.headers || {})['X-Line-Signature'];
    let body = JSON.parse(event.body);
    if (signature === checkHeader) {
        if (body.events[0].replyToken === '00000000000000000000000000000000') { //接続確認エラー回避
            let lambdaResponse = {
                statusCode: 200,
                headers: { "X-Line-Status" : "OK"},
                body: '{"result":"connect check"}'
            };
            context.succeed(lambdaResponse);
        } else {
            obniz.onconnect = async function () {
                console.log('obniz');

                let text = body.events[0].message.text;
                let res_text = 'うーん、私難しいことはわからないな〜';

                if　(text.match(/冷房/)) {
                    res_text = 'わかった！冷房をつけておいてあげるわね。ひんやりひんやり〜';
                }else
                if　(text.match(/暖房/)) {
                    res_text = 'わかった！暖房をつけておいてあげるわね。ぽっかぽかよ！';
                }else
                if　(text.match(/停止/)) {
                    res_text = 'エアコン消しとくわね。節約節約！';
                }else{
                    res_text = 'うーん、私難しいことはわからないな〜';
                }

                var led = obniz.wired("InfraredLED", { anode:0, cathode:1 } );
                obniz.display.clear();
                obniz.display.print("Connected!");
                console.log(res_text);

                if　(text.match(/冷房/)) {
                    console.log('reibo');
                    led.send([1,1,1,1,1,
                    //...中略...
                    0,0,0,0,0]);
                    obniz.display.clear();
                    obniz.display.print("Reibo ON");
                }else
                if　(text.match(/暖房/)) {
                    console.log('danbo');
                    led.send([1,1,1,1,1,
                    //...中略...
                    0,0,0,0,0]);
                    obniz.display.clear();
                    obniz.display.print("Danbo ON");
                }else
                if　(text.match(/停止/)) {
                    console.log('stop');
                    led.send([1,1,1,1,1,
                    //...中略...
                    0,0,0,0,0]);
                    obniz.display.clear();
                    obniz.display.print("OFF");
                }else{
                    //
                }

                const message = {
                    'type': 'text',
                    'text': res_text
                };
                console.log(message);
    
                client.replyMessage(body.events[0].replyToken, message)
                .then((response) => { 
                    let lambdaResponse = {
                    statusCode: 200,
                    headers: { "X-Line-Status" : "OK"},
                    body: '{"result":"completed"}'
                    };
                    context.succeed(lambdaResponse);
                }).catch((err) => console.log(err));

                await obniz.wait(5000);
                obniz.close();
            }
        }
    }else{
        console.log('署名認証エラー');
    }
};
```

text.matchの部分は、obnizとの通信部分とMessaging APIとの通信部分のif文をまとめて書くと、なぜかObnizの動作が失敗するので、上記のように分けて書くことで動作させてます。もっといい書き方はある気がします・・・(JSわからない)
また、Lambdaの環境変数の中に、先ほど控えたチャネルアクセストークンとチャネルシークレットを記載するのを忘れずに。
<img width="963" alt="lambda_env.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329420/0461d043-15e0-9c10-385b-5aac8371096c.png">

Lambdaには本来コンソールから直接プログラムを記載できるのですが、ObnizやLINEのSDKごとアップロードしなければいけないので、zipに固めてアップロードします。
やり方はObniz公式のドキュメントに書いてあります。(さっきのと同じ)
https://obniz.io/ja/lessons/server_side/lessons_lambda
ただ、おそらくzip後のファイルが10MBを越えるため、一度S3バケットにアップロードしリンクURLを指定するやり方をとる必要があるかもしれません。

## Webhook設定
作成したLambdaとAPI Gatewayを紐付け、エンドポイントを作成します。
細かい説明は省きますが、メソッドはPOST、認証はNONEにしましょう。(認証挟んだ方がセキュアだと思いますが手抜きしてしまいました。誰かLINE-SDKとの連携方法のやり方教えてください・・・)

作成できたAPIエンドポイントを、LINE DevelopersのWebhook URLに設定して、完成です！

# まとめ
これで夏も冬も快適エアコンライフを過ごすことができそうです。おかげさまで嫁にも好評でよかったよかった！
今回は要件満たすためのミニマム実装だったので、赤外線パターンを直接ハードコーディングしたりしてますが、これもLINE上から登録できるようにして、DynamoDBあたりに保存するようにできるといいかもですね。
またせっかくbotにしたので、雑談的な会話も対応させてみたかったのですが・・・docomoの雑談対話APIが終了してしまったので断念。
無料で使える雑談APIどこかにないでしょうかね・・・