ボロエアコンをTwitter上から操作する(Python+TwitterAPI+Arduino)

学生の頃に書いた記事を見直すと、意外と閲覧数が多くてびっくりしました。
そして、コードの汚さにもびっくりしました。

どこかで時間を使って、もう少し綺麗にコードを書き直します。

#はじめに
自室にあるエアコンのリモコンが故障してしまった。20年近く前のものなので、寧ろよくもってくれていた。

万能リモコンを購入してもよかったが、自分で作った方が安上がりだったため自作した。
ただの万能リモコンだと今ひとつなので、Twitter上で指定のワードをつぶやくと電源の入切を制御できるように機能を拡張した。

#目標
Twitterで「#AC_switch_ON」「#AC_switch_OFF」というワードをつぶやくことで、自室のエアコンの電源を制御する。

TwitterAPIの利用等を含め、Pythonに関するコーディング技術の向上を図る。

自分のようなパンピー学生が見てもわかりやすいような記事を書く。

#PythonとArduinoの役割
Python側で自分のツイートに指定ワードが流れていないかを監視する。
指定ワードが含まれていた場合、その情報をシリアル通信でArduinoに伝える。

Arduino側はPython側から受け取った情報を基に赤外線を発光し、エアコンを制御する。

#部品
- [Arduino UNO](https://www.switch-science.com/catalog/789/)
- [赤外線LED](http://akizukidenshi.com/catalog/g/gI-03261/)
- [赤外線センサ](http://akizukidenshi.com/catalog/g/gI-00824/)
- 抵抗(220Ω)
- ジャンパワイヤー

赤外線センサ以外は既に持っていたので、今回の費用は30円である。

#配線
![Untitled Sketch 4_ブレッドボード.png](https://qiita-image-store.s3.amazonaws.com/0/186515/ac23c2f9-5a93-f99b-a488-72a3add3ef70.png)

自分が実際に使用した赤外線センサがfritzing内で見つけられなかった。
配線図に使用したセンサは[こちら](http://akizukidenshi.com/catalog/g/gI-00622/)。
これでも同じように動作する...はず。

#1.エアコンの赤外線信号解析
どのように赤外線を発光すればエアコンを制御できるかを、解析する必要がある。

下記のページを参考にさせていただきました。
コンパイルしてArduinoに書き込めば利用できるソースも載せてくれているので、ここは省略。

赤外線センサに向けて、リモコンを照射するとシリアルモニタに解析した赤外線信号がでてくる。
隣の部屋にあるエアコンのリモコンが自室のものと同一だったので助かった...。

ここで解析した信号を鸚鵡返しのように赤外線LEDから発光すれば、行いたい制御が行える。

[ERESTAGE - Arduinoで赤外線リモコンの信号を解析する](https://www.erestage.com/arduino/remo_recv/)

#2.Python+TwitterAPIで指定ワードを監視
Python側のプログラムは、TwitterAPI関連のモジュールが2つ、シリアル通信のモジュールが1つ、それらを動作させるmainのプログラムの4つで構成されている。

mainのプログラムは以下である。。

```Python:ac_switcher_with_twitter.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# import
from twitter_package import get_tweetid_module
from twitter_package import get_user_timeline_module
from arduino_package import serial_module
from time import sleep

# TwitterAPIに必要なCK,CS,AT,ASを設定
# 同時にTwitterアカウントも指定
CK = ***                                    # Consumer Key
CS = ***                                    # Consumer Secret
AT = ***                                    # Access Token
AS = ***                                    # Accesss Token Secert
ID = ***                                    # ID

# 取得したツイート格納用配列の定義
timeline = []

# 最終ツイートのIDを取得する
since_id = get_tweetid_module.get_tweetID(CK, CS, AT, AS, ID)


while True:

    # API制限回避用にディレイを入れる
    sleep(300)

    # since_id以降のツイートを取得してtimelineに格納
    timeline = get_user_timeline_module.get_user_timeline(CK, CS, AT, AS, ID, since_id)

    # timelineから指定文字列を検索
    # 検索結果に応じてArduinoにシリアル通信
    # ACオン
    for i in timeline:
        if "#AC_switch_ON" in i:
            flag = 1
            serial_module.serial_com(flag)
            break

    # ACオフ
    for i in timeline:
        if "AC_switch_OFF" in i:
            flag = 0
            serial_module.serial_com(flag)
            break

    # プログラム停止
    for i in timeline:
        if "AC_switch_stop" in i:
            flag = -1
            break
    if flag == -1:
        break

    # 最終ツイートのIDを更新
    since_id = get_tweetid_module.get_tweetID(CK, CS, AT, AS, ID)

    # timelineを初期化
    timeline.clear()
```

TwitterAPI関連のモジュールは以下の2つである。

```Python:get_tweetid_module.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# get_tweetidは指定したアカウントの最終ツイートのIDを取得するモジュール
# 引数はCK，CS，AT，AS，アカウントID


from requests_oauthlib import OAuth1Session
import json


def get_tweetID(CK, CS, AT, AS, ID):

    # タイムライン取得用のURL
    url = "https://api.twitter.com/1.1/statuses/user_timeline.json"

    # user_idで取得するアカウントを指定
    # countで最新ツイートを指定
    params = {"user_id": ID, "count": 1}

    # OAuth で GET
    twitter = OAuth1Session(CK, CS, AT, AS)
    req = twitter.get(url, params=params)

    if req.status_code == 200:
        # レスポンスはJSON形式なので parse する
        timeline = json.loads(req.text)
        # 最新ツイートのツイートIDを返す
        for tweet in timeline:
            return tweet["id"]

    else:
        # エラーの場合
        print("Error: %d" % req.status_code)


if __name__ == "__main__":

    # CK = ***
    # CS = ***
    # AT = ***
    # AS = ***
    # ID = ***

    print(get_tweetID(CK, CS, AT, AS, ID))
```

```Python:get_user_timeline_module.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# get_timelineは指定したアカウントのタイムラインを取得するモジュール
# since_idを利用して取得するツイートの範囲を限定
# 引数はCK，CS，AT，AS,アカウントID,since_id


from requests_oauthlib import OAuth1Session
import json


def get_user_timeline(CK, CS, AT, AS, ID, since_id):

    # 取得したツイート格納用の配列
    tweet_list = []

    # タイムライン取得用のURL
    url = "https://api.twitter.com/1.1/statuses/user_timeline.json"

    # user_idでアカウントIDを指定
    # countで取得ツイート数を上限まで上げる
    # since_idで取得するツイートの範囲を限定
    params = {"user_id": ID, "count": 200, "since_id": since_id}

    # OAuth で GET
    twitter = OAuth1Session(CK, CS, AT, AS)
    req = twitter.get(url, params=params)

    if req.status_code == 200:
        # レスポンスはJSON形式なので parse する
        timeline = json.loads(req.text)
        # sinceID以降のツイートをリストにまとめて返す
        for tweet in timeline:
            tweet_list.append(tweet["text"])
    else:
        # エラーの場合
        print("Error: %d" % req.status_code)

    #ツイート格納用の配列を返す
    return tweet_list


if __name__ == "__main__":

    # CK = ***
    # CS = ***
    # AT = ***
    # AS = ***
    # ID = ***
    # since_id = ***

    print(get_user_timeline(CK, CS, AT, AS, ID, since_id))
```

シリアル通信のモジュールは以下である。

```Python:serial_module.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# serial_moduleは引数にとった数値をByte型に変換してArduinoにシリアル通信で送信するモジュール


import serial
from time import sleep


def serial_com(flag):
    with serial.Serial("/dev/tty.usbmodem1421",9600,timeout=1) as ser:

            # シリアル通信開始直後に送信するとエラーになるのでディレイさせる
            sleep(5)

            # Byte型に変換
            flag_byte = flag.to_bytes(1,"big")

            # 送信
            ser.write(flag_byte)


if __name__ == "__main__":
    serial_com()
```

TwitterAPIやシリアル通信等の説明を含めると長くなってしまうので省略する。(参考文献を載せる)

プログラムの大まかな流れは、下記の6ステップとなっている。

1. 実行時に自身の最新ツイートのツイートIDを取得する
2. 1.で取得したツイートID以降のツイートをチェック
3. #AC_switch_ON,#AC_switch_OFF,#AC_switch_stopのそれぞれがツイートに含まれていないかチェックする
4. 3.の結果に基づいてflagの内容をArduinoに送ったり、プログラムを停止したりする
5. 最新ツイートのツイートIDを取得して、1.の値を更新する
6. 2~5を300秒おきに繰り返す。ここで300秒ディレイさせているのはTwitterAPIの制限を回避するため。(適当な値でディレイさせているので、もっと短く設定することも可能)

それぞれの処理の細かい説明はコメント文に書いてあるので、そちらを参照していただきたい。


#3.受信したデータに基づきArduinoが赤外線を発光
serial_module.pyから受け取ったflagに基づいて、あらかじめ設定した赤外線信号を発光する。
Arduinoのソースは以下である。

```Arduino:ac_switcher_with_twitter.ino
int ir_out = 10;

// ACをONにするための信号
unsigned int on_data[] = {3930,9913,336,154,56,25,56,27,55,107,54,32,55,106,54,32,54,31,50,32,50,111,50,115,50,37,50,33,50,33,50,112,50,115,47,39,48,35,47,35,47,36,46,36,46,36,47,36,46,36,47,37,46,37,46,36,47,36,46,37,46,114,46,40,47,36,47,36,46,37,46,37,46,36,46,37,45,116,46,41,45,37,46,37,45,38,45,116,46,118,46,119,45,120,44,120,46,119,46,119,46,119,45,42,44,40,43,116,45,41,45,38,44,39,44,41,41,42,42,40,42,40,43,42,41,117,44,121,44,43,44,41,42,118,44,121,44,122,43,43,43,118,44,43,44,42,41,118,44,43,43,40,43,42,41,42,41,41,41,42,42,41,41,42,41,42,41,41,42,41,42,41,41,42,40,42,41,42,41,120,41,45,42,41,42,41,41,42,41,42,40,42,41,42,41,41,41,42,42,42,41,42,41,42,41,42,41,41,42,42,41,42,41,42,42,41,42,41,42,42,41,42,42,41,42,41,42,41,42,40,42,119,43,44,43,40,44,39,44,118,44,42,44,39,44,116,45,118,47,119,46,41,46,115,46,40,46,37,46,116,46};
int dataSize_on = sizeof on_data / sizeof(on_data[0]);

// ACをOFFにするための信号
unsigned int off_data[] = {337,157,59,23,57,26,57,106,55,30,56,105,56,31,55,30,52,30,53,108,53,112,52,34,52,31,52,31,51,110,52,113,51,36,50,32,51,32,50,33,50,32,50,33,50,33,50,33,50,33,50,33,50,33,50,33,49,33,49,112,49,38,49,33,50,34,49,33,49,36,47,34,49,34,49,112,49,38,48,36,46,34,49,36,46,113,48,41,46,37,46,36,47,37,45,38,45,38,45,116,46,41,45,116,45,119,46,117,48,118,47,117,48,120,45};
int dataSize_off = sizeof off_data / sizeof(off_data[0]);
 
// セットアップ
void setup() {
  pinMode(ir_out, OUTPUT);
  Serial.begin(9600);
}
 
// dataからリモコン信号を送信
void sendSignal(unsigned int *data, int dataSize) {
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data[cnt]*10;  // dataは10us単位でON/OFF時間を記録している
    unsigned long us = micros();
    do {
      digitalWrite(ir_out, 1 - (cnt&1)); // cntが偶数なら赤外線ON、奇数ならOFFのまま
      delayMicroseconds(8);  // キャリア周波数38kHzでON/OFFするよう時間調整
      digitalWrite(ir_out, 0);
      delayMicroseconds(7);

    } while (long(us + len - micros()) > 0); // 送信時間に達するまでループ
  }
}
 
void loop() {
  
  if (Serial.available() > 0) {
    int cmd = Serial.read();
    switch (cmd) {
      case 0 : sendSignal(off_data,dataSize_off); break;
      case 1 : sendSignal(on_data,dataSize_on); break;
    }
  } 
}
```

もっと扱える信号を増やすのならば、構造体等で赤外線信号をまとめたほうがいいと思う。(既に見づらい)

テスト時に赤外線LEDが発光しているかを確かめるには、スマホ等のカメラ越しにLEDをうつせば赤外線をとらえることができる。
自分はiPhone7を使用しているが、インカメでのみ発光を確認できた。

#4.実行結果
![twi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186515/ae4b9cf8-a353-c421-2f34-383800c098c5.png)


エアコンがONになったことを確認した。

#5.終わりに
Qiitaに初投稿したが、思った以上にまとめるのが難しかったので、随時気になった箇所を更新していく。
実務経験なし、かつPython初心者のため、見づらいコードになっているかもしれない。努力する。

何か不明な点やアドバイス等があればコメント等でご連絡ください。

準備が整い次第Githubにもあげます。

#参考文献
- [Qiita - PythonでTwitterAPIにアクセス](https://qiita.com/yubais/items/dd143fe608ccad8e9f85)
- [コード7区 - TwitterAPIでツイートを大量に取得。サーバー側エラーも考慮(pythonで)](http://ailaby.com/twitter_api/)
- [FUTABA HOME - 赤外線リモコンについて](http://www.256byte.com/remocon.htm)
- [ERESTAGE - Arduinoで赤外線リモコンの信号を解析する](https://www.erestage.com/arduino/remo_recv/)
- [ERESTAGE - Arduinoで赤外線リモコン信号を発信する](https://www.erestage.com/arduino/arduino-send-remo/)
- [Qiita - PC-Arduino間 Python経由 シリアル通信備忘録](https://qiita.com/Acqua_Alta/items/9f19afddc6db1e4d4286)