はじめに
自宅のエアコンをLINEから操作するようなシステムを作っていきます.
記事が長くなりそうなので
- ラズパイからエアコンを操作できるようにする
- LINEからメッセージを送ってエアコンを操作できるようにする
の2つに分けます.適宜必要な箇所を参照していただけると幸いです.
この記事では,1のラズパイzeroを使ったエアコン制御関係の回路やプログラミングについて説明します.
2のLINE APIを使ったネットワーク上のインフラ構築に関してはこちらの記事です.
環境はWindows10, WSLで行います.
完成形
こんな感じでLINEからエアコンを操作します(クリックするとyoutubeに飛びます).
システムのフロー図を次に示します.
操作はLINEから行うことができるので,出先のスマホからや研究室のPCなどから操作できます.
LINE APIからはWebhookが飛ばされ,それをHeroku上に立てたwebサーバがキャッチし,mqttを通してラズパイに知らせます.
ラズパイでは,赤外LEDを制御し,あらかじめ記録しておいたエアコンの信号を再生することでエアコンを制御するという形になっています.
LINEの画面はこのようになっていて,リッチメニューを実装し,タップするだけで制御できます.
また,エアコンが点灯したかどうかを確認できるようになっています.
本編の概要
本編は,
- ラズパイzeroのセットアップ
- 受信回路構成とプログラム
- 送信回路構成とプログラム
- 電源が点いたこと確認するシステム
- 付録
という構成になっています.
ラズパイのセットアップに関しては,ネット上に多くの資料が存在するので,本エントリでは自分がおすすめするサイトのURLや必要なソフトウェアの紹介程度に収めたいと思います.
受信構成では赤外線受光素子を用いた回路の作成方法や,付録として5V->3.3Vにレベルシフトする回路を用いた赤外センサ周辺回路について解説します.
送信構成ではLEDへの入力電流を決める抵抗の値の計算方法や,サブキャリアを作成するためのラズパイでのPWMの制御方法について解説します.
また,3年ほど前に同じようなシステムを作ったのですが,その時にエアコンが点いたかどうかわからないということが問題点として上げられました.なので今回はリードスイッチを用いてエアコンの開閉も判断できるようにしました.その構成について解説します.
Raspberry Pi Zeroのセットアップ
[](ラズパイの初期セットアップについては簡単にだけ説明しますので各々お願いします(こちらに関しては多くの記事があるため).
一応自分が参考にしたサイトです.)
ラズパイの初期セットアップに関しては,自分が参考にした記事やちょっとした説明で済ませようと思います.
ラズパイzeroにキーボードやディスプレイをつけて開発するのはストレスフルだと思うので,最低限SSHで接続してコマンドを叩けるようにはしておきましょう.
以下,自分が参考にしたURLです.
USB OTGを使ったRapsberry Pi Zero WH のセットアップ(SSHの接続設定もココ)
Raspberry Pi Zero Wに固定IPアドレスを設定する
ラズパイでやらなければいけない4つのセキュリティ対策!
今回はデスクトップ環境を必要としないのでRaspbian Buster Liteをインストールしました.
軽いのでミラーサイトからダウンロードしなくてもいけると思います.
USB-OTGは初期設定を行う場合にとても重宝しますが,Wi-Fiへの接続を設定したあとはほとんど使わないです.
USB-OTGを使用する場合はデバイスのドライバの更新が必要です.上記サイトを参考にして行ってください.
セキュリティ対策に関して,外部に公開するサーバーではありませんが最低限パスワードの変更くらいは行っておきましょう.
必要なソフトウェアのインストール
はじめにパッケージの更新を行います.インストール作業を行う際は以下のコマンドを実行してから行ったほうがいいです.
$ sudo apt update & apt upgrade
C/C++コンパイラのgccをインストールします(Raspbianにはデフォルトで入っているようです).
$ sudo apt install build-essential
FTPサーバをインストールします.
設定ファイルに関してはここを参考にしました.
$ sudo apt install vsftpd
FTPサーバに関しては必須ではありませんが,sshで接続して直接ファイルを編集したりscpでファイルをアップロードをするのは面倒くさいので,別PCを用意し,そこでデータを保持&(自動)アップロードすることにします.
FTPの同期はVSCodeのSFTPという拡張で行います.保存するたびに自動でアップロードしくてれるのでめちゃくちゃ便利です.以下は参考URLです.
さよならFTPツール。VS Codeからファイルを即サーバー同期して作業効率アップ
次にプログラムからGPIOを操作するwiringpi
をインストールします.
$ sudo apt-get install wiringpi
[](
- WiringPiのインストール
- http://wiringpi.com/download-and-install/
- ソースからビルド
)
赤外線信号送受信回路と周辺プログラム
使用した部品
今回は部品を全部秋月電子通商さんで購入できるもので作りました.
抵抗の種類が多いですが大事なところは赤外線LEDへ流入する電流量を決めるところの抵抗くらいであとは大体同じくらいの値を使用すれば問題ないです.
- Raspberry Pi Zero WH
- [受信側] 赤外線リモコン受信モジュールOSRB38C9AA(2個入)
- [受信側] セラミックコンデンサー 0.1μF50V
- [送信側] 5mm赤外LED L12170
- [送信側] トランジスタ 2SC2120-Y
- [送信側] アルミ電解コンデンサー 100μF16V
- カーボン抵抗1/4W 10kΩ
- カーボン抵抗1/4W 1kΩ
- カーボン抵抗1/4W 100Ω
- カーボン抵抗1/4W 20Ω
- ミニブレッドボード(白)
受信回路
まずはリモコンの信号を受信し記録する部分です.
赤外線センサのデータシートに次のような周辺回路の例が載っていたので参考にします.
こちらをブレッドボード上で作成すると次のようになります.ラズパイのGPIOは3.3V系なので,センサの電源電圧は3.3Vに接続しました.C1の47uF~100uFはおそらく電源安定化のためだと思うので今回はノイズ除去用に気持ちだけ0.1uFのセラコンをつけておきます.他R1は10kΩ,R2は100Ωをつけました.
実際に組むとこんな感じです.写真のミニブレッドボードは手軽で便利なのでおすすめです.
受信プログラム
受信プログラムを次に示します.
クリックして展開
#include <iostream>
#include <fstream>
#include <wiringPi.h>
#define DEFAULT_USING_PIN (0)
#define DEFAULT_MEASURE_TIME (2000000)
#define BUFFER_SIZE (100000)
#define ON_STATE (0)
#define OFF_STATE (!ON_STATE)
#define LED_PIN (14)
using namespace std;
unsigned int buffer[BUFFER_SIZE][2];
int main(int argc,char *argv[]){
// WiringPiの初期化処理
if (wiringPiSetup() == -1) {
cout << "cannot setup gpio." << endl;
return 1;
}
// コマンドライン引数受け取り
// scan (出力ファイルパス)
string save_file_name;
if(argc >= 2){
save_file_name = argv[1];
}else{
cout << "Input save file name!" << endl;
return 1;
}
// 測定時間の設定
unsigned int measure_time = DEFAULT_MEASURE_TIME;
if(argc >= 3)
measure_time = atoi(argv[2]);
// センサの入力ピンの設定
int use_pin = DEFAULT_USING_PIN;
if(argc >= 4)
use_pin = atoi(argv[3]);
// ピンアサインの設定
pinMode(use_pin, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, 1);
cout << "*** Measure time:" << measure_time << "(us), Use Pin:"<< use_pin <<". Ready ***" << endl;
delay(1000);
cout << "3" << endl;
delay(1000);
cout << "2" << endl;
delay(1000);
cout << "1" << endl;
delay(1000);
cout << "*** Start ***" << endl;
// 測定処理
unsigned int start_time = micros();
unsigned int prev_time = micros();
int prev_state = OFF_STATE;
int id = 1;
while(start_time + measure_time > micros()) {
int current_state = digitalRead(use_pin); // 現在の状態を取得
if(current_state != prev_state){ // 以前の状態から変化がある時を記録
unsigned int current_time = micros();
buffer[id/2][id%2] = current_time - prev_time;
prev_time = current_time;
prev_state = current_state;
id++;
}
}
// デジタル変換
auto is_valid_signal =[](int us, int T){
const int typical_unit_us = 425; // 家製協フォーマット(NECフォーマットの場合は562)
const int threshold = 50; // 適当
return ((typical_unit_us-threshold)*T <= us) && (us <= (typical_unit_us+threshold)*T);
};
bool detect_start_frame = false;
string write_data;
for (int i = 0; i < BUFFER_SIZE; i++)
{
// 先頭が検出されてからはこっちに入る
if(detect_start_frame){
if(!is_valid_signal(buffer[i][0], 1)) break; // 終了判定
write_data += is_valid_signal(buffer[i][1], 3) ? '1' : '0'; // 書き込みデータ生成
continue;
}
// 先頭検出(家製協フォーマット)
// NECフォーマットの場合は8と4のところをそれぞれ16と8にする.
if(is_valid_signal(buffer[i][0], 8) && is_valid_signal(buffer[i][1], 4)){
detect_start_frame = true;
}
}
if(!detect_start_frame){
// Leader frameが検出できなかった場合
cout << "*** Scan Error!!! ***" << endl;
return 1;
}
// ファイル書き込み
ofstream ofs(save_file_name);
ofs << write_data << endl;
cout << "*** Write Success! ***" << endl;
digitalWrite(LED_PIN, 0);
}
このプログラムではコマンドライン引数から出力ファイルパスと測定時間(デフォルト:2秒)と使用するGPIOのピン番号(デフォルト:0)を受け取り,まず,ONの時間とOFFの時間をバッファに格納します.
その後,赤外線リモコンのフォーマットを参考にして,リーダー部分の検出,信号のデジタル値への変換を行います.ちなみにうちのエアコンは家製協フォーマットでした.
フォーマットに関しては付録Aに詳しく記載するのでそちらのご確認もお願いします.
プログラムを適当な場所にコピーして下記のコマンドでコンパイルします(今回はscan.cpp
という名前にしました).
$ g++ scan.cpp -o scan -lwiringPi
すると,scan
という実行ファイルが作成されるのでコマンドライン引数で出力ファイルパスをつけて実行します.
実行例が以下のgifアニメーションになります.
3,2,1とカウントした後,2秒間測定を開始します.測定秒数もコマンドライン引数で変えることができます.START
の合図が出たら赤外センサにエアコンのリモコンを向けてスイッチを押します.
うまく行けば上記のようにSUCCESS
とでて赤外線のデータをデジタル値で取得することが出来ます.失敗する(信号のleader部分が検出できない)とERROR
を出すようになっています.ERROR
が出てしまった場合は,フォーマットが間違っている可能性があります.コメントアウト下部分を参考にNECフォーマットを試してみてください.
例えば自分の環境でエアコンのSTOP信号を受信すると次のようなデータが取得できます.
10000000000010000000000000000010111111011111111100000000001100111100110001001001101101101100100000110111000001101111100100000000111111110000000011111111000000001111111100000000111111110000000011111111011010101001010110000111011110000000000011111111000000001111111100000001111111101100000000111111100000000111111100010001111011100000000011111111000000001111111111111111000000001111111100000000111111110000000011111111000000000
この結果を送信プログラムに渡してエアコンを制御します.
送信回路
次に送信回路について説明します.まず回路図はこのようになっています.
ir_led_on
の信号でNPNトランジスタをスイッチングし,LEDのオン・オフを制御します.
LEDの入力である5Vのところには電圧降下対策の電解コンデンサをつけています(USBから給電する際はこの電解コンデンサが必須となります).送受信回路の章で説明しますが,自分の環境では2A出力のACアダプタを使用しているのでおそらく大丈夫です.
LEDの電流制限抵抗の値は20Ωに設定していますが,この値については付録Bにて解説します.
この回路をブレッドボード上で構成すると次のようになります.ir_led_on
に接続するピンはPWMに対応したピンを選択する必要があります.今回はGPIO26(PWM0)に接続しました.
送信プログラム
送信プログラムを次に示します.
クリックして展開
#include <iostream>
#include <fstream>
#include <wiringPi.h>
#define LED_PIN (14)
using namespace std;
int main(int argc,char *argv[]){
if (wiringPiSetup() == -1) {
cout << "cannot setup gpio." << endl;
return 1;
}
//コマンドライン引数受け取り
//send (信号データ) (回数[default:3])
string target_file_name;
int try_num = 3;
if(argc >= 2){
target_file_name = argv[1];
if(argc >= 3){
sscanf(argv[2], "%d", &try_num);
}
}else{
cout << "Input target file name!" << endl;
return 1;
}
// ピンアサインの設定
pinMode(26, PWM_OUTPUT);
pinMode(LED_PIN, OUTPUT);
// PWMの設定
pwmSetMode(PWM_MODE_MS);
pwmSetClock(5);
pwmSetRange(101);
pwmWrite(26, 0);
// バッファ読み込み
ifstream ifs(target_file_name);
if (!ifs)
{
cout << "ファイルが開けませんでした。" << endl;
cin.get();
return 0;
}
// 信号データ読み込み
string send_data;
ifs >> send_data;
cout << "[send.cpp] Start IR send " << try_num << " times. msg =>" << target_file_name << endl;
//送信処理
auto send_signal = [](bool high_low, int T){
const int typical_unit_us = 425; // 家製協フォーマット(NECフォーマットの場合は562)
pwmWrite(26, high_low ? 34 : 0);
delayMicroseconds(typical_unit_us*T);
};
for (size_t i = 0; i < try_num; i++) //try_num回繰り返す
{
digitalWrite(LED_PIN, 1);
// leader部出力(家製協フォーマット)
// NECフォーマットの場合は8と4のところをそれぞれ16と8にする.
send_signal(true, 8); send_signal(false, 4);
//データ出力
for(char s : send_data){
send_signal(true, 1);
if(s=='1') send_signal(false, 3);
else send_signal(false, 1);
}
digitalWrite(LED_PIN, 0);
delay(130);
}
}
wiringPiのPWMの設定に関してはこちらのサイトを参考にしました.
pwm frequency=\frac{19.2 [MHz]}{clock\times range}
この式のclock
とrange
を設定します.それぞれの役割については参考サイトを参照してください.簡単に言うとrange
でPWM周期の分解能を決めて,clock
でPWM周期を決める,という形になっています.
PWM周期は付録Aより,38kHzなので,
\frac{19.2 [MHz]}{0.038 [MHz]} = 505.2631...
つまり$clock\times range$の部分が505になるように設定する必要があります.今回は
clock = 5 \\
range = 101
と設定しました.また,デューティ比が3分の1ということで,pwmWrite
関数に与える引数をclock(101)の3分の1である34としています.
送信処理については,付録Aを参考にし,リーダー部の出力を行い,次に01に応じて信号を出力する部分で構成されます.また,変数try_num
で送信回数(デフォルト3回)を指定し,130msecのインターバルを置いて再送信を行うようになっています.
それではプログラムを適当な場所にコピーして下記のコマンドでコンパイルします(今回はsend.cpp
という名前にしました).
$ g++ send.cpp -o send -lwiringPi
実行します.GPIOををPWMモードで動かす場合はsudo
をつけないといけないらしいです.
すると,LEDから3回信号が出力され,エアコンの動作音が聞こえたら成功です.
一応波形を見てみます.ir_led_on
の信号をオシロで確認してみます.
次の図では付録Aの1T
に当たる部分の時間を測定しています.図では491.8usとなっていてフォーマットの範囲内にきちんと収まっています(測定が微妙で少し広い範囲をとってしまいました).
次に3T
にあたる部分を測定します.すると1366usとなっており,理論値が1275us(425x3)なので概ね正しい値となっています.
最後にサブキャリアに当たる部分を拡大して測定します.すると,周波数が38.030kHz,デューティ比が33.53%(約3分の1)と良い感じの数字がでています.
送受信回路
以上の回路を秋月のRaspberry Pi ZERO用ユニバーサル基板の上に実装しました(受信回路に関しては付録C参照).
受信回路も実装しましたが,一度信号を記録してしまえばあとは送信しか行わないので,送信回路のみを実装してもいいと思います.
電源に関してはUSBからの供給ではなく,ACアダプタを使用することにしました.赤外LEDで一瞬ではありますが大きな電流を消費するので安定動作を目的としています.使用した部品は以下です.
電源が点いたこと確認するシステム
非接触でON-OFFを切り替えられるリードスイッチを使用します.リードスイッチとは2本の磁性体を用いることで,磁石を近づけた時に接点が動き導通するというセンサになります.秋月に3種類ほど品揃えがありますが,次のドアセンサースイッチというものがモジュール化されてて使いやすかったです.
送受信回路の部分にLEDとセンサをはんだ付けして動作チェックしてみます.
このように近づけたり遠ざけたりする動作を検知することができます.
エアコンへの設置はこのように一方をエアコンの羽につけることで電源がついたときに離れていくようにしました.
次にこのセンサを使ってエアコンが動作したかを確認するプログラムを次に示します.
クリックして展開
#include <iostream>
#include <fstream>
#include <wiringPi.h>
#include <chrono>
#define SW_PIN (12)
#define DEFAULT_WAIT_TIME (10)
using namespace std;
int main(int argc,char *argv[]){
if (wiringPiSetup() == -1) {
cout << "cannot setup gpio." << endl;
return 1;
}
// コマンドライン引数の受け取り
// switching_verify (タイムアウト(sec)[default:DEFAULT_WAIT_TIME])
int timeout = DEFAULT_WAIT_TIME;
if(argc >= 2){
sscanf(argv[1], "%d", &timeout);
}
// ピンアサインの設定
pinMode(SW_PIN, INPUT);
// 初期状態で開いてる場合エラーを返す
if(digitalRead(SW_PIN)){
cout << "[switchin_verify.cpp] Already turn on." << endl;
return 1;
}
// 時間計測関係
auto start = chrono::system_clock::now();
auto get_elapsed = [start]{
auto end = chrono::system_clock::now();
return (int)chrono::duration_cast<std::chrono::seconds>(end - start).count();
};
// エアコンの開閉の確認
// 開いた場合正常終了(ステータスコード:0)
// 開かなかった場合エラー(ステータスコード:1)
while(get_elapsed() < timeout){
if(digitalRead(SW_PIN)){
cout << "[switchin_verify.cpp] Aircon turned on!" << endl;
return 0;
}
}
cout << "[switchin_verify.cpp] Failed." << endl;
return 1;
}
こちらのプログラムは次のソフトウェア構成の記事で使用します.一応ビルドして動作確認を行います.
$ g++ switching_verify.cpp -o switching_verify -lwiringPi
このようにプログラムを実行している最中にエアコンの電源を入れてみましょう.すると電源がついたことを検知することが出来ました.デフォルトのタイムアウトは10秒です.短い場合は実行時にコマンドライン引数に秒数を渡すことで変えることが出来ます.
他に考えられるシステム
このドアセンサを用いたシステムを構築したあとに思いついたんですが,音センサを使ってもいいかなと考えました
(うちのエアコンではリモコンからの入力があるたびにピッ!と音がなるため).
こちらのほうがドアセンサを用いるものと比べて正確性はわかりませんが,反応速度は格段に良くなると思います.
現在だと記事冒頭の映像からもわかりますがエアコンが起動してから数秒待たないといけない&停止信号を受信したかを確認できないなど問題があります.
こちらのシステムも試してみたいです.
最後に
いかがでしたでしょうか.回路に関しては慣れていないとデバッグの難しいところがあると思いますが,なにかあればコメントをお願いします.できる範囲で対応したいと思います.
次はLINEから操作するシステムを作るので,そちらもぜひよろしくお願いします.
付録A.赤外線リモコンの家製協(AEHA)フォーマット
ここでは赤外線通信のフォーマットについて解説します.NECフォーマット,家製協フォーマット,SONYフォーマットなどが代表的なフォーマットとして挙げられるそうです.自分の部屋のエアコンはHITACHIの白くまくんで家製協フォーマットでした.
NECと家製協についてはリーダー部とT(変調単位時間)の違いで他のパラメータは互換性があります.SONYフォーマットに関しては,~~SONY製のエアコンってあるんですかね?(調べても出てこなかったからわからない).~~今回は扱いません.必要な方は,サブキャリア周波数とリーダー部のところを変更すればいいのでこの資料等をもとに頑張ってください.
今回は家製協フォーマットについてのみ見ていきます(うちのエアコンが家製協フォーマットだったので).
次の図は参考URL先から持ってきたものです.
この図がとてもわかりやすいです.リーダー部があり,その後,信号の0と1に従って,0の場合は,ONを1T時間,OFFも1T時間にします.1の場合は,ONを1T,OFFを3Tにします.この時のTはNECフォーマットが562us,家製協がtyp.425usとなっています.
また,赤外線は身の回りのものから放射されているため,それらと区別する必要があります.そこで一般の赤外線リモコンではキャリア周波数による変調を行っています.下の図のように38kHzの1/3dutyをサブキャリアとした変調波を送信します.
実際に時間をはかってGPIOをONOFFをしてもいいんですが,せっかく機能があるので今回はPWMを採用しました.
おそらくPWMの方がハードウェア依存なので正確だと思います.
付録B.電流制限抵抗の値
ここでは赤外LEDに流入する電流を決める抵抗の値について解説します.
使用する赤外LEDの絶対定格がデータシートより300mAなので,目標は150mA~200mAとします.
今回のシステムにおいて,赤外LEDに流れる電流を制御する要素は以下の3つです.
- 電源電圧
- LEDの順方向電圧
- トランジスタのコレクタ-エミッタ間電圧
3に関しては,気をつけなければいけないことはコレクタ-エミッタ間飽和電圧とそれ決めるベース-エミッタ間電流です.今回はLEDに流れる電流を150mA~200mAと大きくとる予定なのでほぼ0と考えても問題有りません(正確にデータシートのグラフから読み取ると70mVくらい).なので今回は無視します.
次にLEDにかかる順方向電圧ですが,こちらもデータシートの次のグラフより,大体1.5Vと置きます.
電源電圧とLEDの順方向電圧降下の値がわかったので,次に電流制限抵抗の値をオームの法則より計算します(175mAを流す想定で行きます).
$$
\frac{5.0 [V]- 1.5 [V]}{0.175 [A]} = 20 [Ω]
$$
以上より,電流制限抵抗の値を20Ωと決める事ができました.
付録C.5v->3vレベルシフタを用いたセンサ回路
初期の頃,赤外線センサは5v駆動と勘違いしていた(3.3vでやったら上手く動かなかった?)ため,赤外線センサの5vの信号出力を3.3vにレベルシフトする回路を考えていました.こちらでも全然動作しますがメリットがあまりありません.実際に作成した送受信回路でこちらを使用したので付録として載せます.
ブレッドボード上での回路はこのようになります.
複雑なようですが,重要な部分を回路図に書くとこのようになります.
この回路は2つの部分に別れていて,それぞれ赤外線センサ(赤外線受信モジュール)の周辺回路とそのモジュールの出力信号(5v)を3.3vにするレベルシフタ回路で構成されています.
赤外線センサのデータシートに周辺回路が載っていたので参考にしました.GNDに接続されているコンデンサがデータシートの方では47uF~100uFと書かれていますが,こっちでは0.1uFになっています.おそらく電源供給の安定化のためのコンデンサだと思われますが,今回はパスコン(0.1uF)の方だけ入れました.実際パスコンがなくても動きますが,予期せぬ不具合などの原因になるのでセンサやICの周辺にはつけておいた方が良いです.
レベルシフタ回路では5vの出力信号をラズパイ用に3.3vにレベルシフトしています.ラズベリーパイはGPIOが3.3v系なので,5vを加えてしまうと壊れます(正確には壊れても文句言えなくなってしまいます).なので,PNP型トランジスタをかませてコレクタ出力をラズパイに繋ぎます.ベースが0vのときにir_outが3.3v,ベースが5vのときにir_outが0vとなり,信号は反転しますがレベルシフタとして機能しています.
信号が反転してしまうため,先程のプログラムの先頭のDefine部分のON_STATE
を1
にしてあげる必要があります.