序論
夏を前にして、暑がりの妻が言いました。夜寝るとき、エアコンを付けないと暑いし付けっぱなしだと寒いし、タイマーで温度を上げたい。
これに対して、僕としては夜は温度の設定が間違っているだけで、エアコンとは「長期的に快適な温度」を設定すべきと思いましたが、Twitterにもありますように、顧客の意図を「正しく」組みとらなければなりません。
「ドリルを買いに来た人が欲しいのはドリルではなく穴である」ではなく、ドリルを買うことで今後自分の意志で好きな時に好きなだけ穴が空け放題になる自由を買ってる💪💪💪
— izm (@izm) 2018年6月4日
つまり、「暑い寒いを解決すること」ではなく、「暑い寒いを自分の支配下におく」ことがここでの顧客の要望であると考えました。
そこで、ラズパイを使ってこれを実現することを目指しました。
ラズパイではLircや、ArduinoのIRRemotというライブラリがあります。
これらを使って、タイマーで指定時刻にリモコンのキーをエミュレーションして送信すれば実現できると考えられます。
qiita.com
IR Remoteの例
asukiaaa.blogspot.com
しかし、うちのエアコンが悪いのか、プログラムが悪いのか、動作させることはできませんでした。
上手くいかない原因としては、基本周波数が違うとどう頑張ってもダメなことがあるようです。
特にうちで今回ターゲットにしたPanasonicの製品は基本周波数が細かく、厳しいのではないかといううわさがあります。
ちなみにArduinoのIR Remoteではキーコードを一つ入れるとRAMがいっぱいで、つまり一つのキーしか入れられませんでした(Arduino Uno)。
やろうと思っている人は注意です。
そこで、これらのライブラリではなく、動作するリモコンをラズパイでフィジカルハック(物理的ハック)することにしました。
リモコンの回路解析とリード線の作成
制御イメージはこのような感じです。スマホからSlackを通して、ラズパイに指示を送り、GPIOを制御してリモコンを作動させます。
リモコンは安く売っているこちらをAmazonで購入しました。
Hanwha / K-1028E 国内外 合計1000種類のエアコンコードを内蔵した エアコン用 ユニバーサル マルチリモコン [詳細日本語マニュアル付][エアコン 汎用 リモコン]
- 出版社/メーカー: ハンファQセルズジャパン株式会社
- メディア:
- 購入: 5人 クリック: 52回
- この商品を含むブログを見る
パターンを目視とテスターで調べ、ボタンは導電性のゴムで導通させることでONされることが分かります。
ちょうどはんだ付けできどうな点があるので、ここにリード線を付けます。
リード線は作業中や運用中に、変に力がかかってしまうと、はんだごとパターンが取れてしまう可能性があるので、ホットボンドで固めておきます。
回路作成
【図1a】がリモコンの等価回路です。押しボタンの裏には導電性のゴムが付いていて、これにより回路が繋がるとスイッチが動作します。これを、電気的に再現するためにトランジスタで回路を作成しました。【図1b】
しかしこれでは、Pin6とPin7がショートしてしまうようでボタンは動作しません。
そこで、トランジスタのエミッタ側に抵抗を入れることで、リモコンのPin同士は完全では無いにしても絶縁をとり、しかもトランジスタが動作するように構成しました。
これで、Pin6とPin7のあいだは460kΩで接続していることになりますが、リモコン的には絶縁のようにふるまいます。
(抵抗値は結構大きくしてもトランジスタは動作するので手持ちから適当に選定)
試験的にテスト用電源(ただの単三電池を2本直列につないだもの)でBaseに電圧を印加するとリモコンを動作させることができました。
230kΩの抵抗は電池で動作させるときには1kΩでもうまく動いたのですがラズパイでやってみると上手くいかなかったのでできるだけ大きい抵抗にしてみました。
結果的にこれはエミッタフォロワーとなるのかな。
ラズパイで制御
これをラズパイのGPIOから制御できるか確認します。TeratermからGPIOを制御して動作確認します。
コマンドラインからGPIOを操作する - IT父さんのロボブログ
#GPIOを出力に設定 $ gpio -g mode 23 out $ gpio -g mode 24 out $ gpio -g mode 25 out
#GPIO をHIGH $ gpio -g write 25 1
#GPIO をLOW $ gpio -g write 25 0
これで電源スイッチが作動すればOKです。
※ここまで上手くいったかなと思ったのですが、どうしてもラズパイで動かそうとするとリモコンがキー設定モードに入ってしまう現象が発生。制御するボタンを一つにすれば上手くいくのですが、何かいらないところでショートしているのか?仕方ないので制御するボタンを一つにして運用します。もしくはトランジスターではなくフォトカプラにすべきか?
プログラミング
qiita.comこちらを参考に、slackに話しかけて制御します。
ファイルは
slackbot_settings.py
→SlackのAPIキーなどの設定、SlackBotPlugin.py
→話しかけたときの応答内容、bot.py
→Mainのプログラム(これを実行する)airconSet.py
→GPIOの制御、を作成します。SlackBotPlugin.py
には↓のようなキーワードを設定しました。
キーワード | 動作 |
上 up | 温度を上げる設定を追加 |
下 down | 温度を下げる設定を追加 |
リセット 削除 | タイマー設定を削除 |
スタート | タイマースタート |
ヘルプ | これらの制御キーを表示 |
#SlackBotPlugin.py # -*- coding: utf-8 -*- from slackbot.bot import respond_to, listen_to import re import time from datetime import datetime as dt import airconSet @respond_to(u'(上|up|下|down)+') def OrderUpDown(message, something): try: # someting = 反応したword text=message.body['text'] text=text.replace(something,"") #起動ワードを削除 timeSet = re.search('[0-9]{4}', text) timeSet=timeSet.group(0) hh=timeSet[:2] mm=timeSet[2:] hh=int(hh) mm=int(mm) if hh>24: raise ValueError("error!") if mm>59: mm=59 text = re.sub('[0-9]{4}',"", text) tempertureSet =re.search('[0-9]{1}',text) tempertureSet=tempertureSet.group(0) # 命令を出したユーザ名を取得 userID = message.channel._client.users[message.body['user']][u'name'] tdatetime = dt.now() if something=="up" or something=="上": airconSet.set(hh,mm,tempertureSet,"up") message.reply("時刻" + timeSet + "に" + tempertureSet + "℃上げるようにセットしました。") elif something=="down" or something=="下": airconSet.set(hh,mm,tempertureSet,"down") message.reply("時刻" + timeSet + "に" + tempertureSet + "℃下げるようにセットしました。") print(tdatetime, "accept oder from ", userID, ) except Exception as e: print(e) message.reply("タイマーセットの入力書式は hhmm temp up/down です") @respond_to(u'(reset|リセット|削除|delete)+') def timerReset(message, something): try: text = message.body['text'] airconSet.timer_remove() message.reply("タイマー設定を削除しました。") except Exception as e: print(e) message.reply("指示を解釈できませんでした。") @respond_to(u'(start|スタート)+') def timerStart(message, something): try: text = message.body['text'] message.reply("タイマースタートしました。") airconSet.timer() except Exception as e: print(e) message.reply("指示を解釈できませんでした。") @respond_to(u'(help|ヘルプ|助け)+') def help(message, something): try: message.reply("タイマーセット→ hhmm temp up/down、 リセット、スタート") except Exception as e: print(e) message.reply("指示を解釈できませんでした。")
GPIOの制御はwiringpiを使いました。
wiringpiはラズパイにはデフォルトでインストールされていますが、wiringpi2をインストールする必要があります。
$ sudo pip3 install wiringpi2
wiringpi2をインストールしないと、import wiringpi
でErrorがでてしまいます。
以前の記事には(自分で)記載していないがそうだったのかな?試行錯誤している間にインストールしたことを忘れたのかもしれないし、ラズビアンのVersionによっても違うのかもしれません。
s51517765.hatenadiary.jp
トランジスタに繋がったGPIO 3つをOUTPUTモードにし、タクトスイッチをINPUT PULLUPになったGPIOに接続します。
プルアップはラズパイの内部プルアップを使います。プルアップされたタクトスイッチをONすると電圧がLowになります。
このほかに、動作設定をslackから呼ばれた時にスペース区切りでテキストファイルに設定を記録します。
#airconSet.py # -*- coding: utf-8 -*- import wiringpi import time import re from datetime import datetime as dt # OUTPUT PIN_UP = 23 # BCM No PIN_DOWN = 24 PIN_POWER = 25 PIN_RESET = 15 # GPIO初期化 wiringpi.wiringPiSetupGpio() # GPIOを入力モード(0)/出力モード(1)に設定 wiringpi.pinMode(PIN_UP, 1) wiringpi.pinMode(PIN_DOWN, 1) wiringpi.pinMode(PIN_POWER, 1) wiringpi.digitalWrite(PIN_RESET, 0) # 出力をLow wiringpi.digitalWrite(PIN_UP, 0) wiringpi.digitalWrite(PIN_DOWN, 0) wiringpi.digitalWrite(PIN_POWER, 0) #ボタン押下時間 wait=5 onWait=0.3 print("Aircon set import!") def timer(): print("タイマースタート") file = open('airconTimer.txt', 'r', encoding='utf') text="up" try: while "up" in text or "down" in text: text = file.readline() splitText = re.split(" ", text) if "up" in text: key = 1 elif "down" in text: key = -1 else: #キーワードがなければ break hh = int(splitText[0]) mm = int(splitText[1]) tempertureSet = int(splitText[2]) print("up or down =", str(key), "timer=", str(hh), str(mm), "temp=", str(tempertureSet)) except Exception as e: print(e) file.close() while (wiringpi.digitalRead(PIN_RESET)==1): #プルアップしているのでスイッチ押下でLOW time.sleep(0.5) tdatetime = dt.now() HH = int(tdatetime.strftime('%H')) # 時刻 MM = int(tdatetime.strftime('%M')) if HH==hh and MM==mm: if key==1: aircon_temp_set_up(tempertureSet) elif key==-1: aircon_temp_set_down(tempertureSet) print("タイマーストップ") def timer_remove(): file = open('airconTimer.txt', 'w', encoding='utf') file.close() def set(hh,mm ,number,updown): print("Aircon order!") print("time= ",hh,mm) print("set= ", number) file = open('airconTimer.txt', 'a', encoding='utf') # 追記モードでオープン file.write(str(hh)+" "+str(mm)+" "+str(number)+" "+updown+"\n") file.close() def aircon_power(): print("Aircon power!") wiringpi.digitalWrite(PIN_POWER, 1) time.sleep(onWait) wiringpi.digitalWrite(PIN_POWER, 0) time.sleep(wait) def aircon_temp_set_up(count): tdatetime = dt.now() print(tdatetime) print("aircon_temp_set_up") for i in range(count): wiringpi.digitalWrite(PIN_UP, 1) time.sleep(onWait) wiringpi.digitalWrite(PIN_UP, 0) time.sleep(wait) print("temp set complete!") time.sleep(60) def aircon_temp_set_down(count): tdatetime = dt.now() print(tdatetime) print("aircon_temp_set_down") for i in range(count): wiringpi.digitalWrite(PIN_DOWN, 1) time.sleep(onWait) wiringpi.digitalWrite(PIN_DOWN, 0) time.sleep(wait) print("temp set complete!") time.sleep(60) def initialize_GPIO(): # GPIO初期化 wiringpi.wiringPiSetupGpio() # GPIOを入力モード(0)/出力モード(1)に設定 wiringpi.pinMode(PIN_UP, 1) wiringpi.pinMode(PIN_DOWN, 1) wiringpi.pinMode(PIN_POWER, 1) wiringpi.digitalWrite(PIN_RESET, 0) #出力をLow wiringpi.digitalWrite(PIN_UP,0) wiringpi.digitalWrite(PIN_DOWN, 0) wiringpi.digitalWrite(PIN_POWER, 0)
完成
部品リスト
部品 | 数量 |
NPNトランジスタ | 3 |
抵抗 5k | 2 |
抵抗230k | 2 |
ボタン電池ボックス | 1 |
ピンヘッダメス | 5穴 |
ジャンパワイヤ | 5 |
リード線 | 少々 |
ユニバーサル基板 | 1 |
10pcs シングル40ピン オス+メスストレートタイプ ピンヘッダー PCB用 2.54 mm
- 出版社/メーカー: ジェネリック
- メディア: Tools & Hardware
- この商品を含むブログを見る
- 出版社/メーカー: 東芝
- メディア:
- この商品を含むブログを見る
uxcell 電池ボックス 電池ホルダー ボタン電池 ボタン電池対応 3V CR2032/ CR2025 10個入り
- 出版社/メーカー: uxcell
- メディア: その他
- この商品を含むブログを見る
EasyWordMall 10個 PCB 5×7cm ユニバーサル基板 実験プレート
- 出版社/メーカー: Apple Trees E-commerce co., LT
- メディア:
- この商品を含むブログを見る
Raspberry Pi Zero W Starter Kit
- 出版社/メーカー: ケイエスワイ
- メディア: エレクトロニクス
- この商品を含むブログ (1件) を見る