52
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

エアコンをHackしてスマートリモコンをつくる

Last updated at Posted at 2021-08-10

エアコンつけたいけどリモコンに手が届かない……という経験はないでしょうか。エアコンでなくとも似たような経験がある方は多いと思います。 そこで、より怠惰な生活を送るためにRaspberry Piを使ってスマートフォンからエアコンを操作できるシステムを作ることにしました。
システム概要図.jpg
これがシステムの概要図になります。拡張性のためにDiscordを介してRaspberry Piにメッセージを送信するようにしています。
エアコンは赤外線通信によって操作します。なのでライトやテレビのリモコンのような赤外線通信を行うリモコンであれば同じ要領でスマートリモコン化することができます。

1. ラズパイのセットアップ

Raspberry PiをWi-Fiに接続しSSHを利用可能にします。
既にわかりやすい記事を書いてくださっている方がいらっしゃるのでここは割愛します。

2. 赤外線送受信回路を作る

とてもわかりやすい記事を書いてくださっている方がいらっしゃるのでここも割愛します。

3. エアコンのリモコン信号を解析する

3.1 信号解析の必要性

記録した信号を送信すればエアコンを単純にON,OFFすることはできます。しかし、細かな操作をすることはできません。
というのも、エアコンのリモコンは、設定温度、運転モードなどすべてのデータを毎回エアコン本体に送信しているためです。例えば、夏場に冷房で運転しているときにエアコンをONにする信号を記録した場合、冬場に同じ信号でエアコンをONにすると冷房でエアコンが起動してしまいます。
これではあまりに使い勝手が悪いため、エアコンの信号を解析することでエアコンの状態の管理・信号の生成をRaspberry Pi側で行えるようにして実用に耐える使い勝手を実現しようというわけです。

3.2 リモコン信号のフォーマット

まずエアコンのリモコン信号がどのようになっているか簡単に解説します。

リモコン信号のフォーマットはこのページで詳しく解説されています。
日本製のエアコンのフォーマットは

  • NECフォーマット
  • 家製協(AEHA)フォーマット
  • SONYフォーマット

の3種類に分類されます。以下の図はそれぞれのフォーマットでの信号の先頭部分です。
format.jpg

まずはエアコンがどのプロトコルを使用しているかを特定しましょう。
irrp.pyを使って信号を記録します。

 python3 irrp.py -r -g18 -f codes --no-confirm aircon:on

それぞれのオプションの意味は以下の通りです。

オプション 意味
-r  受信した信号を記録するモード
-g18 GPIO18を使って信号を受信する
-f codes 信号を記録するファイルを指定する(ここではcodesを指定)
--no-confirm 確認のために同じ信号を要求しない
aircon:on 信号の識別子
試しに、エアコンを起動する信号を記録してみます。
{"aircon:on": [3260, 1525, 437, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 437, 1168, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 361, 438, 361, 438, 361, 438, 361, 1168, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 438, 361, 1168, 361, 1168, 437, 438, 361, 1168, 361, 1168, 437, 1168, 361, 1168, 437, 1168, 361, 438, 361, 438, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 361, 438, 361, 1168, 437, 1168, 437, 438, 361, 1168, 437, 1168, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 438, 361, 1168, 437, 1168, 361, 1168, 437, 1168, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 437, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 361]}

このような信号が取れればOKです。この数列は赤外線が「ONの時間、OFFの時間、ONの時間……」という意味です。
図に書いてみるとこのようになっています。
airconon.jpg
もうひとつの上の図と照らし合わせてみると $1T \approx 400$程度とすれば$8T \approx 3200$,$4T \approx 1600$となるので、このエアコンは家製協(AEHA)フォーマットを使用しているようです。

3.3 信号を解析する

信号のフォーマットがわかったところで、いよいよ信号のどの部分で何を制御しているか解析していきます。
まず、このままでは長すぎて見にくいので以下のコードで信号を16進数に変換してみます。

decode.py
import json
import os
import argparse

# オプション引数の定義
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file", help="Filename", required=True)
parser.add_argument("-s", "--save", help="saveFilename", type=str)

# オプション引数の取り出し
args = parser.parse_args()

# 指定されたファイルをjson形式で読み込み
with open(args.file, "r") as f:
    rowRecodes = json.load(f)

# ファイルに保存されている全ての信号を16進数に変換する
for key in rowRecodes.keys():
    interval = rowRecodes[key]

    # 信号間隔を読み取って2進数に変換(AEHAフォーマット)
    t = 0.0
    binCode = []
    for i in range(0, len(interval)-1, 2):
        if t * 7.0 < interval[i]:
            t = (interval[i] + interval[i+1]) / 12.0
            binCode.append("")
        elif interval[i+1] < t * 2.0:
            binCode[-1] += "0"
        elif interval[i+1] < t * 6.0:
            binCode[-1] += "1"
    
    # 16進数に変換
    hexCode = ""
    for c in binCode:
        hexCode += format(int(c, 2), "x")
        
    print(key + " : " + hexCode)
    # ファイルがない場合 空のjsonファイルを作成
    if not os.path.isfile(args.save): 
        with open(args.save, "w") as s:
            s.write("{}")

    # ファイルが空の場合 空のjsonファイルを作成
    with open(args.save, "r") as s:
        if len(s.read()) == 0:
            s.write("{}")

    # -s で指定されたファイルに書き込み
    with open(args.save, "r") as s:
        hexRecodes = json.load(s)
    with open(args.save, "w") as s:
        hexRecodes[key] = hexCode
        s.write(json.dumps(hexRecodes))
python3 decode.py -f codes -s codes_hex

それぞれのオプションの意味は以下の通りです。

オプション 意味
-f codes 信号が記録されたファイル(ここではcodesを指定)
-s codes_hex 変換した信号を記録するファイル(ここではcodes_hexを指定)

以下のように変換されていればOKです。

{"aircon:on": "4a75c358a76f90af50ff00b748ef10fd02de21"}

しかしこれだけでは、どこで何を制御しているかわからないのでいくつか信号をとってみて差分を見ることにします。
試しに温度を変える信号をいくつかとってみましょう。
信号の規則はエアコンの機種によって全く異なると思うので、ここからはあくまで一例だと思って読んでいただければ幸いです。

{
    "aircon:cool29": "4a75c358a76f90cf30ff00b748ef10fd02de21",
    "aircon:cool28": "4a75c358a76f902fd0ff00b748ef10fd02de21",
    "aircon:cool27": "4a75c358a76f90af50ff00b748ef10fd02de21",
    "aircon:cool26": "4a75c358a76f906f90ff00b748ef10fd02de21",
    "aircon:cool25": "4a75c358a76f90ef10ff00b748ef10fd02de21",
    "aircon:cool18": "4a75c358a76f907f80ff00b748ef10fd02de21",
}

冷房で29度から25度まで1度ずつ変えて信号を記録してみました。最低温度である18度も記録しています。

{
    "aircon:cool29": "4a75c358a76f90 < cf30 > ff00b748ef10fd02de21",
    "aircon:cool28": "4a75c358a76f90 < 2fd0 > ff00b748ef10fd02de21",
    "aircon:cool27": "4a75c358a76f90 < af50 > ff00b748ef10fd02de21",
    "aircon:cool26": "4a75c358a76f90 < 6f90 > ff00b748ef10fd02de21",
    "aircon:cool25": "4a75c358a76f90 < ef10 > ff00b748ef10fd02de21",
    "aircon:cool18": "4a75c358a76f90 < 7f80 > ff00b748ef10fd02de21",
}

信号列をぐっと睨むと<>で囲んだ部分が変わっていることがわかります。どうやらここが温度を制御している部分のようです。
これだけでは規則性がつかめないので、変わっている部分だけ抜き出して観察してみます。

温度 16進 2進 2進(前後逆転) 16進(前後逆転)
29 cf30 1100 1111 0011 0000 0000 1100 1111 0011 0cf3
28 2fd0 0010 1111 1101 0000 0000 1011 1111 0100 0bf4
27 af50 1010 1111 0101 0000 0000 1010 1111 0101 0af5
26 6f90 0110 1111 1001 0000 0000 1001 1111 0110 09f6
25 ef10 1110 1111 0001 0000 0000 1000 1111 0111 08f7
18 7f80 0111 1111 1000 0000 0000 0001 1111 1110 01fe

ガチャガチャと操作してみると16進(前後逆転)では温度を$T$とすれば

0 ~(32-T) f 32-T

という規則性があることがわかりました。(~はbit反転の意)
運転モードについても同様に解析していきます。温度は20度に固定し運転モードを変えて信号をとってみました。

{
    "aircon:on20":    "4a75c358a7 < 6f90 > 3fc0ff00b748ef10fd02de21",
    "aircon:off20":   "4a75c358a7 < 7f80 > 3fc0ff00b748ef10fd02de21",
    "aircon:auto20":  "4a75c358a7 < ef10 > 3fc0ff00b748ef10fd02de21",
    "aircon:cool20":  "4a75c358a7 < 6f90 > 3fc0ff00b748ef10fd02de21",
    "aircon:dry20":   "4a75c358a7 < af50 > 3fc0ff00b748ef10fd02de21",
    "aircon:clean20": "4a75c358a7 < 2fd0 > 3fc0ff00b748ef10fd02de21",
    "aircon:warm20":  "4a75c358a7 < cf30 > 3fc0ff00b748ef10fd02de21",
}

よく観察すると運転モードを制御する部分の信号は以下の表の温度と同じだということがわかります。

運転モード  温度
warm 29
clean 28
dry 27
cool 26
auto 25
off 18

onの信号はoffにする前の状態の信号を送信しているようです。

3.4 信号を生成する

これらを踏まえて所望の信号を生成するプログラムを書きます。

import os 
import json

base = "4a75c358a76f907f80ff00b748ef10fd02de21"
modeNum = {"off": 18, "auto" : 25, "cool" : 26, "dry" : 27, "clean" : 28, "warm" : 29}
pulse = [[400, 400], [400, 1200]]

def conv_temp_to_hexcode(T):
    """
    設定温度を16進制御コードに変換する

    Parameters
    ----------
    T : int
        エアコンの設定温度

    Returns
    -------
    (signal1, signal2) : tuple(str,str)
        設定温度の16進制御コード
        singal2 は singal1 の補ビット
    """
    bincode = str(bin(32 - T)) # bincode = "0bxxxx"

    # 4bitに整形
    while len(bincode) < 6:
        bincode = bincode[:2] + '0' + bincode[2:]

    signal_1 = bincode[2:6] # bit部だけ取り出す
    signal_1 = (signal_1)[::-1] # reverse
    signal_2 = signal_1.translate(str.maketrans({'0': '1', '1':'0'})) # bit 反転
    signal_1 = format(int(signal_1, 2), 'x')
    signal_2 = format(int(signal_2, 2), 'x')

    return signal_1, signal_2

def conv_mode_to_hexcode(mode):
    """
    エアコンの運転モード(冷房,暖房 etc.)を16進制御コードに変換する

    Parameters
    ----------
    mode : string
        エアコンの運転モード

    Returns
    -------
    signal : str
        運転モードの16進制御コード
    """
    global modeNum
    signal = conv_temp_to_hexcode(modeNum[mode])
    return signal

def gen_code(mode, T):
    """
    エアコンの16進制御コード全体を生成する

    Parameters
    ----------
    mode : string
        エアコンの運転モード
    T : int
        エアコンの設定温度

    Returns
    -------
    hexCode : str
        エアコンの16進制御コード
    """
    global base

    # mode部を書き換え
    tmp = conv_mode_to_hexcode(mode)
    print("mode: " + tmp[0] + "f" + tmp[1] + "0")
    base = base[:10] + tmp[0] + base[11:] # 10文字目をtmp[0]でreplace
    base = base[:12] + tmp[1] + base[13:]

    # 温度部を書き換え
    tmp = conv_temp_to_hexcode(T)
    print("temp: " + tmp[0] + "f" + tmp[1] + "0")
    base = base[:14] + tmp[0] + base[15:]
    base = base[:16] + tmp[1] + base[17:]

    hexCode = base
    print("hexCode: " + hexCode)
    return hexCode

def encode(mode, T):
    """
    エアコンの16進制御コードを赤外線LEDの点灯間隔に変換する

    Parameters
    ----------
    mode : string
        エアコンの運転モード
    T : int
        エアコンの設定温度

    Returns
    -------
    None

    Note 
    -------
    赤外線LEDの点灯間隔は同ディレクトリのairconに書き込まれる
    """
    global pulse

    hexcode = gen_code(mode, T)
    signal = [3200, 1600]

    # 2進変換
    bincode = ""
    for hx in hexcode:
        code = str(bin(int(hx, 16)))
        while len(code) < 6:
            code = code[:2] + '0' + code[2:]
        code = code[2:6]
        bincode += code
    
    # 2進 -> 点灯間隔
    sig = [pulse[int(bit)][i] for bit in bincode for i in range(0,2)]
    signal.extend(sig)
    signal.append(400)
    #print(signal)

    # ファイルがない場合 空のjsonファイルを作成
    if not os.path.isfile("aircon"): 
        with open("aircon", "w") as s:
            s.write("{}")
    # ファイルが空の場合 空のjsonファイルを作成
    with open("aircon", "r") as s:
        if len(s.read()) == 0:
            s.write("{}")
    # -s で指定されたファイルに書き込み
    with open("aircon", "r") as s:
        Recode = json.load(s)
    with open("aircon", "w") as s:
        Recode["aircon:op"] = signal
        s.write(json.dumps(Recode))

if __name__ == '__main__':
    mode = input()
    T    = int(input())

    encode(mode, T)

書きました。

python3 encode.py
input
cool
27
output
mode: 6f90
temp: af50
hexCode: 4a75c358a76f90af50ff00b748ef10fd02de21

動かしてみてこのように出力されていればOKです。元の信号間隔の状態のコードが同ディレクトリのairconというファイルに出力されているはずです。

aircon
{"aircon:op": [3260, 1525, 437, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 437, 1168, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 361, 438, 361, 438, 361, 438, 361, 1168, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 438, 361, 1168, 361, 1168, 437, 438, 361, 1168, 361, 1168, 437, 1168, 361, 1168, 437, 1168, 361, 438, 361, 438, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 438, 361, 1168, 437, 438, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 361, 438, 361, 1168, 437, 1168, 437, 438, 361, 1168, 437, 1168, 361, 1168, 437, 438, 361, 1168, 437, 438, 361, 438, 361, 1168, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 438, 361, 1168, 437, 1168, 361, 1168, 437, 1168, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 1168, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 1168, 437, 1168, 437, 438, 361, 1168, 437, 1168, 437, 1168, 437, 1168, 437, 438, 361, 438, 361, 438, 361, 1168, 437, 438, 361, 438, 361, 438, 361, 438, 361, 1168, 361]}

あとはお好みで風向きや風量を制御する部分を解析して同じようにエンコードしてみてください。
コードはすべてこちらのリポジトリにあげてあります。

参考

4. Discord Botを作る

4.1 Botアカウントの作成

メッセージを監視するためのDiscord Botを作成します。
まずはDiscordのサーバーを作成します。
Discordで サーバーの追加 > オリジナルの作成 > 自分と友達のため > 新規作成 で新しいサーバーを作成します。名前はなんでもいいです。
discord_newserver.png
次にDiscord Developers Portalへアクセスし、BOTのアカウントを作成します。Developers PortalからログインするとBrowser版のホームに飛ばされますが、再度アクセスすればDeveloper Portalへアクセスできます。
Portalへアクセスできたら Applications > New Application > Createで新しいApplicationを作成します。
test.png
ここではApplicationの名前をremocon_testとしました。Bot > Add Bot > Yes, do it!でApplicationをBotとして登録します。
(testのような多く使われている名前だとBotの名前として使用できないことがあるようです)

4.2 Botアカウントの設定

Bot登録が完了すると、Botの設定画面が現れるはずです。PUBLIC BOTはOFFにしておきましょう。
public.png
TOKENをコピーしてBotを動かしてみます。
OAuth2のOAuth2 URL GeneratorでBotの権限を設定します。
bot_setting.png
SCOPESはbot、BOT PERMISSIONSはView Channels, SendMessagesを選択します。これはメッセージの読み取りと、送信の権限です。
生成されたURLをコピーし、アドレスバーにペーストします。
bot_in.png
するとこのようなページに飛ぶのでBotを追加したサーバーを選択してはいを押します。
bot_check.png
権限はこの2つが設定されていればOKです。認証を押して次に進みます。
Botの認証が完了すれば、選択したサーバーにBotが参加しているはずです。これでBotの設定は完了です。

参考

4.3 Botのテスト

Botのプログラムを組んでテストしてみます。

discord_test.py
import discord

TOKEN = "Paste Your TOKEN"
client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "ON":
        await message.channel.send("message is ON")
    elif message.content == "OFF":
        await message.channel.send("message is OFF")

client.run(TOKEN)

TOKENは自分のBotのTOKENに置き換えてください。TOKENはBotのページからコピーできます。
discordはプリインストールされていないので

pip3 install discord

でインストールしてください。

token.png

python3 discord_test.py

でプログラムを実行すればBotがオンラインになるはずです。
試しにONと送信してみましょう。
dousa.png
Botからメッセージが返ってきました!これでBotのテストは完了です。

4.4 メッセージに対応した信号を送信する

subprocessをつかってirrp.pyを呼び出し、信号を送信します。

server.py
import discord
import subprocess
import encode

TOKEN = "Paste Your TOKEN"
client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "ON":
        encode.encode('cool', 27)
        subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./aircon", "aircon:op"])
        await message.channel.send("message is ON")
    elif message.content == "OFF":
        encode.encode('off', 27)
        subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./aircon", "aircon:op"])
        await message.channel.send("message is OFF")

client.run(TOKEN)

これでエアコンのON,OFFができればOKです。

5. エアコンの状態をラズパイ側で管理する

ここまででエアコンの単純なON,OFFはできるようになりました。ここからはエアコンの状態を管理できるようにします。
SQLiteをインストールしDBを作成します。

sudo apt install sqlite
sqlite3 db.sqlite3

テーブルを作成し、初期データを入れておきます。

sqlite> create table user_data(aircon_mode, aircon_temp);
sqlite> insert into user_data values('cool', 27);
sqlite> select * from user_data;
off|27
sqlite> .exit

サーバ側からSQLiteにアクセスしてデータをやりとりできるようにします。

server.py
import discord
import subprocess
import sqlite3
import encode

connect = sqlite3.connect('db.sqlite3')
cursor  = connect.cursor()

TOKEN = "Paste Your TOKEN"
client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "ON":
        cursor.execute("SELECT aircon_mode, aircon_temp FROM user_data")
        state = cursor.fetchall()[0]
        mode = state[0]
        temp = state[1]
        encode.encode(mode, temp)
        cursor.execute("UPDATE user_data SET aircon_mode = ?", (mode,))
        cursor.execute("UPDATE user_data SET aircon_temp = ?", (temp,))
        subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./aircon", "aircon:op"])
        await message.channel.send("message is ON")
    elif message.content == "OFF":
        cursor.execute("SELECT aircon_temp FROM user_data")
        state = cursor.fetchall()[0]
        temp = state[0]
        encode.encode('off', temp)
        cursor.execute("UPDATE user_data SET aircon_mode = ?", ('off',))
        subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./aircon", "aircon:op"])
        await message.channel.send("message is OFF")

client.run(TOKEN)

これでラズパイ側でエアコンの状態を管理して利用できるようになりました。

6. メッセージに対応した信号を送信する

正規表現を使ってひらすらパターンを列挙していきます。正規表現についてはこちらを参照してください。

データベースの更新、エアコンの状態の取得、irrp.pyの呼び出しは関数にしてまとめました。
Discord BotのTOKENもデータベースで管理するようにしました。
ついでにライトの制御もできるようになっています

import discord
import re 
import subprocess
import sqlite3
import encode

connect = sqlite3.connect('db.sqlite3')
cursor  = connect.cursor()

cursor.execute("SELECT token from token")
TOKEN=str(cursor.fetchall()[0][0])
client = discord.Client()

def dbUpdate(column, state):
    """
    データベースの内容を更新する

    Parameters
    ----------
    column : str
        変更するカラム
    state : int or str
        セットする値
    """
    if column == 'light':
        cursor.execute("UPDATE user_data SET light = ?", (state,))
    elif column == 'aircon_temp':
        cursor.execute("UPDATE user_data SET aircon_temp = ?", (state,))
    elif column == 'aircon_mode':
        cursor.execute("UPDATE user_data SET aircon_mode = ?", (state,))

    connect.commit()

def getState():
    """
    エアコンの運転モードと設定温度を取得する

    Returns
    ----------
    state : list[str, int]
        エアコンの運転モードと設定温度
    """
    cursor.execute("SELECT aircon_mode, aircon_temp FROM user_data")
    return list(cursor.fetchall()[0])

def lightON():
    proc = subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./codes", "light:on"])

def lightOFF():
    proc = subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./codes", "light:off"])

def airconOP(state):
    encode.encode(state[0], state[1])
    proc = subprocess.run(["python3", "./irrp.py", "-p", "-g17", "-f", "./aircon", "aircon:op"])

@client.event
async def on_message(message):
    message.content = str(message.content).replace(' ', '')

    if not re.match(r'\$', message.content) is None:
        pass
    elif not re.search(r'電気\S*つけて', message.content) is None:
        lightON()
        dbUpdate('light', 1)
    elif not re.search(r'電気\S*消して', message.content) is None:
        lightOFF()
        dbUpdate('light', 0)
    elif not re.search(r'エアコン\S*つけて', message.content) is None:
        airconOP(['off', 27])
        state = getState()
        airconOP(state)
        dbUpdate('aircon_mode', state[0])
    elif not re.search(r'エアコン\S*消して', message.content) is None:
        temp = getState()[1]
        airconOP(['off', temp])
        dbUpdate('aircon_mode', 'off')
    elif not re.search(r'エアコン\S*冷房', message.content) is None:
        airconOP(['cool', 27])
        dbUpdate('aircon_mode', 'cool')
        dbUpdate('aircon_temp', 27)
    elif not re.search(r'エアコン\S*暖房', message.content) is None:
        airconOP(['warm', 20])
        dbUpdate('aircon_mode', 'warm')
        dbUpdate('aircon_temp', 20)
    elif not re.search(r'温度\S*下げて', message.content) is None:
        state = getState()
        state[1] -= 1
        airconOP(state)
        dbUpdate('aircon_temp', state[1])
    elif not re.search(r'温度\S*上げて', message.content) is None:
        state = getState()
        state[1] += 1
        airconOP(state)
        dbUpdate('aircon_temp', state[1])
    elif not re.match(r'now', message.content) is None:
        cursor.execute("SELECT * FROM user_data")
        await message.channel.send(str(cursor.fetchall()))

client.run(TOKEN)

コードはすべてこちらのリポジトリにあげてあります。

おまけ. Google Assistantと連携してボイスコントロールする

IFTTTを利用してGoogle AssistantとDiscordを連携させます。

sysytem_c.jpg

まず、IFTTTにアクセスして、メールアドレスを登録します。
Createでアプリの作成画面に移ります。
create.png

Addを押してトリガーとなるアプリを選択します。Google Assistantを選択しましょう。

choose.png

ここではSay a phrase with a text ingredientを選択します。
Assistant.png

Google Assistantを利用しているGoogleアカウントと連携します。
connet.png

呼び出すためのキーフレーズを設定します。ここではリモコンに設定しました。$はテキスト変数です。
つまりリモコン エアコンつけてとGoogle Assistantに言うとリモコンつけての部分がDiscordに送信されます。
set.png
Create triggerを押してトリガーを作成します。
Then ThatのAddを押して連携するサービスを選択します。ここではWebhooksを選択しましょう。

webhooks.png

action fieldは以下のように設定してください。URLの部分は後述するDiscordのwebhookURLです。
Webhooksettings.png

DiscordのWebhookURLを生成します。サーバー設定 > 連携サービス > ウェブフックの作成で新しくWebhookURLを作成できます。
webhoos.png

名前は何でもいいです。ウェブフックURLをコピーを押すとURLがクリップボードにコピーされるので、先程のURL欄にペーストしてください。
Web.png

Createを押したらGoogle Assistantとの連携は完了です。
レスポンスがとても遅い(5分〜)ことがあります。テストしたい場合はAdvanced REST clientなどを利用してリクエストを送ってみてください

参考

52
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?