平日19時にエアコンONにする？と聞く仕組みを作った

# はじめに

以前に外出先からエアコンを動かす仕組みを作りました。

* [Raspberry PiとSlackで外出先から自宅のエアコンを動かそう！](https://qiita.com/gnk263/items/661e64723b0194c5bc21)

しかしながら、実際に使っていると、**うっかり忘れてしまう**という運用上の欠点が発覚しました（知ってた）。

そこで今回は、予め決まった時間になると「エアコンをONにする？」と聞くようにすることで、**うっかり忘れてしまう**ことを防ぎたいと思います！


# 全体像

![system.png](https://qiita-image-store.s3.amazonaws.com/0/203323/8c466aec-61c2-146d-0cad-bc8f0f840fea.png)

Lambdaを定期実行し、「エアコンをONにする？」とSlackにボタン付きでPOSTします。
SlackでYesボタンを選択すると、別途用意されているAPI Gatewayを叩くことでエアコンが付きます！

なお、本記事の内容は、赤枠部分になります。赤枠以外は前回の内容そのままです。

* [Raspberry PiとSlackで外出先から自宅のエアコンを動かそう！](https://qiita.com/gnk263/items/661e64723b0194c5bc21)

# 環境構築

## Slackの環境構築

難しいことは行いません。メッセージを「受信」してアクション結果を「送信」するSlackアプリを作成します。

なお、投稿対象となるチャンネルは準備済みとします。

### アプリの作成

Slackの下記サイトを開き、Appを作成します。

* https://api.slack.com/apps

![slack01.png](https://qiita-image-store.s3.amazonaws.com/0/203323/0b3d0d2f-dc48-f9a6-ffe0-be93254e8298.png)

適当に作成します。

![slack02.png](https://qiita-image-store.s3.amazonaws.com/0/203323/00b2afd0-ca45-c888-f1d2-b06faf1ddf47.png)

### Incoming Webhooks

「Incoming Webhooks」を選択し、Onにします。

![slack03.png](https://qiita-image-store.s3.amazonaws.com/0/203323/2c0a54c5-cb9c-9222-c3fc-2601195ead6d.png)

一番下の「Add New Webhooks to Workspace」を選択し、許可を行います。

ここで作成されたWebhooks URLは後に必要になるためメモしておきます。

![slack04.png](https://qiita-image-store.s3.amazonaws.com/0/203323/cd7a6cbf-0aee-c2b1-cfaf-d8ad9ce7e8b7.png)

### Interactive Components

続いて、「Interactive Components」を選択し、Onにします。

![slack05.png](https://qiita-image-store.s3.amazonaws.com/0/203323/09b0bd47-487b-733c-3582-66addc933d8d.png)

「Request URL」には、応答先のURLを入力し、「Save Changes」を選択します。

ここの応答先とは、Slack上でボタンを押した際にPOSTする先となります。私の場合は、以前に作成したAPI Gatewayを使い回すため、そのURLを入力しています。

![slack06.png](https://qiita-image-store.s3.amazonaws.com/0/203323/4fcd8c86-b50a-c226-1c8d-b2f7d2ad3914.png)

## AWSの環境構築

Slackに通知するLambda（定期実行）を作成します。

AWS SAMを使っているため、ポイントだけご紹介します。

### Lambda

```python:app.py
import os
import json
import requests

SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]

def lambda_handler(event, context):
    post_slack("エアコンをOnにしますか？")

def post_slack(message):
    payload = {
        "attachments": [
            {
                "text": message,
                "callback_id": "ask_aircon",
                "color": "#3AA3E3",
                "attachment_type": "default",
                "actions": [
                    {
                        "name": "aircon",
                        "text": "Yes",
                        "type": "button",
                        "value": "yes"
                    },
                    {
                        "name": "aircon",
                        "text": "No",
                        "type": "button",
                        "value": "no"
                    }
                ]
            }
        ]
    }

    # http://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/
    try:
        response = requests.post(SLACK_WEBHOOK_URL, data=json.dumps(payload))
    except requests.exceptions.RequestException as e:
        print(e)
    else:
        print(response.status_code)
```

環境変数として下記を設定しています。

|Key|Value|
|---|---|
|SLACK_WEBHOOK_URL|Incoming WebhooksのWebhooks URL|

全貌はこちらをどうぞ。

* https://github.com/gnk263/NotifySlack

### CloudWatch Events

下記のcron式を使用し、「毎週月～金のAM10時(UTC)」に実行させています。日本時間の19時ですね。帰宅時間！（帰れるとは言ってない）

```
cron(0 10 ? * MON-FRI *)
```

## Raspberry Pi

[以前の作成したRaspberry Piのコード](https://qiita.com/gnk263/items/661e64723b0194c5bc21)を修正します。

修正結果はこちら。受信データの解析処理を分離させました。

```python:main.py
#長いので省略

def subscribe_callback(client, userdata, message):
    logger.info("Received a new message: ")
    logger.info(message.payload)
    logger.info("from topic: ")
    logger.info(message.topic)

    params = parse_payload.parse(message.payload)
    logger.info(json.dumps(params, indent=4))

    remote_control(params)

def remote_control(params):
    if is_aircon_on(params):
        logger.info("Execute GPIO_AIRCON_PIN")
        execute(GPIO_AIRCON_PIN)

def is_aircon_on(params):
    if params["command"] == "/control" and params["param"] == "aircon":
        return True
    if params["command"] == "ask_aircon" and params["param"] == "yes":
        return True
    return False
```

```python:parse_payload.py
import json
import urllib.parse as url_parse


def parse(payload):
    format_type = _check_format(payload)

    if format_type == "slash_commands":
        data = _parse_slash_commands(payload)
        return {
            "command": data["command"],
            "param": data["text"],
            "data": data
        }
    elif format_type == "interactive_message":
        data = _parse_interactive_message(payload)
        return {
            "command": data["callback_id"],
            "param": data["actions"][0]["value"],
            "data": data
        }
    else:
        return {}


def _check_format(payload):
    data = url_parse.unquote(payload.decode(encoding="utf-8"))
    if data.startswith("payload="):
        return "interactive_message"
    else:
        return "slash_commands"


def _parse_slash_commands(payload):
    params = {}
    key_value_list = url_parse.unquote(payload.decode(encoding="utf-8")).split("&")
    for item in key_value_list:
        (key, value) = item.split("=")
        params[key] = value
    return params


def _parse_interactive_message(payload):
    data = url_parse.unquote(payload.decode(encoding="utf-8")).lstrip("payload=")
    return json.loads(data)
```

全貌はこちらをどうぞ。

* https://github.com/gnk263/RaspberryPi_App

# 動作結果

平日の19時になると、Slackにメッセージが来ます。それをiPhoneが通知してくれます。

![result01.PNG](https://qiita-image-store.s3.amazonaws.com/0/203323/e7095e5e-ec0b-0c31-ca9c-f696ac69302e.png)

通知をタップ、もしくは、Slackを開くと下記のようになっており、「Yes」を選択すればエアコンがONになります！！

![result02.PNG](https://qiita-image-store.s3.amazonaws.com/0/203323/3d19702f-ad2f-b23c-ed7f-12b98bb5adf3.png)

1週間ほど運用してみましたが、エアコンを付け忘れて帰宅しても部屋が暑い、ということは無くなりました！！はっぴー！！

# 参考

* https://api.slack.com/interactive-messages
* https://api.slack.com/docs/message-buttons
* https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md
* https://qiita.com/masakabe/items/c47fc98e939475594842
* https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html