お盆中は1週間ほどお休みをいただきゆっくり過ごすことができました。
お休みを利用して家の中の赤外線リモコンを解析してスマホで操作できるようにしたり、お風呂の自動ボタンを外出先で操作できるようにしてみました。
使ったマイコンはESP32です。こちらを使えば何でもできます。
可視光より波長が少し長いだけの赤外線に、どんな情報が乗っているのかと不思議だったので調べるためにESP32を使ってたら、いろいろできるようになりました。
まずは赤外線リモコンをスマート化
もともと+styleというスマートマルチリモコンを使ってテレビ、照明、BD/DVDプレイヤー、扇風機などをスマホで操作したり、アレクサで音声操作で使えるようにはなっていました。ただエアコンだけは+style内の準備されたエアコンメーカーと型番がピタっと合わないと正しい信号が出ているのか分からない状態だったので使うのを控えていました。エアコン側に液晶画面があって現在の運転モードを確認できればわかりやすいのですが、ないので仕方ありません。
そこでSEP32を使ってリモコンから送信される信号をデータとして確認できるものを作りました。
これでできることは受けとった赤外線信号をパソコンにバックアップできて、リモコンが壊れたり紛失しても送信機に移植すれば復活できるメリットがあります。またバックアップしたデータをもとにして学習リモコンに信号を登録させたり、マイコン送信機を作って+styleのようにスマホで操作や、アレクサを介して音声での操作も可能となります。
まず赤外線信号をバックアップするために必要なものは以下のとおり。
①ESP32-S3
②受信機VS1838Bと送信LED
③抵抗
④ブレッドボードとジャンパー線、ジャンパーピン
⑤結局いらなかったけど買ってしまったロジックアナライザー
配線はこんな感じ、ESP32の4番から入力してパソコンにデータを渡すように接続しています。
実験用だから半田付けはしないでブレッドボードに差しただけです。
ESP32のGNDからVS1838Bの真ん中
ESP32の4からVS1838Bの左
ESP32の3V3からVS1838Bの右
※レシーバーの製造元が違うと順番が違う場合有り

配線が正しいとなれば、パソコンでArduinoIDEをインストールする。(私はwindows11ですが、windows10の64bitですすめました)
ArduonoIDEを起動して日本語化にするには、File→Preferences→languageで日本語を選ぶと日本語になって使いやすくなります。
まずはESP32本体をUSBケーブルでパソコンと接続して、ボードは「ESP32S3 Dev Module」を追加することになります。
そしてインストールする必要なライブラリはIRremoteESP8266だけです。
スケッチに書くコードは以下のとおり
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRutils.h>
// VS1838BのOUTを接続したピン
const uint16_t kRecvPin = 4;
// エアコンなどの長い信号にも対応
const uint16_t kCaptureBufferSize = 1024;
const uint8_t kTimeout = 50;
IRrecv irrecv(
kRecvPin,
kCaptureBufferSize,
kTimeout,
true
);
decode_results results;
void setup() {
Serial.begin(115200);
delay(1000);
irrecv.setUnknownThreshold(12);
irrecv.enableIRIn();
Serial.println("IR Receiver Ready");
}
void loop() {
if (irrecv.decode(&results)) {
Serial.println();
Serial.println("===== RECEIVED =====");
Serial.println(resultToHumanReadableBasic(&results));
Serial.println(resultToSourceCode(&results));
Serial.println("====================");
irrecv.resume();
}
}
これだけでOKです。コンパイルしてESP32に書き込みます。
受信機に向かってリモコンでボタンを押すとArduonoIDEのシリアルモニタにこんな感じで信号が届きます。
パナソニックのテレビのチャンネルをアップした信号
Protocol : PANASONIC
Code : 0x400401002C2D (48 Bits)
uint16_t rawData[99] = {3502, 1760, 424, 420, 426, 1324, 446, 420, 426, 444, 426, 444, 426, 444, 448, 420, 424, 444, 426, 444, 426, 444, 426, 444, 424, 444, 426, 444, 432, 1314, 450, 418, 432, 438, 424, 444, 424, 444, 426, 444, 424, 444, 426, 444, 426, 444, 426, 444, 424, 1346, 424, 426, 426, 450, 426, 450, 426, 450, 426, 450, 426, 450, 426, 450, 426, 450, 426, 450, 426, 448, 426, 1350, 424, 420, 426, 1324, 446, 1322, 424, 420, 424, 444, 426, 444, 426, 444, 426, 1344, 426, 426, 424, 1330, 446, 1322, 424, 420, 426, 1346, 424}; // PANASONIC 400401002C2D
uint32_t address = 0x4004;
uint32_t command = 0x1002C2D;
uint64_t data = 0x400401002C2D;
これはエアコンを冷房26.5℃で送信した時の情報です。
Protocol : MITSUBISHI_AC
Code : 0x23CB26010020581A16F85F0000001C000030 (144 Bits)
uint16_t rawData[583] = {3406, 1736, 410, 1274, 432, 1274, 434, 414, 434, 416, 434, 414, 434, 1268, 436, 418, 434, 414, 434, 1272, 434, 1274, 432, 416, 434, 1246, 460, 414, 434, 416, 434, 1272, 434, 1262, 444, 416, 432, 1216, 492, 1274, 434, 414, 434, 416, 434, 1252, 452, 416, 434, 414, 434, 1270, 436, 416, 434, 412, 434, 416, 434, 414, 434, 416, 434, 414, 434, 414, 434, 416, 434, 412, 434, 416, 434, 414, 434, 416, 434, 414, 434, 416, 434, 414, 434, 416, 434, 414, 434, 416, 434, 414, 432, 414, 434, 1222, 484, 414, 434, 416, 432, 416, 432, 416, 434, 414, 434, 1274, 434, 1272, 432, 416, 434, 1272, 434, 414, 434, 418, 434, 1258, 446, 414, 432, 1274, 434, 1272, 434, 416, 436, 414, 432, 414, 434, 416, 434, 1270, 434, 1240, 466, 416, 434, 1272, 434, 416, 434, 414, 434, 414, 434, 414, 434, 416, 434, 414, 434, 1274, 432, 1274, 432, 1274, 432, 1274, 434, 1274, 432, 1204, 504, 1274, 434, 1272, 432, 1274, 432, 1274, 434, 414, 434, 1274, 434, 416, 434, 416, 434, 414, 434, 414, 434, 414, 434, 416, 434, 414, 434, 416, 434, 414, 434, 414, 434, 416, 434, 414, 434, 416, 434, 416, 434, 416, 434, 414, 432, 416, 434, 416, 434, 414, 434, 416, 434, 414, 434, 416, 434, 418, 434, 412, 434, 416, 434, 414, 434, 414, 434, 1274, 432, 1296, 410, 1294, 412, 414, 434, 416, 434, 414, 434, 416, 434, 414, 434, 416, 432, 416, 434, 414, 434, 416, 434, 414, 434, 416, 434, 414, 432, 418, 434, 414, 434, 414, 434, 414, 434, 414, 434, 414, 434, 416, 434, 414, 434, 416, 434, 414, 434, 414, 434, 1294, 410, 1296, 412, 414, 434, 414, 436, 12888, 3408, 1734, 412, 1294, 412, 1294, 412, 414, 434, 416, 434, 414, 434, 1294, 412, 414, 434, 416, 434, 1294, 412, 1294, 414, 414, 436, 1294, 412, 414, 434, 414, 436, 1294, 412, 1294, 412, 414, 436, 1294, 412, 1294, 414, 412, 464, 386, 444, 1286, 412, 412, 460, 390, 460, 1270, 436, 392, 434, 414, 434, 440, 432, 416, 434, 414, 412, 436, 412, 438, 412, 438, 412, 438, 410, 438, 414, 436, 410, 438, 412, 436, 418, 432, 442, 406, 446, 402, 444, 406, 446, 402, 442, 406, 416, 432, 414, 1266, 440, 410, 438, 412, 438, 410, 436, 412, 436, 414, 436, 1270, 436, 1270, 434, 414, 434, 1272, 434, 416, 432, 416, 434, 1272, 434, 414, 434, 1272, 434, 1274, 432, 414, 432, 418, 432, 414, 432, 418, 432, 1274, 432, 1274, 432, 418, 432, 1274, 430, 420, 432, 418, 430, 416, 432, 418, 430, 418, 430, 418, 430, 1276, 430, 1278, 428, 1278, 428, 1298, 408, 1298, 408, 1298, 408, 1300, 406, 1300, 406, 1300, 406, 1300, 406, 422, 426, 1300, 406, 424, 424, 444, 404, 444, 404, 446, 404, 442, 404, 446, 404, 446, 402, 448, 402, 446, 402, 446, 402, 448, 402, 448, 402, 448, 398, 452, 398, 450, 396, 456, 396, 450, 396, 454, 396, 452, 396, 454, 396, 452, 396, 452, 396, 454, 394, 454, 396, 452, 396, 454, 396, 452, 396, 1314, 394, 1312, 392, 1336, 370, 454, 394, 454, 394, 452, 396, 454, 396, 452, 394, 454, 396, 452, 394, 454, 396, 454, 374, 476, 394, 454, 394, 454, 396, 452, 394, 456, 394, 454, 394, 452, 396, 456, 394, 454, 394, 454, 394, 454, 396, 454, 394, 454, 372, 478, 386, 1344, 370, 1336, 370, 454, 374, 474, 394}; // MITSUBISHI_AC
uint8_t state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x58, 0x1A, 0x16, 0xF8, 0x5F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x30};
テレビの場合は「現在の状態からチャンネルを1つアップする」という情報で済みますが、エアコンの場合は現在の設定から温度を1℃アップするという信号の出し方をしません。
冷房モード、26℃、風量、ルーバーの方向、人感センサーonなど様々な情報を毎回送るようになっているために信号がすごく長くなっています。
この為に既存サービスのスマートリモコン(+Style)などでは扱うエアコンの型番が一致していないと正しい信号が出ているのか分からないわけです。
ちなみに買う必要なかったロジックアナライザーで信号をみるとこんな感じです。

パソコンと一緒で赤外線リモコンの信号は0と1だけの波の情報になりますね。
この情報でもバックアップとしては機能するようでしたが、数字とか16進数の情報見たかったので私にロジックアナライザはいらなかったです。
赤外線LED送信する
先の話からエアコンの信号は長く、何百というパターンがあり全部のパターンの信号をバックアップするのは大変なので、よく使うところだけをバックアップし、送信機に仕込み外出先からスマホで扱うということを進めます。
送信には先ほど使った①ESP32-S3でもいいのですが、私は送信専用機として別に欲しかったので、小型のXIAO ESP32Cを購入しました。
それと信号を出す司令塔のトランジスタIRLZ44N
最終的に基盤に半田付けなのでユニバーサル基盤
配線はこんな感じです。
テストでブレッドボードに配置して、動作確認したらユニバーサル基盤で半田付けする流れです。

※写真のトランジスタ左右は文字の書いてある正面から見た場合です。
USB5Vがプラス、GNDがマイナス、D0が赤外線の信号を出力するところ。
5Vから18Ω~22Ω → +LED- → +LED- →トランジスタの真ん中へ。
トランジスタ右→左に10kΩ。
トランジスタ左→ 220Ω →XIAOのD0。
トランジスタ右→ マイナス
※抵抗は18Ωが手元になかったので2個(22Ωと100Ω)並列で18Ωくらいとしてにして使っています。
また前の赤外線受信機でセットで入っていたLEDはどのくらいの出力対応か不明だったので、TSAL6200を買って安心を手に入れました。セットのLEDを使う場合はどこまで強くしていいかわからないので、33Ωくらいまでにしておけば安全かなと思います。
これはユニバーサル基盤で半田付けした後

半田付けなんて20年以上ぶりなので大変だった。
USBケーブルでパソコンとつないだらArduino IDEのボード設定はXIAO_ESP32C3を選択。
必要なライブラリはSinricProのスマホアプリを使う予定なので以下の3つをインストールする必要があります。
esp32 by Espressif Systems
IRremoteESP8266
SinricPro
エアコンでよく使うのは25℃~27℃くらいの冷房と20℃暖房とエアコンオフくらいで、同じことしたい人はコピペして信号部分変えるといけます。
ArduonoIDEスケッチに書くコードはこんな感じになりました。
#include <WiFi.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ir_Mitsubishi.h>
#include <SinricPro.h>
#include <SinricProWindowAC.h>
#define WIFI_SSID "ここにWi-Fi名"
#define WIFI_PASS "ここにWi-Fiパスワード"
#define APP_KEY "ここにAPP_KEY"
#define APP_SECRET "ここにAPP_SECRET"
#define AC_ID "ここにエアコンDEVICE_ID"
const uint16_t kSendPin = 2;
IRMitsubishiAC mitsubishiAc(kSendPin);
// 冷房25.0℃
const uint8_t acCool25[18] = {
0x23,0xCB,0x26,0x01,0x00,0x20,
0x58,0x09,0x16,0xF8,0x66,0x00,
0x00,0x00,0x1C,0x00,0x00,0x26
};
// 冷房25.5℃
const uint8_t acCool255[18] = {
0x23,0xCB,0x26,0x01,0x00,0x20,
0x58,0x19,0x16,0xF8,0x66,0x00,
0x00,0x00,0x1C,0x00,0x00,0x36
};
// 冷房26.0℃
const uint8_t acCool26[18] = {
0x23,0xCB,0x26,0x01,0x00,0x20,
0x58,0x0A,0x16,0xF8,0x66,0x00,
0x00,0x00,0x1C,0x00,0x00,0x27
};
// 冷房26.5℃
const uint8_t acCool265[18] = {
0x23,0xCB,0x26,0x01,0x00,0x20,
0x58,0x1A,0x16,0xF8,0x66,0x00,
0x00,0x00,0x1C,0x00,0x00,0x37
};
// 冷房27.0℃
const uint8_t acCool27[18] = {
0x23,0xCB,0x26,0x01,0x00,0x20,
0x58,0x0B,0x16,0xF8,0x66,0x00,
0x00,0x00,0x1C,0x00,0x00,0x28
};
// 暖房20.0℃
const uint8_t acHeat20[18] = {
0x23,0xCB,0x26,0x01,0x00,0x20,
0x48,0x04,0x10,0xF8,0x5F,0x00,
0x00,0x00,0x1C,0x00,0x00,0x04
};
float acTemperature = 26.0;
String acMode = "COOL";
bool acPower = false;
void loadCurrentAcState() {
if (acMode == "HEAT") {
acTemperature = 20.0;
mitsubishiAc.setRaw(
acHeat20
);
return;
}
int step =
round(
acTemperature * 2.0
);
switch (step) {
// 25.0℃
case 50:
acTemperature = 25.0;
mitsubishiAc.setRaw(
acCool25
);
break;
// 25.5℃
case 51:
acTemperature = 25.5;
mitsubishiAc.setRaw(
acCool255
);
break;
// 26.0℃
case 52:
acTemperature = 26.0;
mitsubishiAc.setRaw(
acCool26
);
break;
// 26.5℃
case 53:
acTemperature = 26.5;
mitsubishiAc.setRaw(
acCool265
);
break;
// 27.0℃
case 54:
acTemperature = 27.0;
mitsubishiAc.setRaw(
acCool27
);
break;
// 想定外なら26.0℃
default:
acTemperature = 26.0;
mitsubishiAc.setRaw(
acCool26
);
break;
}
}
void sendAcOn() {
loadCurrentAcState();
mitsubishiAc.on();
mitsubishiAc.send();
Serial.print(
"IR -> AC ON / "
);
Serial.print(
acMode
);
Serial.print(
" / "
);
Serial.print(
acTemperature,
1
);
Serial.println(
" C"
);
}
void sendAcOff() {
loadCurrentAcState();
mitsubishiAc.off();
mitsubishiAc.send();
Serial.print(
"IR -> AC OFF / "
);
Serial.print(
acMode
);
Serial.print(
" / "
);
Serial.print(
acTemperature,
1
);
Serial.println(
" C"
);
}
bool acSendPending = false;
unsigned long
acSendRequestedAt = 0;
const unsigned long
AC_SEND_DELAY_MS = 500;
void scheduleAcSend() {
acSendPending = true;
acSendRequestedAt =
millis();
Serial.println(
"AC send scheduled"
);
}
void processPendingAcSend() {
if (!acSendPending) {
return;
}
// 最後のSinricイベントから500ms待つ
if (
millis() - acSendRequestedAt
< AC_SEND_DELAY_MS ) { return; } acSendPending = false; Serial.println( "AC scheduled send START" ); if (acPower) { sendAcOn(); } else { sendAcOff(); } Serial.println( "AC scheduled send END" ); } bool onAcPowerState( const String &deviceId, bool &state ) { Serial.print( "SINRIC -> AC Power "
);
Serial.println(
state
? "ON"
: "OFF"
);
acPower =
state;
scheduleAcSend();
return true;
}
bool onAcTargetTemperature(
const String &deviceId,
float &temperature
) {
Serial.print(
"SINRIC -> AC Temperature "
);
Serial.println(
temperature,
1
);
// 暖房は現在20℃固定
if (
acMode == "HEAT"
) {
acTemperature =
20.0;
temperature =
20.0;
} else {
// 冷房は25~27℃
if (
temperature < 25.0 ) { temperature = 25.0; } if ( temperature > 27.0
) {
temperature =
27.0;
}
// 0.5℃単位
temperature =
round(
temperature * 2.0
)
/ 2.0;
acTemperature =
temperature;
}
// 温度指定されたら運転ON
acPower =
true;
scheduleAcSend();
temperature =
acTemperature;
return true;
}
bool onAcAdjustTargetTemperature(
const String &deviceId,
float &temperatureDelta
) {
Serial.print(
"SINRIC -> AC Temp Delta "
);
Serial.println(
temperatureDelta,
1
);
// 暖房は20℃固定
if (
acMode == "HEAT"
) {
acTemperature =
20.0;
} else {
acTemperature +=
temperatureDelta;
if (
acTemperature < 25.0 ) { acTemperature = 25.0; } if ( acTemperature > 27.0
) {
acTemperature =
27.0;
}
// 0.5℃単位
acTemperature =
round(
acTemperature * 2.0
)
/ 2.0;
}
acPower =
true;
scheduleAcSend();
temperatureDelta =
acTemperature;
return true;
}
bool onAcThermostatMode(
const String &deviceId,
String &mode
) {
Serial.print(
"SINRIC -> AC Mode "
);
Serial.println(
mode
);
if (
mode == "COOL"
) {
acMode =
"COOL";
// 暖房などから冷房へ戻した時
// 範囲外なら26℃にする
if (
acTemperature < 25.0 || acTemperature > 27.0
) {
acTemperature =
26.0;
}
acPower =
true;
scheduleAcSend();
return true;
}
if (
mode == "HEAT"
) {
acMode =
"HEAT";
acTemperature =
20.0;
acPower =
true;
scheduleAcSend();
return true;
}
Serial.println(
"Unsupported AC mode"
);
return false;
}
void setupWiFi() {
WiFi.mode(
WIFI_STA
);
// IR送信の安定性を考えて
// Wi-FiスリープOFF
WiFi.setSleep(
false
);
WiFi.setAutoReconnect(
true
);
WiFi.begin(
WIFI_SSID,
WIFI_PASS
);
Serial.print(
"Wi-Fi connecting"
);
while (
WiFi.status()
!= WL_CONNECTED
) {
Serial.print(
"."
);
delay(
500
);
}
Serial.println();
Serial.println(
"Wi-Fi connected!"
);
Serial.print(
"IP address: "
);
Serial.println(
WiFi.localIP()
);
}
void setupSinricPro() {
SinricProWindowAC &ac =
SinricPro[
AC_ID
];
ac.onPowerState(
onAcPowerState
);
ac.onTargetTemperature(
onAcTargetTemperature
);
ac.onAdjustTargetTemperature(
onAcAdjustTargetTemperature
);
ac.onThermostatMode(
onAcThermostatMode
);
SinricPro.onConnected(
[]() {
Serial.println(
"Connected to SinricPro!"
);
}
);
SinricPro.onDisconnected(
[]() {
Serial.println(
"Disconnected from SinricPro!"
);
}
);
SinricPro.begin(
APP_KEY,
APP_SECRET
);
}
void printMenu() {
Serial.println();
Serial.println(
);
Serial.println(
"Q = Cool 25.0"
);
Serial.println(
"W = Cool 25.5"
);
Serial.println(
"A = Cool 26.0"
);
Serial.println(
"E = Cool 26.5"
);
Serial.println(
"F = Cool 27.0"
);
Serial.println(
"H = Heat 20.0"
);
Serial.println(
"O = AC OFF"
);
Serial.println(
);
}
void setup() {
Serial.begin(
115200
);
delay(
1000
);
// 三菱エアコンIR送信開始
mitsubishiAc.begin();
// Wi-Fi
setupWiFi();
// SinricPro
setupSinricPro();
// シリアルメニュー
printMenu();
}
void loop() {
// SinricPro
SinricPro.handle();
// 予約されたIR送信
processPendingAcSend();
if (
Serial.available()
) {
char c =
Serial.read();
switch (c) {
case 'A':
case 'a':
acMode =
"COOL";
acTemperature =
25.0;
acPower =
true;
sendAcOn();
break;
case 'B':
case 'b':
acMode =
"COOL";
acTemperature =
25.5;
acPower =
true;
sendAcOn();
break;
case 'C':
case 'c':
acMode =
"COOL";
acTemperature =
26.0;
acPower =
true;
sendAcOn();
break;
case 'D':
case 'd':
acMode =
"COOL";
acTemperature =
26.5;
acPower =
true;
sendAcOn();
break;
case 'E':
case 'e':
acMode =
"COOL";
acTemperature =
27.0;
acPower =
true;
sendAcOn();
break;
case 'F':
case 'f':
acMode =
"HEAT";
acTemperature =
20.0;
acPower =
true;
sendAcOn();
break;
case '0':
acPower =
false;
sendAcOff();
break;
}
}
}
エアコンをスマホで扱うためには
SinricPROのWEBサイトでsign-upからユーザー新規登録します。登録後は日本語で扱えるので安心してください。
画面左のデバイス→デバイス追加に進み、デバイス名は「エアコン」、説明「MITUBISI」デバイスタイプ「Window AC Unit」あとは次へで最後までいって保存でok。
ちなみに3つまでのデバイスが無料で登録して使えます。(私はエアコンと風呂自動と温度監視)
最後までいくとアプリキーとアプリシークレットキーとデバイスIDが出ます。この3つはコードに入力する必要がありますので、コピーでメモ帳にでもとっておきましょう。
ArduinoIDEのスケッチでコード上部5か所(アプリキー、アプリシークレットキー、デバイスID、wi-fiのSSID、wi-fiのパスワード)を入力してコードをコンパイルしてXIAO ESP32本体に読み込ませたらあとは、スマホでSinricPROアプリをインストールし、先に作ったアカウント情報でログインすればエアコンが出ていると思います。
アレクサとの連動したい場合は、アレクサアプリでスキルの追加からSinric Proで検索して進めるとアレクサのデバイスに載ってきます。そこまで進められれば音声で「アレクサエアコンを冷房26℃にして」で扱うことができます。
また外出先からはSinricProアプリからエアコンの正確な信号を送ることがができます。
さらに私はSHT31という室温計とコードを追加して、指定の室温に上がってきたら冷房on、という自動で室温管理のところまで設定して使っています。
そうすることでネット障害やルーターの不都合があった場合でも、エアコンonの信号はESP32本体だけで完結し、ウサギに怒られなくて済みます。

長くなったので風呂自動をスマート化は別記事で投稿します。
