RaspberryPi zero WH・LINE API・heroku・Beebotteを使ってエアコンを遠隔操作する【ソフトウェア編】

---
# はじめに
自宅のエアコンをLINEから操作するようなシステムを作っていきます．
記事が長くなりそうなので

1. ラズパイからエアコンを操作できるようにする
2. LINEからメッセージを送ってエアコンを操作できるようにする

の2つに分けます．適宜必要な箇所を参照していただけると幸いです．
この記事では，2のLINEからメッセージを送り，それを受けてエアコンを制御する部分について説明します．

1のハードウェア部分の構成に関してはこちらです．

- [RaspberryPi zero WH・LINE API・heroku・Beebotteを使ってエアコンを遠隔操作する【ハードウェア編】](https://qiita.com/items/b6a7cfec62cd07fc0e69)

環境は**Windows10, WSL**で行います．

---
# 完成形

こんな感じでLINEからエアコンを操作します(クリックするとyoutubeに飛びます)．

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/CG9zMDD0T9s/0.jpg)](http://www.youtube.com/watch?v=CG9zMDD0T9s)

システムのフロー図を次に示します．

![2020-04-03_18h18_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/fce2f1ec-ea8b-92b6-8730-a516da7b5901.png)
操作は**LINE**から行うことができるので，出先のスマホからや研究室のPCなどから操作できます．

LINE APIからは**Webhook**が飛ばされ，それを**Heroku**上に立てたwebサーバがキャッチし，**mqtt**を通してラズパイに知らせます．

ラズパイでは，**赤外LED**を制御し，あらかじめ記録しておいたエアコンの信号を再生することでエアコンを制御するという形になっています．
<img width="300" alt="IMG_4539.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/fedda451-776f-1612-ade6-325216c5086a.png">
LINEの画面はこのようになっていて，**リッチメニュー**で操作できます．
また，センサを使用してエアコンが点灯したかどうかを確認できるようになっています．

---
# 本編の概要

本編は，

1. LINE Botの作成
2. HerokuとBeeboteの準備
3. Heroku上にWebサーバをデプロイする
4. ラズパイ上で動作させるプログラムの説明
5. 動作確認
6. 付録

という構成になっています．

---
# LINE Botの作成

LINEの**Messaging API**を使用して今回のシステムで操作の受け付け・結果表示を行うLINEのボットを作成します．
こちらのサイトを参考にして，LINE Developersへの登録・チャネルの作成を行ってください．

- [Messaging APIを利用するには](https://developers.line.biz/ja/docs/messaging-api/getting-started/#%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90)

「**aircon_control**」と適当に名前をつけてこのようにボットが作成できたら完了です．

![2020-04-01_08h03_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/8183c309-fca7-db60-2040-964b9aa798f7.png)

次に友達登録を行います．チャネルの管理画面から，**Messaging API**をクリックし，**QR code**を使用して友達登録を行ってください．

![2020-04-01_08h09_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/09b83e6d-5210-5d46-4e78-e110e373f0ed.png)

また次の画面を参考に「Auto-reply message」と「Greeting message」を**Disabled**に設定し，「Issue」ボタンを押してチャネルアクセストークンを発行します．

![2020-04-01_08h46_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/bbdc4d21-c3df-c030-b4ae-570169cc30da.png)

今後必要になってくる情報は以下の2つです．

- **Channel secret**
- **Channel access token**

こちらの情報は，先程のチャネルの管理画面から参照することができるので，必要になったらこのページにきてコピーしてください．

---
# HerokuとBeeboteの準備

## Herokuのセットアップ

### [Heroku](https://jp.heroku.com/) とは

[こちらのサイト](https://qiita.com/okasir4444/items/b9b1e70fdbbbd5794f1b)がわかりやすいです．

**Heroku**とは**PaaS**と呼ばれるWEBアプリケーションを簡単にホスティングできるサービスです．
初めからネットワークなどのインフラやOSなどのプラットフォームが整っている状態なので，ユーザはソースを書いてアップロードするだけで勝手にデプロイしてくれます．

### 登録

まず[こちら](https://signup.heroku.com/)からHerokuの登録を行ってください．

![2020-04-01_09h36_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/336c0c1d-5b1d-9054-0daa-4fb42d02630b.png)

### Heroku CLIのインストール

今回は**WSL**上に**Heroku CLI**(コマンドラインからHerokuを操作するもの)をインストールしたいと思います．

**WSL**のインストールは[こちら](https://qiita.com/matarillo/items/61a9ead4bfe2868a0b86)を参照してください．バージョン1で大丈夫です．

**Heroku CLI**のインストールは[こちら](https://510052.xyz/posts/p1c3q88urojvcrztas1h//)のサイトを参考にしました．
下記コマンドをWSL上で実行してください.

```bash
$ curl https://cli-assets.heroku.com/install.sh | sh
```

次のコマンドでHerokuにログインできたら成功です(ブラウザが立ち上がってログインするとターミナル上でもログインが完了します)．

```bash
$ heroku login
```

### アプリの登録

適当にアプリケーションの名前をつけてアプリを作成します．
この名前がURLに組み込まれるので，他の人と被ると使えません．

なので汎用的な名前(`test`など)はもう使われているので適当に探しましょう．また`_(アンダーバー)`は使えないので`-(ハイフン)`で代替します．

```bash 
$ heroku create {アプリ名}
```

あるいはHerokuの管理画面からもアプリの作成が行なえます．

![2020-04-01_12h18_39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/e2d35fec-1d8a-a15b-6648-7ce970e81efb.png)

## Beebotteのセットアップ

### 登録

[こちら](https://beebotte.com/)のサイトへ行ってBeebotteへの登録を行います．

![2020-04-03_18h37_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/7f66e0a2-4c0c-c061-be29-31511a02ab32.png)

### 新規チャネルの作成

登録が終わったら管理画面に行き，新規チャネルの登録を行います．
**Create New**をクリックします．

![2020-04-03_16h23_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/0e869032-0ea0-8abe-5078-b48df1b45456.png)

次の画面で名前等を設定します．

![2020-04-03_16h25_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/9fe0097b-14bd-4981-de35-53e8191f6bf8.png)

今回は,

| 設定 | 値 |
|:-:|:-:|
| Channel Name | my_home |
| Resource name | aircon_control |

と設定しました．ここは何でも大丈夫です．
ここの値がmqttのトピック名(**my_home/aircon_control**)のように設定されます．

### トークンの取得

管理画面から先程作成したChannelをクリックすると**Channel Token**(下図のtoken_\*の部分)を得ることが出来ます．

![2020-04-03_18h48_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/32cabe34-9386-5cfc-bd28-ae5b4f63b800.png)


---
# Webサーバをデプロイする

LINEの**Messaging API**から送信される`webhook`を受け取るwebサーバを作成します．

## 環境変数を設定する

まずトークンなどをHerokuの内部の環境変数として定義します．
**トークンの悪用などを防ぐ目的**なので必ず行いましょう．

まず**LINE API**関係です．

```bash
$ heroku config:set YOUR_CHANNEL_SECRET="Channel Secretの文字列" --app {アプリ名}
$ heroku config:set YOUR_CHANNEL_ACCESS_TOKEN="Access tokenの文字列" --app {アプリ名}
```

`Channel Secret`と`Access token`に関しては先程のLINEBotの管理画面から参照してください．

次に**Beebotte**関係です．

```bash
$ heroku config:set YOUR_BEEBOTTE_TOKEN="tokenの文字列(token_*の形)" --app {アプリ名}
```

先程取得したトークンを入力します．

## 必要なファイルを用意する

必要なファイルはこの5つです．

| ファイル名| 役割 | 
| -------- | -------- | 
|main.py|ソースコード| 
|runtime.txt|Pythonのバージョンを記載|
|requirements.txt|インストールするライブラリとそのバージョンを記載|
|Procfile|プログラムの実行方法を定義|
|mqtt.beebotte.com.pem|Beebotteのサーバにアクセスするための証明書．[**こちら**](https://beebotte.com/certs/mqtt.beebotte.com.pem)からダウンロード．|

適当にディレクトリを作成し，以下の内容をコピーしてファイルを作成してください．
**ファイル名を間違えると怒られるので気をつけてください．**


```:runtime.txt
python-3.7.0
```

```:requirements.txt
Flask==1.0.2
line-bot-sdk==1.16.0
paho-mqtt==1.5.0
```

```:Procfile
web: python main.py
```

`main.py`に関しては少し長いので次をクリックして見てください．

<details><summary>クリックして展開</summary><div>

```python=:main.py
from flask import Flask, request, abort
import paho.mqtt.publish as publish
import os
import json

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

# LINE API関係の設定値取得
YOUR_CHANNEL_ACCESS_TOKEN = os.environ['YOUR_CHANNEL_ACCESS_TOKEN']
YOUR_CHANNEL_SECRET = os.environ['YOUR_CHANNEL_SECRET']

# Beebotte関係の設定値取得
YOUR_BEEBOTTE_TOKEN = os.environ['YOUR_BEEBOTTE_TOKEN']

line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

# 動作を起こすメッセージのリスト
on_msg = [s.encode('utf-8') for s in ['on', 'エアコンつけて！']]
off_msg = [s.encode('utf-8') for s in ['off', 'エアコン切って！']]

# LINEに通知メッセージを送る
def broadcast_line_msg(msg):
    line_bot_api.broadcast(TextSendMessage(text=msg))

# エアコン制御用のMQTTをパブリッシュする
def publish_aircon_control_msg(msg):
    publish.single('my_home/aircon_control', \
                    msg, \
                    hostname='mqtt.beebotte.com', \
                    port=8883, \
                    auth = {'username':'token:{}'.format(YOUR_BEEBOTTE_TOKEN)}, \
                    tls={'ca_certs':'mqtt.beebotte.com.pem'})

@app.route('/callback', methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info('Request body: ' + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    msg = event.message.text.encode('utf-8')

    if msg in on_msg:
        publish_aircon_control_msg('on')
    elif msg in off_msg:
        publish_aircon_control_msg('off')
    else:
        broadcast_line_msg('\n'.join(['エアコンをつけたい時：', \
                                     *['['+s.decode('utf-8')+']' for s in on_msg], \
                                     '\nエアコンを消したい時：', \
                                     *['['+s.decode('utf-8')+']' for s in off_msg] , \
                                     '\nって話しかけてね！']))

if __name__ == '__main__':
    port = int(os.getenv('PORT'))
    app.run(host='0.0.0.0', port=port)
```
</div></details>

### main.pyの説明

以下，**main.py**の簡単な説明です．

```python
# LINE API関係の設定
YOUR_CHANNEL_ACCESS_TOKEN = os.environ['YOUR_CHANNEL_ACCESS_TOKEN']
YOUR_CHANNEL_SECRET = os.environ['YOUR_CHANNEL_SECRET']

# Beebotte関係の設定
YOUR_BEEBOTTE_TOKEN = os.environ['YOUR_BEEBOTTE_TOKEN']
```

先程環境変数として設定したLINE APIやBeebotteのトークンを取得しています

```python
# 動作を起こすメッセージのリスト
on_msg = [s.encode('utf-8') for s in ['on', 'エアコンつけて！']]
off_msg = [s.encode('utf-8') for s in ['off', 'エアコン切って！']]
```

LINEから受け取ったメッセージのうち，どのフレーズでon/off動作を行うか設定します．
日本語の処理が面倒なので，一旦すべて**UTF-8**にエンコードして扱います．

```python
# エアコン制御用のMQTTをパブリッシュする
def publish_aircon_control_msg(msg):
    publish.single('my_home/aircon_control', \
                    msg, \
                    hostname='mqtt.beebotte.com', \
                    port=8883, \
                    auth = {'username':'token:{}'.format(YOUR_BEEBOTTE_TOKEN)}, \
                    tls={'ca_certs':'mqtt.beebotte.com.pem'})
```

`publish_aircon_control_msg`関数では`msg`を受け取ってそれをpayloadとしてパブリッシュします．
mqttのパブリッシュには**paho.mqtt**のの**Publish**クラスを使用します．リファレンスは[こちら](https://www.eclipse.org/paho/clients/python/docs/#id2)です．

```python
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    msg = event.message.text.encode('utf-8')

    if msg in on_msg:
        publish_aircon_control_msg('on')
    elif msg in off_msg:
        publish_aircon_control_msg('off')
    else:
        broadcast_line_msg('\n'.join(['エアコンをつけたい時：', \
                                     *['['+s.decode('utf-8')+']' for s in on_msg], \
                                     '\nエアコンを消したい時：', \
                                     *['['+s.decode('utf-8')+']' for s in off_msg] , \
                                     '\nって話しかけてね！']))
```

LINEからのwebhookの署名が正しい場合にこちらのハンドラが実行されます．
on/offメッセージを判断し，mqttをパブリッシュします．それ以外のメッセージの場合，反応するメッセージのリストを示すようになっています．

![2020-04-03_19h35_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/db6cbc3b-4da7-8767-d072-4703a197583a.png)

表示はこんな感じです．

## デプロイを行う

**Heroku**ではアプリのソースをGitレポジトリとして管理するので，ローカルでGitレポジトリを構成します．

対象のディレクトリで移動し，次のコマンドを実行します．

```bash
$ git init
```

ここで

```
*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.
```

などと出たらgitの初期設定を行っていないので指示に従って設定を済ませます．

リモートレポジトリにherokuのレポジトリを指定します．

```bash
$ heroku git:remote -a {アプリ名}
```

次に各ファイルを追跡対象に加えコミットします．

```bash
$ git add .
$ git commit -m "Initial commit"
```

最後にプッシュします．こちらは**Heroku**にログインした状態で行ってください．

```bash
$ git push heroku master
```

これでエラーログなどが出なければデプロイ成功です．

## LINE APIでWebhookの設定を行う

LINE APIの管理画面から**Webhook**の設定を行います．
Webhook URLに，

```
https://{アプリ名}.herokuapp.com/callback
```

と設定して**Verify**します．

![2020-04-01_21h48_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/e5dcfbdf-a6bd-541d-cc9b-544323e26abf.png)

以上で**LINE->Heroku->Beebotte**の通路が完成しました．

---
# ラズパイ上で動作させるプログラム

次に**Publish**されたmqttを**Subscribe**して赤外LEDを制御するラズパイ側のプログラムを説明します．

## ライブラリのインストール

まず`pip3`からインストールします．`Raspbian`では最初から入っていないため，手動でインストールする必要があります．

こちらの[サイト](https://turtlechan.hatenablog.com/entry/2019/06/07/191450)を参考にしました．

```bash
$ sudo apt-get -y install python3-dev
$ sudo apt-get -y install python3-pip
```

インストールされたか確認します．

```bash
$ pip3 --version
```

バージョン情報が出てきたら成功です．

次に必要なライブラリをインストールします．

```bash
$ pip3 install paho-mqtt
$ pip3 install line-bot-sdk
```

## 証明書のダウンロード

[**こちら**](https://beebotte.com/certs/mqtt.beebotte.com.pem)から`mqtt.beebotte.com.pem`をダウンロードして，同ディレクトリに置いてください．

## プログラム本体

<details><summary>クリックして展開</summary><div>

```python:mqtt_aircon_control.py
import subprocess
import paho.mqtt.client as mqtt     # MQTTのライブラリをインポート
import datetime
from linebot import LineBotApi
from linebot.models import TextSendMessage
from linebot.exceptions import LineBotApiError

# LINEでメッセージを送信
def send_line_msg(msg):
    line_bot_api = LineBotApi({LINEのアクセストークンの文字列})
    line_bot_api.broadcast(TextSendMessage(text=msg))

# ブローカーに接続できたときの処理
def on_connect(client, userdata, flag, rc):
    print('[mqtt_aircon_control.py] Connected with result code ' + str(rc))  # 接続できた旨表示
    client.subscribe('my_home/aircon_control')  # subするトピックを設定 

# ブローカーが切断したときの処理
def on_disconnect(client, userdata, flag, rc):
    if  rc != 0:
        print('[mqtt_aircon_control.py] Unexpected disconnection.')

# メッセージが届いたときの処理
def on_message(client, userdata, msg):
    # エアコンの信号データのパスを指定
    stop_log_path = '/home/pi/ws/send_data/stop.log'
    heater_log_path = '/home/pi/ws/send_data/heater.log'
    cooler_log_path = '/home/pi/ws/send_data/cooler.log'
    # プログラムの実行ファイルのパスを指定
    exec_send_command = '/home/pi/ws/send'
    exec_switching_verify_command = '/home/pi/ws/switching_verify'

    # メッセージ受け取り
    get_msg = msg.payload.decode('utf-8')

    # 現在日時を取得し，送信するデータファイルを決定する
    dt_now = datetime.datetime.now()
    print('[mqtt_aircon_control.py] Get aircon_control topic(msg:{}) [{}]'.format(get_msg, dt_now.strftime('%Y/%m/%d %H:%M:%S')))
    if 11 <= int(dt_now.strftime('%m')) or int(dt_now.strftime('%m')) <= 4:
        send_data_file = heater_log_path
    else:
        send_data_file = cooler_log_path

    # 信号送信処理
    if get_msg == 'on':
        send_line_msg('エアコンの電源を入れるよ！')
        for i in range(3):
            subprocess.run(['sudo', exec_send_command, send_data_file])
            ret = subprocess.run([exec_switching_verify_command , '10'])
            try:
                if ret.returncode == 0:
                    print('[mqtt_aircon_control.py] Switching successed.')
                    send_line_msg('エアコンの起動を確認したよ！')
                    break
            except:
                print('[mqtt_aircon_control.py] Switching failed.')
                continue
    elif get_msg == 'off':
        send_line_msg('エアコンの電源を消すよ！')
        subprocess.run(['sudo', exec_send_command, stop_log_path])

# MQTTの接続設定
client = mqtt.Client()                 # クラスのインスタンス(実体)の作成
client.on_connect = on_connect         # 接続時のコールバック関数を登録
client.on_disconnect = on_disconnect   # 切断時のコールバックを登録
client.on_message = on_message         # メッセージ到着時のコールバック
 
client.username_pw_set("token:{Beebotteのチャネルトークン(token_*の形)}") 
client.tls_set("/home/pi/ws/mqtt.beebotte.com.pem")
client.connect("mqtt.beebotte.com", 8883, 60)

send_line_msg('システムを起動するよ！')

client.loop_forever()                  # 永久ループして待ち続ける
```

</div></details>

## プログラムの説明

上記のプログラムの簡単な説明を行います．

```python:send_line_msg()
# LINEでメッセージを送信
def send_line_msg(msg):
    line_bot_api = LineBotApi({LINEのアクセストークンの文字列})
    line_bot_api.broadcast(TextSendMessage(text=msg))
```

`send_line_msg`関数では，引数に文字列を受け取り，それをLINEの登録している友達に送信します．{**LINEのアクセストークンの文字列**}には自分のLINEBotのアクセストークンを記入します．


```python:on_massage()
# メッセージが届いたときの処理
def on_message(client, userdata, msg):
    # エアコンの信号データのパスを指定
    stop_log_path = '/home/pi/ws/send_data/stop.log'
    heater_log_path = '/home/pi/ws/send_data/heater.log'
    cooler_log_path = '/home/pi/ws/send_data/cooler.log'
    # プログラムの実行ファイルのパスを指定
    exec_send_path = '/home/pi/ws/send'
    exec_switching_verify_path = '/home/pi/ws/switching_verify'

    # メッセージ受け取り
    get_msg = msg.payload.decode('utf-8')

    # 現在日時を取得し，送信するデータファイルを決定する
    dt_now = datetime.datetime.now()
    print('[mqtt_aircon_control.py] Get aircon_control topic(msg:{}) [{}]'.format(get_msg, dt_now.strftime('%Y/%m/%d %H:%M:%S')))
    if 11 <= int(dt_now.strftime('%m')) or int(dt_now.strftime('%m')) <= 5:
        send_data_file = heater_log_path
    else:
        send_data_file = cooler_log_path

    # 信号送信処理
    if get_msg == 'on':
        send_line_msg('エアコンの電源を入れるよ！')
        for i in range(3):
            subprocess.run(['sudo', exec_send_path, send_data_file])
            ret = subprocess.run([exec_switching_verify_path , '10'])
            try:
                if ret.returncode == 0:
                    print('[mqtt_aircon_control.py] Switching successed.')
                    send_line_msg('エアコンの起動を確認したよ！')
                    break
            except:
                print('[mqtt_aircon_control.py] Switching failed.')
                continue
    elif get_msg == 'off':
        send_line_msg('エアコンの電源を消すよ！')
        subprocess.run(['sudo', exec_send_path, stop_log_path])
```

`on_message`関数は，`paho`のmqttクライアントがサブスクライブしているトピックのメッセージを受信した際に実行される関数です．

処理内容は，まずメッセージを受け取り`on`と`off`を判別します．
`on`の場合，11月から4月なら**暖房**，それ以外なら**冷房**の信号を送信します．
`off`の場合は，そのまま`stop`信号を送信します．

ユーザが環境によって変えるところは，**信号のログファイル**(ハードウェア編で作成した`stop.log`など)と，**送信プログラム**と**起動確認用プログラム**の実行ファイルのパスです．

```python:main
# MQTTの接続設定
client = mqtt.Client()                 # クラスのインスタンス(実体)の作成
client.on_connect = on_connect         # 接続時のコールバック関数を登録
client.on_disconnect = on_disconnect   # 切断時のコールバックを登録
client.on_message = on_message         # メッセージ到着時のコールバック
 
client.username_pw_set("token:{Beebotteのチャネルトークン(token_*の形)}") 
client.tls_set("/home/pi/ws/mqtt.beebotte.com.pem")
client.connect("mqtt.beebotte.com", 8883, 60)

send_line_msg('システムを起動するよ！')

client.loop_forever()                  # 永久ループして待ち続ける
```

`main`関数がないので，スクリプトを実行したらこのコードが実行されます．
`client.tls_set`にはサーバ証明書(`mqtt.beebotte.com.pem`)の**フルパス**を指定します．

## 電源を入れた時にプログラムを自動起動

電源を入れる度に先程のpyファイルを実行するのは面倒くさいので，ラズパイが起動したら際にプログラムも自動で起動するようにします．

自動起動にはいくつか方法がありますが今回は**systemd**を使用します．
[**こちら**](https://qiita.com/tkato/items/6a227e7c2c2bde19521c)の記事を参考にしました．

`*.service`に指定する実行するシェルスクリプトだけ示します(記事中の`autoexec.sh`に該当するもの)．

```bash:aircon_control.sh
#!/bin/sh

/usr/bin/python3 -u /home/pi/ws/mqtt_aircon_control.py >> /home/pi/ws/aircon_control.log 2>&1
```

`/usr/bin/python3`はフルパスを指定する必要があります．

`-u`は標準出力のバッファリングを無効にします．実行中の出力などをファイルに書き出したいため，このオプションを指定します．

`/home/pi/ws/mqtt_aircon_control.py`は先程作成したpythonスクリプトです．

`>> /home/pi/ws/aircon_control.log 2>&1`はスクリプトの出力をファイルにリダイレクトします．ここではpythonスクリプトと同ディレクトリに追記で出力するようにしています．

---
# 動作確認＆最後に

以上で作業は完了になります．
`on`や`off`とメッセージを送ることでラズパイが動作し，エアコンが制御できるようになりましたか？

上手くいかない場合はデバッグを行うことになります．
以下のコマンドでherokuのログを確認したり，デバッグプリントを入れたりして地道に原因を突き止めましょう．

```bash
$ heroku logs --tail
```

デバッグは辛いときも多いですがその分動作したときの喜びも一入です．
頑張ってください．

なにか分からないことがあればコメントで対応します．

ちなみに当初は**CloudMQTT**を使用してこちらの記事を書いていました．
ですが，[無料プランが使用できなくなるとのこと](https://www.cloudmqtt.com/blog/2020-02-13-cloudmqtt-cute-cat-free-plan-out-of-stock.html)で急遽**Beebotte**を使う仕様に変更しました．なので，Beebotteならこうした方がいいよ等あるかもしれませんがその時ははコメントで教えて下さい．
**CloudMQTT**の方が手軽だったので残念でした．

最後に，ハードウェア編もよろしくお願いします．

- [RaspberryPi zero WH・LINE API・heroku・Beebotteを使ってエアコンを遠隔操作する【ハードウェア編】](https://qiita.com/items/b6a7cfec62cd07fc0e69)

---
---
# 付録A.LINE botにリッチメニューを実装する

LINE APIを使用してリッチメニューから操作できるようにします．
Messaging APIのリッチメニューに関しては[こちら](https://developers.line.biz/ja/docs/messaging-api/using-rich-menus/#creating-a-rich-menu-using-the-messaging-api)を参照してください．

<img width="300" alt="IMG_4539.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/fedda451-776f-1612-ade6-325216c5086a.png">

リッチメニューを登録する順番は，

1. リッチメニューオブジェクトをボディにしたリクエストをだし，**richMenuId**を取得する．
2. 画像をアップロードする
3. デフォルトのリッチメニューとして登録する

という感じです．これを自動で行うpythonスクリプトを作成したのでそれを利用してください．

まず，画像編集ソフトを使用してリッチメニューにアップする画像を作成します．
[自分が作成した画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/bd121bf6-2425-0076-a618-e1238ebf1f45.png)も一応上げておきます．

![2020-04-03_21h26_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427721/b0591b9f-b73f-b188-7ba0-8d85318fbcbd.png)

次に下記のスクリプトを使用してリッチメニューの登録を行います．

<details><summary>クリックして展開</summary><div>

```python:richmenu.py
import requests

url = 'https://api.line.me/v2/bot/richmenu'
access_token={channel access token}
img_path = 'rich_menu_simple.png'
send_data='''{
"size":{
    "width":2500,
    "height":843
},
"selected": true,
"name": "LINE Developers Info",
"chatBarText": "Tap to open",
"areas": [
    {
        "bounds": {
            "x": 0,
            "y": 0,
            "width": 1250,
            "height": 843
        },
        "action": {
        "type": "message",
        "label": "on",
        "text": "エアコンつけて！"
        }
    },
    {
        "bounds": {
            "x": 1250,
            "y": 0,
            "width": 1250,
            "height": 843
        },
        "action": {
        "type": "message",
        "label": "off",
        "text": "エアコン切って！"
        }
    }
]
}'''

header = {'Authorization': 'Bearer {}'.format(access_token)}
res = requests.post(url, headers={**header, **{'Content-Type': 'application/json'}}, data=send_data.encode("utf-8"), verify=True).json()

update_url = 'https://api.line.me/v2/bot/richmenu/{}/content'.format(res['richMenuId'])
img_file = open(img_path, "rb")
update_res = requests.post(update_url, headers={**header, **{'Content-Type': 'image/jpeg'}}, data=img_file, verify=True).json()

if update_res:
    print('[ERROR] ', update_res)
    exit()

apply_url = 'https://api.line.me/v2/bot/user/all/richmenu/{}'.format(res['richMenuId'])
apply_res = requests.post(apply_url, headers=header, verify=True).json()

if apply_res:
    print('[ERROR] ', apply_res)
    exit()

print('[SUCCESS] Rich menu image update!')

```
</div></details>


使用する場合は`access_token`，`img_path`，`send_data`を編集します．
`send_data`に関しては先程のリンクを参考にしてください．

実行して**SUCCESS**が表示されたら成功です．

リッチメニューを押すことで特定のメッセージが送信される仕組みになっています(その後はいままでの処理と同じ)．

# 付録B.Heroku上のアプリを24時間起動させる

通常，HerokuにデプロイしたWebアプリは30分間アクセスがないと自動で停止し，次にアクセスが起こった時にまた起動します．
この起動に時間がかかるため，できるだけ起動したままの状態を保持したいです．

今回は**cron**を使用して定期的にサーバにリクエストを送ることで停止するのを防ぎます．

以下のコマンドで設定します．

```bash
$ crontab -e
```

エディタが開いたらファイルの最後に以下を追記します．
20分ごとにリクエストをだし，出力は捨てるようになっています．

```
*/20 * * * * curl https://{アプリ名}.herokuapp.com/ > /dev/null 2>&1
```

正常に動作しているかの確認はherokuのログを見ることで確認できます．
次のコマンドでログを見てみましょう．

```bash
$ heroku logs --tail
```

Herokuの無料プランの枠は**一月あたり1000時間**なので，1つのアプリを運用することは問題ありあませんが，2つ以上はこれを超過してしまうため気をつけましょう．