ESP32で家のエアコンをスマート化する(受信編)
きっかけ
普段は所謂JTCでエンジニアをしているのですが、古い技術をずっと使っていたり、士気の低さだったりで色々モチベーションが持たなくなってきたので、久しぶりに自分の興味あることを素直にやってみることにしました。
思い返せば新卒で入社して3年ですが、段々と心が萎れてきていて、、、
修士のときは機械学習を用いて数値データを扱っていたのでマイコンを触るのは学部生以来ですが、とても新鮮で楽しいです。
せっかくなのでアウトプットついでに記事にしてみることにします。
目的
最終目標はESP32を用いて「家のエアコンに対して、外から停止信号を送信すること」です。
この記事ではその前段として、リモコンが送っている停止信号を解析することをゴールにします。
エアコンはダイキンのF364ATASです。
用意したもの
全部秋月電子で揃えました。
- ESP32
- ブレッドボード
- ジャンパーワイヤとか
- 赤外線受信モジュール(OSRB38C9AA)
- 赤色LED(Lチカ用なので何でもいい)
- 赤外線LED(OSI5LA5113A)
- 抵抗
Arduino IDEのインストール
公式からAppImageを落としてきて実行権を与えます。なんかエラーが出たので、ggrと--no-sandbox
をつければ良いとのこと。
こんな感じでコマンドを実行しています。
/home/arduino-ide_2.3.3_Linux_64bit.AppImage --no-sandbox
信号の解析
送る信号がわからなければ送ることができないので、まずは信号を解析します。
さまざまな方の記事を参考にさせていただきました(リンクは後ろに載せます)。
信号のフォーマットはこちらとこちらを参考にしました。
マクロのT
は変調単位で、うまく解析できるようにちょっとずつ変えていきました。
まずは割り込みをして、信号を受信できるようにする部分です。
ここらへんも普段Arduinoを触っている人からしたら当たり前なのかもしれませんが、久しぶりだったのでドキドキしながら書いていました。
#define MAX_LEN 800
#define PIN 13
#define T 410
#define SMALL_T 330
volatile unsigned int buf[MAX_LEN];
volatile unsigned int x = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
attachInterrupt(PIN, handler, CHANGE);
}
void handler() {
if (x > MAX_LEN) return;
buf[x++] = micros();
}
次に繰り返し部分です。
5秒ごとに評価して、信号が保存されていれば解析処理をします。
本当はノイズ除去とかもするべき7日もしれませんが、今回はなくても期待通り動いてしまいました。
また、試行錯誤してわかったことですが、このエアコンは信号を2フレーム送ります。
同じものを送っているだけと早とちりして無視していたのですが、実際には違う信号でした。
私のエアコンは信号2フレーム分を1つの命令として解釈するようです。
void loop() {
// put your main code here, to run repeatedly:
delay(5000);
if (x > 0) {
detachInterrupt(PIN);
////////////
for (int i = 1; i < x; i++) {
buf[i - 1] = buf[i] - buf[i - 1];
}
/////////////
debug_array(buf, x, true);
/////////////
// 9000以上の信号はリピートと解釈する。
// 開始位置を決定する
int start = 0;
for (int i = 0; i < x - 1; i++) { // 最後は長いから無視しないとうまく行かない
if (buf[i] < 9000) { //一番最初に出てきたそれっぽい信号を取得する。
start = i;
break;
}
}
///////////////
// 時間をT何個分か換算
int length = x - start;
int count_T[length];
for (int i = start; i < x; i++) {
int n = get_n(buf[i]);
count_T[i - start] = n;
}
count_T[length - 1] = 3;
debug_array(count_T, length, true);
/////////////
// Tをbitに直す
/////////////
int bit_length = length / 2 - 1; // リーダコードの8, 4の分を考慮する。2で割るから-1
int bit[bit_length];
for (int i = 2; i < length; i = i + 2) {
if (count_T[i] > 10) continue;
if (count_T[i] == 1 && count_T[i + 1] == 1) {
bit[i / 2 - 1] = 0;
} else if (count_T[i] == 1 && count_T[i + 1] == 3) {
bit[i / 2 - 1] = 1;
} else {
bit[i / 2 - 1] = 9;
}
}
debug_array(bit, bit_length, false);
//////////////
// 見やすさのために、bitを16進数に直す
//////////////
for (int i = 0; i < bit_length - 1; i = i + 4) {
int val = 0;
if (bit[i] == 9) {
Serial.print("\n");
i = i - 3;
continue;
}
for (int j = i + 3; j >= i; j--) {
val = val << 1;
if (bit[j] == 1) val += 1;
}
Serial.print(val, HEX);
}
Serial.print("\n");
//////////////
x = 0;
}
}
// T何個分か計算する関数
int get_n(int t) {
int n = 0;
for (int i = t; i > 0; i = i - T) {
if (i > SMALL_T) {
n++;
}
}
return n;
}
void handler() {
if (x > MAX_LEN) return;
buf[x++] = micros();
}
// 配列を出力するだけ
void debug_array(volatile unsigned int arr[], int x, bool line) {
for (int i = 0; i < x; i++) {
Serial.print(arr[i]);
if (line) Serial.print("\n");
}
Serial.print("\n=======================\n");
}
void debug_array(int arr[], int x, bool line) {
for (int i = 0; i < x; i++) {
Serial.print(arr[i]);
if (line) Serial.print("\n");
}
Serial.print("\n=======================\n");
}
結果
binの出力
10019
25054
3524
1641
526
1241
489
383
48
~~~
~~~
count_Tの出力
余計な部分は捨てているので、binの出力の最初2行は消えています。
8, 4ときているので、家製協フォーマットとわかる(もちろんわかっててそういうふうにしているが)。
8
4
1
3
1
1
1
~~~
~~~
bitの出力
これは受信した順で出力しているが、C_0, C_1, ..., C_7となっているので、下位bitが先にprintされています。
1000100001011011...991000100001011...
HEX出力
最後にbitを16進数にして出力した。
なんか気分でC_0, C_1, C_2, C_3をC_3, C_2, C_1 ,C_0に直している。だから、
C_3, C2_, C_1 ,C_0, C_7, C_6, C_5 ,C_4, の順のbitをHEXに直した出力ということになる。
どうせならちゃんとC_7, C_6, C_5 ,C_4, C_3, C2_, C_1 ,C_0に直せばよかった。
2フレーム送るけど、フレーム長は違うっぽいですね〜
11AD720020E70C000420280813B02D4F034200CE
11AD720000842300070000600600A07C004275
このデータが何を表しているのかについてはまた今度...
回路図
回路図ってこういうときどう書けばいいんだろう...
ツールとかもわからず、テキトーになってしまいました。
コード全部
#define MAX_LEN 800
#define PIN 13
#define T 410
#define SMALL_T 330
volatile unsigned int buf[MAX_LEN];
volatile unsigned int x = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
attachInterrupt(PIN, handler, CHANGE);
}
void loop() {
// put your main code here, to run repeatedly:
delay(5000);
if (x > 0) {
detachInterrupt(PIN);
////////////
for (int i = 1; i < x; i++) {
buf[i - 1] = buf[i] - buf[i - 1];
}
/////////////
debug_array(buf, x, true);
/////////////
// 9000以上の信号はリピートと解釈する。
// 開始位置を決定する
int start = 0;
for (int i = 0; i < x - 1; i++) { // 最後は長いから無視しないとうまく行かない
if (buf[i] < 9000) {
start = i;
break;
}
}
///////////////
int length = x - start;
int count_T[length];
for (int i = start; i < x; i++) {
int n = get_n(buf[i]);
count_T[i - start] = n;
}
count_T[length - 1] = 3;
debug_array(count_T, length, true);
/////////////
// count_T to bit
/////////////
int bit_length = length / 2 - 1; // リーダコードの8, 4の分を考慮する。2で割るから-1
int bit[bit_length];
for (int i = 2; i < length; i = i + 2) {
if (count_T[i] > 10) continue;
if (count_T[i] == 1 && count_T[i + 1] == 1) {
bit[i / 2 - 1] = 0;
} else if (count_T[i] == 1 && count_T[i + 1] == 3) {
bit[i / 2 - 1] = 1;
} else {
bit[i / 2 - 1] = 9;
}
}
debug_array(bit, bit_length, false);
//////////////
for (int i = 0; i < bit_length - 1; i = i + 4) {
int val = 0;
if (bit[i] == 9) {
Serial.print("\n");
i = i - 3;
continue;
}
for (int j = i + 3; j >= i; j--) {
val = val << 1;
if (bit[j] == 1) val += 1;
}
Serial.print(val, HEX);
}
Serial.print("\n");
//////////////
x = 0;
}
}
int get_n(int t) {
int n = 0;
for (int i = t; i > 0; i = i - T) {
if (i > SMALL_T) {
n++;
}
}
return n;
}
void handler() {
//Serial.print("handler\n");
if (x > MAX_LEN) return;
buf[x++] = micros();
}
void debug_array(volatile unsigned int arr[], int x, bool line) {
for (int i = 0; i < x; i++) {
Serial.print(arr[i]);
if (line) Serial.print("\n");
}
Serial.print("\n=======================\n");
}
void debug_array(int arr[], int x, bool line) {
for (int i = 0; i < x; i++) {
Serial.print(arr[i]);
if (line) Serial.print("\n");
}
Serial.print("\n=======================\n");
}
Discussion