いきさつ
今住んでるマンションのエアコンはオフタイマーとオンタイマーを同時に設定できなく、夜オフタイマーをつけると、朝は寒い中起きてこなくちゃいけない。去年買ったRaspberry Piが使われずに転がってたので、これを使って問題を解決してみることにした。
Raspberry Piでリモコン赤外線を送受信する方法は、
- 赤外線LEDと受信モジュールを使って自前で回路を実装してGPIOで通信する
- USB接続の赤外線リモコンキットを買ってくる
の2通りが考えられるけど、半田ごてが見当たらなかったので、キットを買ってくることにした。ネットで調べるとUSB接続 赤外線リモコンキット(株式会社ビット・トレード・ワン)、irMagician(大宮技研 合同会社)辺りが、Raspberry Piでの実績がすでにあるらしい。irMagicianの方がメモリ容量も大きいので長い信号に対応してそうな上、サイズも小さいので、irMagicianを使うことにした。
インターネットからRaspberry Piに直接アクセスできるようにするにはルーターのポートを開けたりする必要があり、面倒なので、今回はFirebase Realtime Databaseを使って通信することにした。また、自分以外の人にエアコンを操作されたくないので、Firebase Authenticationを使ってGoogleアカウントで認証するようにした。
購入
品物 | 値段(円) |
---|---|
irMagician | 3,980 |
Raspberry Pi 3 | 5,800 |
Raspberry Pi 3 ケース | 1,280 |
ACアダプタ 5V2.5A | 1,100 |
USBケーブル 1.5m | 120 |
マイクロSDカード16GB | 900 |
irMagicianはITプラザで完成品を購入。(はんだ付けに自信があれば1,980円のキットのほうがお得。)それ以外は秋月電子で購入。Raspberry Pi 2 Model Bをすでに持ってるけど、Raspberry Pi 3の方がWi-FiとBluetooth(今回は使わない)を内蔵してるし、せっかくなので、と思い衝動買い。
これ以外に、マイクロSD->SDカードアダプタ、ACアダプタからの給電用USBケーブル、USBキーボード、HDMIケーブルとディスプレー(テレビで可)が別途必要。
セットアップ
Macで起動用SDカードの作成
公式ページから最新のRASPBIAN JESSIE LITEをダウンロード。(12月4日現在2016-11-25版、2016-11-25-raspbian-jessie-lite.zipが最新)
念のためSHA-1 ダイジェストがダウンロードページに書いてあるSHA-1と一致してるのを確認して、解凍。
$ openssl sha1 2016-11-25-raspbian-jessie-lite.zip
SHA1(2016-11-25-raspbian-jessie-lite.zip)= 6741a30d674d39246302a791f1b7b2b0c50ef9b7
$ unzip 2016-11-25-raspbian-jessie-lite.zip
Archive: 2016-11-25-raspbian-jessie-lite.zip
inflating: 2016-11-25-raspbian-jessie-lite.img
SDカードをMacに挿す前と後でdiskutil listを比較して、増えた/dev/diskN
を確認。(私の環境では/dev/disk2
。) diskutil unmountDisk
でディスクをアンマウントし、dd
コマンドでディスクイメージをSDカードに書き込む。dd
コマンドはかなり時間がかかる。Ctrl+T
で何バイト書き込まれたか確認できる。
$ diskutil unmountDisk /dev/disk2
Unmount of all volumes on disk2 was successful
$ sudo dd if=2016-11-25-raspbian-jessie-lite.img of=/dev/disk2 bs=1m
Raspberry Piの初期設定
Raspberry Piをケースに入れて、SDカードを挿して、HDMIケーブルでテレビに繋いで、USBキーボードを挿して、USBアダプタから電源を供給すると初回起動が始まるので、ログイン画面まできたら、ID: pi
、Password: raspberry
でログインし、sudo raspi-config
で初期設定。
-
2 Change User Password
でパスワードの変更 -
4 Internationalisation Options
→I2 Change Timezone
→Asia
→Tokyo
-
4 Internationalisation Options
→I3 Change Keyboard Layout
→Generic 105-key (Intel) PC
→Other
→Japanese
→Japanese
→The default for the keyboard layout
→No compose key
-
4 Internationalisation Options
→I4 Change Wi-fi Country
→JP Japan
-
7 Advanced Options
→A4 SSH
→Yes
sudo shutdown -r now
で再起動し、sudo vi /etc/wpa_supplicant/wpa_supplicant.conf
でWi-fiの設定。
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid="Wi-fiのSSID"
psk="Wi-fiのパスワード"
key_mgmt=WPA-PSK
}
そして、sudo shutdown -r now
で再起動。画面にMy IP address is 192.168.0.10 ...
とIPアドレスが出てくるのでメモってMacからssh pi@192.168.0.10
でログインできることを確認。
最後に、sudo apt-get update
とsudo apt-get dist-upgrade
でパッケージの更新をして初期設定は終了。
irMagicianの動作確認
Raspberry PiとirMagicianをUSBで接続し、dmesg
コマンドでttyACM0として認識されたのを確認する。
$ dmesg
[ 0.000000] Booting Linux on physical CPU 0x0
...
[ 275.569696] usb 1-1.4: new full-speed USB device number 4 using dwc_otg
[ 275.692440] usb 1-1.4: New USB device found, idVendor=04d8, idProduct=000a
[ 275.692461] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 275.692475] usb 1-1.4: Product: The Ultimate irController - irMagician
[ 275.692487] usb 1-1.4: Manufacturer: Microchip Technology Inc.
[ 275.692500] usb 1-1.4: SerialNumber: 0123
[ 275.714912] cdc_acm 1-1.4:1.0: ttyACM0: USB ACM device
[ 275.715694] usbcore: registered new interface driver cdc_acm
[ 275.715706] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
動作確認のために使うscreen
コマンドをsudo apt-get install screen
でインストールし、screen /dev/ttyACM0 9600
で、irMagicianと通信開始。v
キーを押して、Enterキーを押すと、画面に
irMagician 1.0.1
OK
と表示されていたらirMagicianと通信は成功。
次は信号の学習の動作確認。c
キーを押して、Enterキーを押すと、画面に...
と表示されるので、テレビリモコンのボタンを押してirMagicianのセンサに赤外線を当てる。... 整数
という表示になれば成功。... Time Out !
と表示されたら失敗。
最後に、信号の再生の動作確認。irMagicianをテレビに向けて、p
キーを押して、Enterキーを押すテレビの電源が切れるor入れば成功。Ctrl+a k
でscreenを終了。
irMagicianと通信するPythonプログラム
開発元の大宮技研のサイトで、irMagicianと通信するPythonプログラム例が公開されている。このプログラムはpyserialというパッケージを使っているので、まず、pipをインストールしてから、pyserialをインストールする。
$ sudo apt-get install python-pip
$ sudo pip install pyserial
cap.py
、play.py
、saveIrMagician.py
、playIrMagicianLocal.py
をダウンロード。
$ mkdir ~/irm
$ cd ~/irm
$ wget http://www.omiya-giken.com/irMagician/python/linux/cap.py
$ wget http://www.omiya-giken.com/irMagician/python/linux/play.py
$ wget http://www.omiya-giken.com/irMagician/python/linux/saveIrMagician.py
$ wget http://www.omiya-giken.com/irMagician/python/linux/playIrMagicianLocal.py
cap.py
で赤外線信号を学習し、play.py
で再生が成功していたら、saveIrMagician.py filename
で信号情報をファイルに保存、playIrMagicianLocal.py filename
でファイルから信号情報を読み取って送信できる。
今回は適当な温度設定でエアコンをオンにする信号情報on.json
と、オフにする信号情報off.json
を作成しておく。
#Firebaseの登録
Firebase Consoleにログインし、「新規プロジェクトを作成」をクリック。
適当なプロジェクト名を入力して、「プロジェクトを作成」をクリック。
ギアのアイコンをクリックし、「プロジェクトの設定」をクリックし、「プロジェクト ID」をメモっておく。
左のメニューの「Authentication」をクリックし、「ログイン方法」、「Google」を選択し、「有効にする」にチェックを入れ、保存をクリック。
「ウェブ設定」をクリックし、configの中身をメモっておく。
// Initialize Firebase
var config = {
apiKey: "XXXXXXXXXXXXXXXX-XXXXXXXXXXXX",
authDomain: "プロジェクト ID.firebaseapp.com",
databaseURL: "https://プロジェクト ID.firebaseio.com",
storageBucket: "プロジェクト ID.appspot.com",
messagingSenderId: "0123456789"
};
firebase.initializeApp(config);
Firebase コマンドラインツール
手元のパソコン/MacにFirebase コマンドラインツールをインストールしてコマンドでFirebaseをいじれるようにする。(手元のパソコン/MacにもNodeJSをインストールしておく必要がある。)
$ npm install -g firebase-tools
Firebaseの設定するためのディレクトリを作成し、firebase
コマンドで設定する。
$ mkdir ~/firebase_prj
$ cd ~/firebase_prj
$ firebase login
? Allow Firebase to collect anonymous CLI usage information? (Y/n) # これはお好みで選択
Visit this URL on any device to log in:
https://accounts.google.com/o/oauth2/auth?client_id=......
# 表示されたURLをブラウザでアクセスし、先程Firebaseに登録したGoogleアカウントでログインする。
Waiting for authentication...
✔ Success! Logged in as あなたのGooogleアカウントアドレス
$ firebase init
# 中略
? What Firebase CLI features do you want to setup for this folder? (Press <space> to select)
❯◉ Database: Deploy Firebase Realtime Database Rules
◉ Hosting: Configure and deploy Firebase Hosting sites
# 両方選択した状態で、Enter
=== Project Setup
First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.
? What Firebase project do you want to associate as default?
# 先ほど作成したプロジェクトを選択し、Enter
=== Database Setup
Firebase Realtime Database Rules allow you to define how your data should be
structured and when your data can be read from and written to.
? What file should be used for Database Rules? (database.rules.json)
# Enter
=== Hosting Setup
Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.
? What do you want to use as your public directory? (public)
# Enter
? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)
# Enter
✔ Wrote public/404.html
✔ Wrote public/index.html
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
✔ Firebase initialization complete!
$ ls
database.rules.json firebase.json public
database.rules.jsonを下記のように変更。
{
"rules": {
".read": true,
".write": "auth.provider == 'google' && auth.token.email == '自分のGoogleアカウントアドレス@gmail.com'"
}
}
public/index.htmlを下記のように変更
<script src="https://www.gstatic.com/firebasejs/3.6.1/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: "XXXXXXXXXXXXXXXX-XXXXXXXXXXXX",
authDomain: "プロジェクト ID.firebaseapp.com",
databaseURL: "https://プロジェクト ID.firebaseio.com",
storageBucket: "プロジェクト ID.appspot.com",
messagingSenderId: "0000000000"
};
firebase.initializeApp(config);
function loginOrLogout() {
if (firebase.auth().currentUser) {
// Logout
firebase.auth().signOut().then(_ => console.log('Logout: OK '),
_ => console.log('Logout: Error'));
} else {
// Login
var provider = new firebase.auth.GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/plus.login');
firebase.auth().signInWithRedirect(provider);
}
}
function sendEnabled(value) {
firebase.database().ref('aircon')
.set({value: value, timestamp: firebase.database.ServerValue.TIMESTAMP})
.then(_ => console.log('sendEnabled: OK'),
_ => console.log('sendEnabled: Error'));
}
function sendTimer() {
firebase.database().ref('timer')
.set({
on: {
enabled: document.getElementById('onCheck').checked,
time: document.getElementById('onTime').value
},
off: {
enabled: document.getElementById('offCheck').checked,
time: document.getElementById('offTime').value
},
timestamp: firebase.database.ServerValue.TIMESTAMP
})
.then(_ => console.log('sendTimer: OK'),
_ => console.log('sendTimer: Error'));
}
function initialize() {
firebase.auth().onAuthStateChanged(user => {
document.getElementById('loginOrLogout').value = user ? 'Logout' : 'Login';
});
firebase.database().ref('/')
.on('value', snapshot => {
var timer = snapshot.val().timer;
if (!timer)
return;
if (timer.on) {
if (timer.on.enabled)
document.getElementById('onCheck').checked = timer.on.enabled;
if (timer.on.time)
document.getElementById('onTime').value = timer.on.time;
}
if (timer.off) {
if (timer.off.enabled)
document.getElementById('offCheck').checked = timer.off.enabled;
if (timer.off.time)
document.getElementById('offTime').value = timer.off.time;
}
});
}
window.addEventListener('load', initialize);
</script>
<input type="button" id='loginOrLogout' onclick="loginOrLogout()" value="">
<div>
<input type="button" onclick="sendEnabled(true)" value="On">
<input type="button" onclick="sendEnabled(false)" value="Off">
<input type="button" onclick="sendTimer()" value="Set Timer">
</div>
<div>
<label><input type="checkbox" id="onCheck">On: </label>
<input type="time" id="onTime">
</div>
<div>
<label><input type="checkbox" id="offCheck">Off: </label>
<input type="time" id="offTime">
</div>
そして、 firebase deploy
でデプロイ。
$ firebase deploy
これで、 https://プロジェクトID.firebaseapp.com で遠隔操作画面にアクセスできる。
Raspberry PiにNodeJSをインストール
$ sudo apt-get install git
$ git clone https://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ nvm ls-remote # Latest LTSを確認
$ nvm install v6.9.1 # 上で確認したLatest LTSを選ぶ
Raspberry Piからfirebaseに繋いでリモコンを操作するプログラム
$ mkdir ~/prj
$ cd ~/prj
$ npm init
$ npm install --save firebase
下記のようなaircon.jsをprjディレクトリに作成する。
var firebase = require("firebase");
var config = { databaseURL: "https://プロジェクトID.firebaseio.com" };
firebase.initializeApp(config);
var execSync = require('child_process').execSync;
var AIRCON_ON_COMMAND =
'python /home/pi/irm/playIrMagicianLocal.py /home/pi/irm/on.json';
var AIRCON_OFF_COMMAND =
'python /home/pi/irm/playIrMagicianLocal.py /home/pi/irm/off.json';
firebase.database().ref('/aircon').on('value', function(snapshot) {
console.log('/aircon: ' + JSON.stringify(snapshot.val()));
if (snapshot.val().value) {
console.log('aircon on');
console.log(execSync(AIRCON_ON_COMMAND).toString());
} else {
console.log('aircon off');
console.log(execSync(AIRCON_OFF_COMMAND).toString());
}
});
var onTimerInfo = {enabled: false};
var offTimerInfo = {enabled: false};
function fireOnTimer() {
console.log('fireOnTimer');
console.log(execSync(AIRCON_ON_COMMAND).toString());
onTimerInfo.timeoutID =
setTimeout(_ => { fireOnTimer(); },
getDiffTimeMiliSec(onTimerInfo.time, new Date()));
}
function fireOffTimer() {
console.log('fireOffTimer');
console.log(execSync(AIRCON_OFF_COMMAND).toString());
offTimerInfo.timeoutID =
setTimeout(_ => { fireOffTimer(); },
getDiffTimeMiliSec(offTimerInfo.time, new Date()));
}
function getDiffTimeMiliSec(timeString, nowDate) {
var time = (parseInt(timeString.substring(0, 2)) * 60 +
parseInt(timeString.substring(3, 5))) * 60;
var nowTime = (nowDate.getHours() * 60 + nowDate.getMinutes()) * 60 +
nowDate.getSeconds();
if (time > nowTime)
return (time - nowTime) * 1000;
return (time + 24 * 60 * 60 - nowTime) * 1000;
}
firebase.database().ref('/timer').on('value', function(snapshot) {
console.log('/timer: ' + JSON.stringify(snapshot.val()));
var timer = snapshot.val();
if (!timer)
return;
if (onTimerInfo.enabled) {
clearTimeout(onTimerInfo.timeoutID);
onTimerInfo.enabled = false;
}
if (offTimerInfo.enabled) {
clearTimeout(offTimerInfo.timeoutID);
offTimerInfo.enabled = false;
}
if (timer.on && timer.on.enabled && timer.on.time) {
onTimerInfo.enabled = true;
onTimerInfo.time = timer.on.time;
onTimerInfo.timeoutID =
setTimeout(_ => { fireOnTimer(); },
getDiffTimeMiliSec(onTimerInfo.time, new Date()));
}
if (timer.off && timer.off.enabled && timer.off.time) {
offTimerInfo.enabled = true;
offTimerInfo.time = timer.off.time;
offTimerInfo.timeoutID =
setTimeout(_ => { fireOffTimer(); },
getDiffTimeMiliSec(offTimerInfo.time, new Date()));
}
});
あとは、node aircon.js
で起動
$ node aircon.js