2021年12月5日日曜日

Arduino(ESP32)でエアコンの赤外線リモコンの信号をコピーして発信する


背景

以前Arduinoで赤外線リモコンの信号を送受信する記事を書きました。
エアコンのリモコンに対して行おうとしたところ、その記事と同じ方法では読み取れませんでした。
試行錯誤の末に期待通り動かせたので、備忘録を兼ねて方法を記事に残します。

ESP32を使う理由: 多めのRAM(メモリ)が要るから

エアコンのリモコンから送信される情報はテレビのリモコンなどに比べると長いため、Arduino UnoではRAMが足らずに受信すら難しいです。
Arduino Leonardoなら受信は何とかできますが、いくつかのボタンの情報を保持して送信しようとするとRAMが足りなくなります。

上記のRAM不足の理由から、この記事ではESP32を利用します。

使ったもの

  • ESP32開発ボード+USBケーブル
    PCからESP32にプログラムを書き込むセットです。
  • ESP32にプログラムを書き込めるPCの環境
    下記のどちらかの組み合わせの環境でプログラムを書き込めます。
    手持ちのPCにインストールしてください。
    VSCode + PlatformIO(プラグイン)
    Arduino IDE + arduino-esp32
  • 赤外線受光器
    下記の2個入りの製品を利用しました。
    OSRB38C9AA
  • 赤外線LED
    常時なら100mA、瞬時なら1A流せる 5mmの砲弾型赤外線LEDを利用しました。
    下記のLEDは順電圧が1.35Vなので、5Vで1A流すには(5-1.35)/1 = 3.65Ωの抵抗が必要です。
    5mm赤外線LED 940nm OSI5FU5111C-40
  • N-mosfet
    1A以上流せるものを利用しました。
    下記mosfetは4V駆動なのでESP32からの3.3V信号では部分的にしか動作しませんが、データシートを見ると3.3Vで10A流せそうだったので、今回の用途である1A弱は十分に流せると思います。
    NchパワーMOSFET 60V 25A 2SK2232
  • 2.2Ω抵抗
    電圧降下約1.3VのLEDに対して5Vで1A弱を流には3.65Ωが必要ですが、LEDが壊れないように若干大きい抵抗として、2.2Ω2本を直列にして4.4Ωを利用します。
    カーボン抵抗(炭素皮膜抵抗) 1W2.2Ω (100本入)
  • 10kΩ抵抗
    mosfetのゲートのプルダウン(GNDに繋げて、信号が着てない場合は電位を低くする)に使います。
    これがないとプログラム書き込み中にmosfetが駆動して、瞬時通電用の抵抗で組んでいる回路だとLEDが故障する可能性があります。
    カーボン抵抗(炭素皮膜抵抗) 1/4W10kΩ (100本入)
  • 任意: 37Ω以上の抵抗
    今回利用する赤外線LEDは短時間なら1A流せますが、常時流す場合は100mAが上限です。
    1Aを流し続けるとLEDが故障する可能性があるので、回路に不安がある場合LEDの抵抗として配置します。
    回路やプログラムが完成し、書き込み時や動作時にLEDが常時点灯することが無いと確認できたら不要になります。
    自分は47Ωで動作確認しました。
    カーボン抵抗(炭素皮膜抵抗) 1/4W47Ω (100本入)
  • ブレッドボード
    部品を配置します。
  • ジャンパワイヤ
    ブレッドボード上の部品を配線します。
  • ニッパー
    LEDや抵抗の足を短くするのに使います。
    無くても良いですが、あると回路を低く仕上げられます。
  • 情報をコピーしたいリモコン
    今回はアパートに設置されている東芝のエアコンのリモコンを使いました。
    型番: WH-D6B

回路作成

今回利用するIRremoteというライブラリのESP32の標準ピン割当では、GPIO15で受信、GPIO4で送信するプログラムができるので、それに合わせて受信機とLEDを配線します。
GPIOからの出力では数十mAほどしか流せないので、1A近く流してLEDを強く光らせるためにmosfetを利用しています。
回路に不安がある場合は、2.2Ω抵抗の1つを37Ω以上のものに変えてください。
(抵抗が大きすぎると携帯電話のカメラなどでも確認できないので、LEDの発光が確認できない場合は並列などで抵抗を減らしてみてください。)

回路の全体像です。


実際に組むとこうなりました。


5Vと3.3VはESP32開発ボードで隠れる穴から引き出しています。


上からの写真ではわかりにくいmosfet周辺の様子です。



回路に不安がある場合は、2.2Ωを37Ω以上に置き換えてください。
下記の写真では47Ωに置き換えています。


回路が出来たのでプログラムの作成に移ります。

ライブラリ IRremote(3.5.0以上)を開発環境に設定

PlatformIOの場合

プロジェクトのplatformio.iniにlib_depsとしてIRremoteを記述すればビルド時に自動的にダウンロードされます。
プロジェクトの作り方は後ほど説明します。
lib_deps = IRremote

Arduino IDEの場合

ライブラリマネージャからIRremoteをインストールしてください。
ライブラリマネージャは「スケッチ -> ライブラリをインクルード -> ライブラリを管理」で開けます。
右上の検索欄にIRremoteと入力すると2番目くらいに出てくるので、それの3.5.0以上をインストールしてください。


リモコンの情報を読む

IRremoteのReceiveDumpというサンプルプログラムを変更してエアコンのリモコンの情報を読める状態にします。

プロジェクトを準備

PlatformIOの場合

下記の操作でプロジェクトを準備してください。
  1. (irReceiveなどの名前で)プロジェクト用のディレクトリを作成
  2. ディレクトリに入り下記のコマンドを実行してesp32向けのプロジェクトを作成 (srcディレクトリやplatformio.iniファイルが作成されます)
    pio init -b esp32dev
  3. platformio.iniに下記の行を追加
    これによりIRremoteが利用可能になります。
    lib_deps = IRremote
  4. src/main.cppの1行目に「#include <Arduino.h>」を記述してその後にReceiveDump.inoの内容をペースト
  5. src/PinDefinitionsAndMore.hというファイルを作ってPinDefinitionsAndMore.hの内容をペースト

Arduino IDEの場合

ファイル -> スケッチ例 -> IRremote -> ReceiveDump でプログラムを開いてください。

エアコンの情報を読むためにサンプルコードを変更

サンプルコードのReceiveDumpではメモリの領域や待機時間がエアコンのリモコンから送信される信号に対して足りないので、それぞれ増やします。
また、RedeiveDumpはLED_BUILTINを利用しますがESP32はそれが定義されていないボードが多いので、使ってないピンをLED_BUILTINとして定義します。
IRreceive.hppを読み込む行の前に下記の行を追加してください。
(RAW_BUFFER_LENGTHを400としていましたが、400では動かないことがあるとコメントで共有していただいたのでサンプルコードで提示されている最大値の750に増やしました。)
#define LED_BUILTIN 26
#define RAW_BUFFER_LENGTH 750
#define RECORD_GAP_MICROS 12000


上記の操作でリモコンの情報が読み取れるプログラムに変更できたので、ESP32の開発ボードをPCにUSB接続してプログラムを書き込みます。

プログラムを書き込む

PlatformIOの場合

Ctrl + Shift + @ で端末を開き、下記のコマンドを実行するとプログラムを書き込めます。
pio run -t upload
ポートの指定が必要な場合は、platformio.iniにupload_portとしてポート(linuxなら/dev/ttyUSB0など、WindowsならCOM4など)を指定してください。

Arduino IDEの場合

下記の操作で書き込めます。
ツール -> ボード をESP32 dev moduleに設定
ツール -> シリアルポートをESP32のポートに設定
アップロードボタン(IDE上部の右矢印ボタン)を押す

書き込んだらシリアルポートを開いて、受信した情報を見れる状態にします。

シリアルモニタを開く

PlatformIOの場合

端末で下記のコマンドを入力します。
pio device monitor -b 115200

Arduino IDEの場合

IDE右上の虫眼鏡マークのシリアルモニタを開くボタンを押します。

開いたら右下の通信速度を15200bpsに変更します。

シリアルモニタに表示される情報をコピーする

赤外線受光器にリモコンを向けてボタンを押すと、回路が組めていればシリアルモニタに情報が表示されます。


成功すると長めのログが表示されます。
この記事では最後に表示されるprontoDataを利用します。
Protocol=UNKNOWN Hash=0xB1BB3DB4 148 bits received

Raw result in internal ticks (50 us) - with leading gap
rawData[296]:
-28671
+ 87,- 89 + 11,- 32 + 10,- 33 + 11,- 33
+ 10,- 33 + 11,- 11 + 10,- 12 + 10,- 33
+ 10,- 12 + 10,- 11 + 11,- 11 + 10,- 12
+ 10,- 11 + 11,- 33 + 10,- 33 + 11,- 11
+ 10,- 33 + 11,- 11 + 10,- 12 + 10,- 12
+ 10,- 11 + 10,- 12 + 11,- 11 + 11,- 31
+ 11,- 33 + 11,- 32 + 11,- 33 + 10,- 33
+ 11,- 32 + 12,- 32 + 11,- 32 + 11,- 11
+ 11,- 11 + 10,- 12 + 10,- 11 + 11,- 11
+ 10,- 11 + 12,- 10 + 10,- 12 + 10,- 11
+ 11,- 33 + 10,- 11 + 11,- 33 + 10,- 11
+ 11,- 33 + 10,- 12 + 11,- 10 + 10,- 12
+ 10,- 12 + 10,- 12 + 9,- 12 + 10,- 11
+ 11,- 11 + 10,- 12 + 10,- 33 + 10,- 33
+ 11,- 33 + 10,- 12 + 10,- 12 + 10,- 11
+ 10,- 12 + 10,- 12 + 10,- 11 + 10,- 12
+ 10,- 11 + 11,- 11 + 10,- 33 + 11,- 11
+ 10,- 33 + 11,- 11 + 10,- 33 + 11,- 33
+ 10,- 11 + 11,- 106 + 88,- 88 + 10,- 33
+ 11,- 33 + 10,- 33 + 10,- 33 + 11,- 11
+ 11,- 11 + 11,- 32 + 11,- 11 + 10,- 12
+ 10,- 11 + 11,- 11 + 10,- 11 + 11,- 33
+ 11,- 32 + 12,- 10 + 10,- 33 + 11,- 11
+ 10,- 12 + 11,- 11 + 11,- 10 + 11,- 11
+ 10,- 11 + 11,- 33 + 11,- 32 + 10,- 33
+ 11,- 33 + 10,- 33 + 11,- 32 + 12,- 32
+ 10,- 33 + 11,- 11 + 10,- 12 + 11,- 10
+ 11,- 11 + 10,- 12 + 10,- 11 + 11,- 11
+ 11,- 11 + 11,- 10 + 11,- 33 + 10,- 11
+ 11,- 33 + 10,- 12 + 10,- 33 + 10,- 12
+ 10,- 12 + 10,- 11 + 10,- 11 + 11,- 11
+ 11,- 11 + 11,- 11 + 10,- 11 + 11,- 11
+ 10,- 33 + 11,- 32 + 11,- 33 + 10,- 12
+ 10,- 11 + 11,- 11 + 10,- 12 + 10,- 11
+ 11,- 11 + 10,- 12 + 10,- 11 + 11,- 11
+ 11,- 32 + 11,- 11 + 10,- 33 + 11,- 11
+ 10,- 33 + 11,- 33 + 10,- 12 + 10
Raw result in microseconds - with leading gap
rawData[296]:
-1433550
+4350,-4450 + 550,-1600 + 500,-1650 + 550,-1650
+ 500,-1650 + 550,- 550 + 500,- 600 + 500,-1650
+ 500,- 600 + 500,- 550 + 550,- 550 + 500,- 600
+ 500,- 550 + 550,-1650 + 500,-1650 + 550,- 550
+ 500,-1650 + 550,- 550 + 500,- 600 + 500,- 600
+ 500,- 550 + 500,- 600 + 550,- 550 + 550,-1550
+ 550,-1650 + 550,-1600 + 550,-1650 + 500,-1650
+ 550,-1600 + 600,-1600 + 550,-1600 + 550,- 550
+ 550,- 550 + 500,- 600 + 500,- 550 + 550,- 550
+ 500,- 550 + 600,- 500 + 500,- 600 + 500,- 550
+ 550,-1650 + 500,- 550 + 550,-1650 + 500,- 550
+ 550,-1650 + 500,- 600 + 550,- 500 + 500,- 600
+ 500,- 600 + 500,- 600 + 450,- 600 + 500,- 550
+ 550,- 550 + 500,- 600 + 500,-1650 + 500,-1650
+ 550,-1650 + 500,- 600 + 500,- 600 + 500,- 550
+ 500,- 600 + 500,- 600 + 500,- 550 + 500,- 600
+ 500,- 550 + 550,- 550 + 500,-1650 + 550,- 550
+ 500,-1650 + 550,- 550 + 500,-1650 + 550,-1650
+ 500,- 550 + 550,-5300 +4400,-4400 + 500,-1650
+ 550,-1650 + 500,-1650 + 500,-1650 + 550,- 550
+ 550,- 550 + 550,-1600 + 550,- 550 + 500,- 600
+ 500,- 550 + 550,- 550 + 500,- 550 + 550,-1650
+ 550,-1600 + 600,- 500 + 500,-1650 + 550,- 550
+ 500,- 600 + 550,- 550 + 550,- 500 + 550,- 550
+ 500,- 550 + 550,-1650 + 550,-1600 + 500,-1650
+ 550,-1650 + 500,-1650 + 550,-1600 + 600,-1600
+ 500,-1650 + 550,- 550 + 500,- 600 + 550,- 500
+ 550,- 550 + 500,- 600 + 500,- 550 + 550,- 550
+ 550,- 550 + 550,- 500 + 550,-1650 + 500,- 550
+ 550,-1650 + 500,- 600 + 500,-1650 + 500,- 600
+ 500,- 600 + 500,- 550 + 500,- 550 + 550,- 550
+ 550,- 550 + 550,- 550 + 500,- 550 + 550,- 550
+ 500,-1650 + 550,-1600 + 550,-1650 + 500,- 600
+ 500,- 550 + 550,- 550 + 500,- 600 + 500,- 550
+ 550,- 550 + 500,- 600 + 500,- 550 + 550,- 550
+ 550,-1600 + 550,- 550 + 500,-1650 + 550,- 550
+ 500,-1650 + 550,-1650 + 500,- 600 + 500

Result as internal ticks (50 us) array - compensated with MARK_EXCESS_MICROS=20
uint8_t rawTicks[295] = {87,89, 11,32, 10,33, 11,33, 10,33, 11,11, 10,12, 10,33, 10,12, 10,11, 11,11, 10,12, 10,11, 11,33, 10,33, 11,11, 10,33, 11,11, 10,12, 10,12, 10,11, 10,12, 11,11, 11,31, 11,33, 11,32, 11,33, 10,33, 11,32, 12,32, 11,32, 11,11, 11,11, 10,12, 10,11, 11,11, 10,11, 12,10, 10,12, 10,11, 11,33, 10,11, 11,33, 10,11, 11,33, 10,12, 11,10, 10,12, 10,12, 10,12, 9,12, 10,11, 11,11, 10,12, 10,33, 10,33, 11,33, 10,12, 10,12, 10,11, 10,12, 10,12, 10,11, 10,12, 10,11, 11,11, 10,33, 11,11, 10,33, 11,11, 10,33, 11,33, 10,11, 11,106, 88,88, 10,33, 11,33, 10,33, 10,33, 11,11, 11,11, 11,32, 11,11, 10,12, 10,11, 11,11, 10,11, 11,33, 11,32, 12,10, 10,33, 11,11, 10,12, 11,11, 11,10, 11,11, 10,11, 11,33, 11,32, 10,33, 11,33, 10,33, 11,32, 12,32, 10,33, 11,11, 10,12, 11,10, 11,11, 10,12, 10,11, 11,11, 11,11, 11,10, 11,33, 10,11, 11,33, 10,12, 10,33, 10,12, 10,12, 10,11, 10,11, 11,11, 11,11, 11,11, 10,11, 11,11, 10,33, 11,32, 11,33, 10,12, 10,11, 11,11, 10,12, 10,11, 11,11, 10,12, 10,11, 11,11, 11,32, 11,11, 10,33, 11,11, 10,33, 11,33, 10,12, 10}; // Protocol=UNKNOWN Hash=0xB1BB3DB4 148 bits received

Result as microseconds array - compensated with MARK_EXCESS_MICROS=20
uint16_t rawData[295] = {4330,4470, 530,1620, 480,1670, 530,1670, 480,1670, 530,570, 480,620, 480,1670, 480,620, 480,570, 530,570, 480,620, 480,570, 530,1670, 480,1670, 530,570, 480,1670, 530,570, 480,620, 480,620, 480,570, 480,620, 530,570, 530,1570, 530,1670, 530,1620, 530,1670, 480,1670, 530,1620, 580,1620, 530,1620, 530,570, 530,570, 480,620, 480,570, 530,570, 480,570, 580,520, 480,620, 480,570, 530,1670, 480,570, 530,1670, 480,570, 530,1670, 480,620, 530,520, 480,620, 480,620, 480,620, 430,620, 480,570, 530,570, 480,620, 480,1670, 480,1670, 530,1670, 480,620, 480,620, 480,570, 480,620, 480,620, 480,570, 480,620, 480,570, 530,570, 480,1670, 530,570, 480,1670, 530,570, 480,1670, 530,1670, 480,570, 530,5320, 4380,4420, 480,1670, 530,1670, 480,1670, 480,1670, 530,570, 530,570, 530,1620, 530,570, 480,620, 480,570, 530,570, 480,570, 530,1670, 530,1620, 580,520, 480,1670, 530,570, 480,620, 530,570, 530,520, 530,570, 480,570, 530,1670, 530,1620, 480,1670, 530,1670, 480,1670, 530,1620, 580,1620, 480,1670, 530,570, 480,620, 530,520, 530,570, 480,620, 480,570, 530,570, 530,570, 530,520, 530,1670, 480,570, 530,1670, 480,620, 480,1670, 480,620, 480,620, 480,570, 480,570, 530,570, 530,570, 530,570, 480,570, 530,570, 480,1670, 530,1620, 530,1670, 480,620, 480,570, 530,570, 480,620, 480,570, 530,570, 480,620, 480,570, 530,570, 530,1620, 530,570, 480,1670, 530,570, 480,1670, 530,1670, 480,620, 480}; // Protocol=UNKNOWN Hash=0xB1BB3DB4 148 bits received

Pronto Hex as string
char ProntoData[] = "0000 006D 0094 0000 00A8 00AA 0016 003D 0014 003F 0016 003F 0014 003F 0016 0014 0014 0016 0014 003F 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 003F 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0016 0014 0014 0014 0016 0016 0014 0016 003B 0016 003F 0016 003D 0016 003F 0014 003F 0016 003D 0018 003D 0016 003D 0016 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0014 0018 0012 0014 0016 0014 0014 0016 003F 0014 0014 0016 003F 0014 0014 0016 003F 0014 0016 0016 0012 0014 0016 0014 0016 0014 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 003F 0014 003F 0016 003F 0014 0016 0014 0016 0014 0014 0014 0016 0014 0016 0014 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 003F 0014 0014 0016 00CB 00AA 00A8 0014 003F 0016 003F 0014 003F 0014 003F 0016 0014 0016 0014 0016 003D 0016 0014 0014 0016 0014 0014 0016 0014 0014 0014 0016 003F 0016 003D 0018 0012 0014 003F 0016 0014 0014 0016 0016 0014 0016 0012 0016 0014 0014 0014 0016 003F 0016 003D 0014 003F 0016 003F 0014 003F 0016 003D 0018 003D 0014 003F 0016 0014 0014 0016 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 0016 0014 0016 0012 0016 003F 0014 0014 0016 003F 0014 0016 0014 003F 0014 0016 0014 0016 0014 0014 0014 0014 0016 0014 0016 0014 0016 0014 0014 0014 0016 0014 0014 003F 0016 003D 0016 003F 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0016 003D 0016 0014 0014 003F 0016 0014 0014 003F 0016 003F 0014 0016 0014 06C3 ";

自分が試したエアコンのリモコンは「運転・停止」ボタンを押すと「運転」と「停止」の信号を交互に送信する仕様だったので、「22℃暖房運転開始」と「22℃暖房運転停止」の情報としてprontoの情報をコピーしました。

開始信号のpronto(22℃暖房)
char ProntoData[] = "0000 006D 0094 0000 00AC 00A7 0018 003B 001A 003B 0018 003B 0018 003D 0018 0011 0018 0012 0018 003B 0018 0012 0018 0012 0018 0011 0018 0012 0018 0012 0018 003B 0018 003D 0018 0011 0018 003D 0016 0012 0018 0012 0016 0014 0016 0012 0018 0012 0016 0014 0016 003D 0018 003B 0018 003D 0016 003F 0016 003B 0018 003D 0016 003D 0018 003D 0016 0014 0016 0012 0016 0014 0016 0012 0018 0012 0016 0014 0016 0014 0016 0012 0014 0016 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0014 0016 0014 0016 0014 0016 0012 0018 0012 0014 0016 0014 0014 0018 0012 0016 003D 0018 003D 0014 0016 0016 0014 0012 0016 0014 0014 0018 0014 0012 0016 0014 0016 0014 0014 0014 0016 0014 003F 0014 0016 0014 003F 0016 0014 0014 0016 0014 003F 0014 0016 0014 00CF 00AC 00A7 0016 003F 0014 003F 0014 003F 0018 003D 0014 0016 0016 0012 0016 003D 0018 0012 0018 0012 0016 0014 0016 0012 0018 0012 0018 003B 0016 003D 0016 0014 0018 003D 0016 0012 0018 0012 0018 0012 0014 0014 0016 0014 0016 0014 0016 003D 0014 003F 0018 003D 0014 003F 0016 003D 0016 003F 0014 003F 0016 003F 0014 0014 0016 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 003F 0016 0014 0014 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 003F 0016 0014 0014 003F 0018 0012 0016 0014 0016 003D 0016 0014 0016 06C3 ";

停止信号のpronto(22℃暖房)
char ProntoData[] = "0000 006D 0094 0000 00A8 00AA 0016 003D 0014 003F 0016 003F 0014 003F 0016 0014 0014 0016 0014 003F 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 003F 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0016 0014 0014 0014 0016 0016 0014 0016 003B 0016 003F 0016 003D 0016 003F 0014 003F 0016 003D 0018 003D 0016 003D 0016 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0014 0018 0012 0014 0016 0014 0014 0016 003F 0014 0014 0016 003F 0014 0014 0016 003F 0014 0016 0016 0012 0014 0016 0014 0016 0014 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 003F 0014 003F 0016 003F 0014 0016 0014 0016 0014 0014 0014 0016 0014 0016 0014 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 003F 0014 0014 0016 00CB 00AA 00A8 0014 003F 0016 003F 0014 003F 0014 003F 0016 0014 0016 0014 0016 003D 0016 0014 0014 0016 0014 0014 0016 0014 0014 0014 0016 003F 0016 003D 0018 0012 0014 003F 0016 0014 0014 0016 0016 0014 0016 0012 0016 0014 0014 0014 0016 003F 0016 003D 0014 003F 0016 003F 0014 003F 0016 003D 0018 003D 0014 003F 0016 0014 0014 0016 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 0016 0014 0016 0012 0016 003F 0014 0014 0016 003F 0014 0016 0014 003F 0014 0016 0014 0016 0014 0014 0014 0014 0016 0014 0016 0014 0016 0014 0014 0014 0016 0014 0014 003F 0016 003D 0016 003F 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0016 003D 0016 0014 0014 003F 0016 0014 0014 003F 0016 003F 0014 0016 0014 06C3 ";

リモコンと同じ情報を発信する

先ほどコピーしたprontoの情報を赤外線LEDで発信します。

情報発信用のプロジェクトを作成

PlatformIOの場合

下記の操作でプロジェクトを準備してください。
  1. (irSendなどの名前で)プロジェクト用のディレクトリを作成
  2. ディレクトリに入り下記のコマンドを実行してesp32向けのプロジェクトを作成 (srcディレクトリやplatformio.iniファイルが作成されます)
    pio init -b esp32dev
  3. platformio.iniに下記の行を追加
    これによりIRremoteが利用可能になります。
    lib_deps = IRremote

Arduino IDEの場合

  1. ファイル -> 新規ファイル で新しいプロジェクトを開く
  2. Ctrl + sを入力し、irSendなどの名前でプロジェクトを保存

先ほどメモしたprontoの情報を使ってプログラムを作成

bootボタン(0番ピン)を押したらエアコンを起動し、18番ピンに接続したボタンを押したらエアコンを停止するプログラムです。
開始信号をprontDataTrunOn22Heater、停止信号をprontDataTurnOff22Heaterと定義して、ボタンが押されたら各情報を送信しています。
#include <Arduino.h>

#define PIN_SEND 4
#define PIN_BUTTON_TURN_ON 0
#define PIN_BUTTON_TURN_OFF 18

#define IR_SEND_PIN PIN_SEND
#include <IRremote.hpp>

char prontoDataTurnOn22Heater[] = "0000 006D 0094 0000 00AC 00A7 0018 003B 001A 003B 0018 003B 0018 003D 0018 0011 0018 0012 0018 003B 0018 0012 0018 0012 0018 0011 0018 0012 0018 0012 0018 003B 0018 003D 0018 0011 0018 003D 0016 0012 0018 0012 0016 0014 0016 0012 0018 0012 0016 0014 0016 003D 0018 003B 0018 003D 0016 003F 0016 003B 0018 003D 0016 003D 0018 003D 0016 0014 0016 0012 0016 0014 0016 0012 0018 0012 0016 0014 0016 0014 0016 0012 0014 0016 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0014 0016 0014 0016 0014 0016 0012 0018 0012 0014 0016 0014 0014 0018 0012 0016 003D 0018 003D 0014 0016 0016 0014 0012 0016 0014 0014 0018 0014 0012 0016 0014 0016 0014 0014 0014 0016 0014 003F 0014 0016 0014 003F 0016 0014 0014 0016 0014 003F 0014 0016 0014 00CF 00AC 00A7 0016 003F 0014 003F 0014 003F 0018 003D 0014 0016 0016 0012 0016 003D 0018 0012 0018 0012 0016 0014 0016 0012 0018 0012 0018 003B 0016 003D 0016 0014 0018 003D 0016 0012 0018 0012 0018 0012 0014 0014 0016 0014 0016 0014 0016 003D 0014 003F 0018 003D 0014 003F 0016 003D 0016 003F 0014 003F 0016 003F 0014 0014 0016 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 003F 0016 0014 0014 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 003F 0016 0014 0014 003F 0018 0012 0016 0014 0016 003D 0016 0014 0016 06C3 ";
char prontoDataTurnOff22Heater[] = "0000 006D 0094 0000 00A8 00AA 0016 003D 0014 003F 0016 003F 0014 003F 0016 0014 0014 0016 0014 003F 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 003F 0014 003F 0016 0014 0014 003F 0016 0014 0014 0016 0014 0016 0014 0014 0014 0016 0016 0014 0016 003B 0016 003F 0016 003D 0016 003F 0014 003F 0016 003D 0018 003D 0016 003D 0016 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0014 0018 0012 0014 0016 0014 0014 0016 003F 0014 0014 0016 003F 0014 0014 0016 003F 0014 0016 0016 0012 0014 0016 0014 0016 0014 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 003F 0014 003F 0016 003F 0014 0016 0014 0016 0014 0014 0014 0016 0014 0016 0014 0014 0014 0016 0014 0014 0016 0014 0014 003F 0016 0014 0014 003F 0016 0014 0014 003F 0016 003F 0014 0014 0016 00CB 00AA 00A8 0014 003F 0016 003F 0014 003F 0014 003F 0016 0014 0016 0014 0016 003D 0016 0014 0014 0016 0014 0014 0016 0014 0014 0014 0016 003F 0016 003D 0018 0012 0014 003F 0016 0014 0014 0016 0016 0014 0016 0012 0016 0014 0014 0014 0016 003F 0016 003D 0014 003F 0016 003F 0014 003F 0016 003D 0018 003D 0014 003F 0016 0014 0014 0016 0016 0012 0016 0014 0014 0016 0014 0014 0016 0014 0016 0014 0016 0012 0016 003F 0014 0014 0016 003F 0014 0016 0014 003F 0014 0016 0014 0016 0014 0014 0014 0014 0016 0014 0016 0014 0016 0014 0014 0014 0016 0014 0014 003F 0016 003D 0016 003F 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0014 0016 0014 0016 003D 0016 0014 0014 003F 0016 0014 0014 003F 0016 003F 0014 0016 0014 06C3 ";

void setup() {
Serial.begin(115200);
IrSender.begin();
pinMode(PIN_BUTTON_TURN_ON, INPUT_PULLUP);
pinMode(PIN_BUTTON_TURN_OFF, INPUT_PULLUP);
}

void loop() {
if (digitalRead(PIN_BUTTON_TURN_ON) == LOW) {
IrSender.sendPronto(prontoDataTurnOn22Heater);
Serial.println("send trun on at " + String(millis()));
} else if (digitalRead(PIN_BUTTON_TURN_OFF) == LOW) {
IrSender.sendPronto(prontoDataTurnOff22Heater);
Serial.println("send trun off at " + String(millis()));
}
delay(200);
}
PlatformIOの場合はmain.cppに、Arduino IDEの場合はプロジェクトのinoファイルに上記のプログラムを記述してください。

上記プログラムのprontoDataTurnOn22HeaterとprontoDataTurnOff22Heaterは、読み取ったエアコンの情報に書き換えてください。
(温度や暖房・冷房条件に合わせて、変数名も書き換えておくと分かりやすいと思います。冷房25℃ならprontDataTurnOn25Coolerなど)

prontDataを自身で読み取った値に変更したら、ESP32に書き込んでください。

発光確認

赤外線LEDは肉眼では光が見えませんが、スマホなどカメラで見ると光を確認できます。
bootボタンを押しながらスマホのカメラを通して赤外線LED(下記画像右下)を見ると、紫色に光りました。


エアコンを制御

信号のコピーと発信に成功していれば、bootボタンでエアコンが起動し、18ボタンでエアコンが停止します。


赤外線LEDに接続している抵抗が100Ωとかだとエアコンに近づかないと信号を認識してくれませんが、約4Ωで1A近く流している場合はリモコンと同様に遠距離からでもLEDの向きが合えば動くと思います。

終わり

Arduino Unoではメモリ領域が足りなくて動かせませんでしたが、ESP32の利用によりエアコンのリモコンの情報を読み取ち、その情報を元にエアコンを制御できました。

「ここが分からない」「ここ間違いでは」などありましたら、コメントなどで教えていただけると嬉しいです。

変更履歴

2021.12.31
IRremoteのESP32の送信ピン割当は4番だったので、それに合わせました。
IRremoteでPWMで情報送信する場合のピン割当はIR_SEND_PINというマクロで定義する仕様だったので、そのマクロをIRremote.hppをincludeする前に停止しました。
2022.02.18
IRremote.hをincludeしていてコンパイルエラーになる記述があったので、IRremote.hppに修正しました。
2023.08.17
RAW_BUFFER_LENGTHが400だと動かないことがあるとコメントで共有していただいたので750に変更しました。

23 件のコメント :

辻田 さんのコメント...

勘違いだったらすみません。
標準のリモコンって、電池交換の頻度を気にするので、1Aも流さないし、電圧も5Vよりも低いと思うんです。だとすると、どうして1A流さないと距離が出ない・反応しないのか考えてみたのですが…

もしかしたら、100Ωの時って、LEDをドライブするパルス波形が鈍っていたりしませんか?MOSFETのゲート容量とか1nFあるようなので。リモコン用の安価なMCUだと、駆動電流値の大きいGPIOで直接LEDドライブしたりしてる気がします。

Asuki Kono さんのコメント...

コメントありがとうございます。
乾電池2本で動いているリモコンの信号強度が強い理由が気になっていましたが、波形の鋭さが関係している可能性があるのですね。
波形も気にしながら消費電力少なめでも十分な信号強度を出せる回路を探ってみます。

Asuki Kono さんのコメント...

エアコンのリモコンは分解しにくかったので秋月電子で売られているリモコンを分解して調べてみました。


オプトサプライの赤外線リモコンのLEDは0.3〜0.14Aの電流で光っている


1Aは流れていませんでしたが、マイコンによるLEDのカソード制御により最大0.3Aほど流れていました。
5V電源で100Ωと電圧降下1.3VのLEDを使う場合は 3.7/100 = 0.037A = 37mAとなり、調べたリモコンの電流量の1/8ほどなので、同等の強度を求めるのは厳しいと分かりました。
12Ωなら約0.3Aになって、同等の強度になりそうです。

Unknown さんのコメント...

インクルードしたIRremoteのスケッチ例にReceiveDumpがないので
このページのリンクからReceiveDumpをコピペして
エアコンの情報を読むためにサンプルコードを変更
ReceiveDumpに
#define LED_BUILTIN 26
#define RAW_BUFFER_LENGTH 400
#define RECORD_GAP_MICROS 12000
を追記してコンパイルすると以下のようなエラーが出ます


Arduino:1.8.19 (Windows 10), ボード:"Arduino Nano, ATmega328P (Old Bootloader)"



IRrecvDumpkai:40:10: fatal error: PinDefinitionsAndMore.h: No such file or directory

compilation terminated.

exit status 1

PinDefinitionsAndMore.h: No such file or directory

どうしたら正常にコンパイルできますか?
超初心者より。

Asuki Kono さんのコメント...

そのエラーは「PinDefinitionsAndMore.h」が無いために発生しています。
ReceiveDumpのそのファイルは下記urlにあります。
https://github.com/Arduino-IRremote/Arduino-IRremote/blob/master/examples/ReceiveDump/PinDefinitionsAndMore.h

現在最新版の3.5.2にはサンプルとしてReceiveDumpが存在しているので、Arduino IDEにインストールしているirRemoteのバージョンが古いのかも知れません。
下記の操作でirRemoteのバージョン確認と、古ければ更新を行うのはどうでしょう?

Arduino IDEで スケッチ -> ライブラリをインクルード -> ライブラリを管理 を選択
表示されたライブラリマネージャの上部の検索欄にirRemoteと入力
絞り込まれて出てくるirRemoteのバージョンを確認
最新の3.5.2でなければ更新

Unknown さんのコメント...

irRemoteで検索して最新版に更新したんですけど出なくて
irRemoteESP8266を取り込んで
PinDefinitionsAndMore.hを取り込むことができました。
しかし以下のエラーがでます

/Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/freertos/port/port_common.c:118: undefined reference to `app_main'

collect2.exe: error: ld returned 1 exit status

exit status 1

ボードESP32 Dev Moduleに対するコンパイル時にエラーが発生しました。
どうしたら良いんでしょうか…度々お尋ねして申し訳ないです。

Asuki Kono さんのコメント...

それはesp-idfで開発時に app_main 関数が定義されてない場合に発生するエラーだと思います。
arduino-esp32環境でビルド出来てないようです。
利用されている環境はArduino IDEですか?
Arduino IDEの場合、OSとIDEのバージョンを知りたいです。
PlatformIOを利用されている場合は platformio.ini の framework を arduino にしたらビルドできるかもしれません。


> irRemoteESP8266を取り込んで

これはirRemoteではない別のライブラリです。
IRremoteESP8266
「取り込む」とはどのような操作や記述を意味しますか?


> 度々お尋ねして申し訳ないです。

問題ありません。
謝罪より感謝をいただけると嬉しいです。

Unknown さんのコメント...

irRemoteESP8266はツール>ライブラリを管理>ライブラリマネージャーからインストールしました

OSはウインドウズ10 Arduino IDE 1.8.19 です 親切にしていただき ありがとうございます

Unknown さんのコメント...

irRemoteは3.52をインストールしました
IRremote
by Armin Joachimsmeyerです

Unknown さんのコメント...

いろいろインストールすれば解決するのかなと思って
IRReniteControl をインストールしてしまいました以下のエラーが出ました

xtensa-esp32-elf-gcc: error: CreateProcess: No such file or directory

exit status 1

ボードESP32 Dev Moduleに対するコンパイル時にエラーが発生しました。

余計なことをしてしまったかもしれません。

Asuki Kono さんのコメント...

> rRemoteESP8266はツール>ライブラリを管理>ライブラリマネージャーからインストールしました
> OSはウインドウズ10 Arduino IDE 1.8.19
> irRemoteは3.52をインストールしました IRremote by Armin Joachimsmeyerです

情報ありがとうございます。
今の状態でも ファイル -> スケッチ例 -> IRremote -> ReceiveDump を選べませんか?


> いろいろインストールすれば解決するのかなと思って
> IRReniteControl をインストールしてしまいました以下のエラーが出ました

現在遭遇しているエラーはライブラリの追加では直らならそうですが、追加でエラーが増える可能性は低いです。


> xtensa-esp32-elf-gcc: error: CreateProcess: No such file or directory

esp-idf向けにビルドが実行されたり、ESP32のビルドに必要なコンパイラが無かったり、arduino-esp32の状態に不具合がありそうです。
Arduino IDEへのESP32環境のインストールはどのように行いましたか?
ちなみに、公式で紹介されている「環境設定」にarduino-esp32のjsonを追加してボードマネージャからインストールする方法が楽で良いと思います。

Installing using Boards Manager

上記のページの「Installing using Boards Manager」から「Windows」の直前までが、arduino-esp32のお勧めのインストール方法です。


ちなみに、ページを開いてコメント欄に入力する前に、関連するコメントの下部に表示される「返信」をクリックしてからコメントを入力してもらうと、関連するやり取りが繋がって嬉しいです。

Unknown さんのコメント...

>今の状態でも ファイル -> スケッチ例 -> IRremote -> ReceiveDump を選べませんか?
選べましたESP32をボードマネージャからインストールし直したら書き込むことができました
シリアルモニタで受信しようとリモコンをつかいましたが送信してもなにも表示されない状態に陥りました。配線がもんだいなのかとおもって何度もみなおしたんですけど駄目でした。
N-ch MOSFETに問題があるのかなっていまはおもってます。諦めようかなっておもってます。
丁寧なご回答ありがとうございました

Asuki Kono さんのコメント...

書き込めたようで良かったです。


> シリアルモニタで受信しようとリモコンをつかいましたが送信してもなにも表示されない

ESP32のリセットボタンを押したらシリアルモニタに起動時のログは表示されますか?
されない場合はCOMポートか通信速度(シリアルモニタの右下で設定可能)が間違っている可能性があります。
リセットボタンをしても表示されない場合はCOMポートを切り替えたり通信速度が115200bpsになっているか確認してみると効果があるかもしれません。


> N-ch MOSFETに問題があるのかなって

受信に関してはMOSFETは関係ありません。
この記事の「回路作成」の図の「ir receiver」さえ正しく配線されれば動くはずなので、その3本に注目するととうでしょう?
受信機が壊れている可能性もあるので、予備があるなら交換しても良いかもしれません。

Unknown さんのコメント...

配線に問題がありました修正したら受信できました
送信するスケッチをコンパイルしたら以下のエラーがでました


C:\Users\momogitune3\OneDrive\�h�L�������g\Arduino\libraries\IRremote\IRremote.cpp:24:10: fatal error: avr/interrupt.h: No such file or directory
「IRremote.h」に対して複数のライブラリが見つかりました
#include
使用済:C:\Users\momogitune3\OneDrive\ドキュメント\Arduino\libraries\IRremote
^~~~~~~~~~~~~~~~~
未使用:C:\Users\momogitune3\OneDrive\ドキュメント\Arduino\libraries\IRremote-2.2.3
compilation terminated.
未使用:C:\Users\momogitune3\OneDrive\ドキュメント\Arduino\libraries\Arduino-IRremote-master
exit status 1
ボードESP32 Dev Moduleに対するコンパイル時にエラーが発生しました。
良かったらまたご教授いただけると幸いです

Asuki Kono さんのコメント...

受信できたようで良かったです。
そのエラーはライブラリ更新によって消えたIRemote.hをincludeしようとして出ています。
IRremote.hをIRremote.hppにすると動くと思います。

記事のプログラムがそのように間違えていたので修正しました。
動作確認情報ありがとうございます。

Unknown さんのコメント...

送信スケッチをコンパイルすることができました。ありがとうございます。しかしLEDが点灯しません抵抗を39Ωにかえてみても光りません配線は確認したんですけど合っているとおもいます、スマホから覗いてみても光りませんでしたエアコンも反応しないです、良かったら助けていただきたいです

Asuki Kono さんのコメント...

コンパイルできてよかったです。
これらを行い、部品や回路の動作確認を行うとどうでしょう?
- 37Ω以上の抵抗+赤外線LEDを5VとGNDに接続してスマホのカメラを通してLEDの点灯を確認
LEDが壊れていなかの確認です。
- 37Ω以上の抵抗でmosfetの回路を組み、mosfetのgateをD4ではなく3.3Vか5Vに接続し、LEDの点灯を確認
mosfetの回路が組めているかESP32のGPIOピン無しで確認です。
- D4と赤外線LED(もしくは他の色のLED)とGNDを接続し、LEDの点灯を確認
D4が動くか確認です。D4から出力される電流は数十mAなので、抵抗なしでLEDを繋いで良いです。(抵抗を付けても良いです。)

Unknown さんのコメント...

抵抗を100Ωにしたらピコピコ点灯したのを確認しました、しかしスイッチを押してもなにも反応ないです

- 37Ω以上の抵抗+赤外線LEDを5VとGNDに接続してスマホのカメラを通してLEDの点灯を確認
LEDが壊れていなかの確認です。

100Ωの抵抗でひかりました

- 37Ω以上の抵抗でmosfetの回路を組み、mosfetのgateをD4ではなく3.3Vか5Vに接続し、LEDの点灯を確認
mosfetの回路が組めているかESP32のGPIOピン無しで確認です。

こちらも光りました

- D4と赤外線LED(もしくは他の色のLED)とGNDを接続し、LEDの点灯を確認
D4が動くか確認です。D4から出力される電流は数十mAなので、抵抗なしでLEDを繋いで良いです。(抵抗を付けても良いです。)
光りました

どこに問題があるんでしょうか?

Asuki Kono さんのコメント...

共有してもらった確認内容からすると、回路はできていそうです。

> しかしスイッチを押してもなにも反応ないです
これはLEDも光らず、シリアルモニタに情報の出力も無いということでしょうか。
シリアルモニタに出力があり、LEDが光っているなら、何かしらの信号を赤外線LEDで送信しようとはしています。
その場合、「LEDの出力が弱くて信号が装置に届いていない」か「送信している信号が壊れていて装置が受け付けない」のが原因として考えられます。

装置に近づけて信号を送信してみるか、リモコンの信号を読み直してみるのはどうでしょう?

Unknown さんのコメント...

>これはLEDも光らず、シリアルモニタに情報の出力も無いということでしょうか。
シリアルモニタはこんな感じです
send trun off at 94222
send trun off at 94498
send trun off at 94773
send trun off at 95050
send trun off at 95326
send trun off at 95602
send trun off at 95878
send trun off at 96154
send trun off at 96430
send trun off at 96706
send trun off at 96982
send trun off at 97258
send trun off at 97534
send trun off at 97810
send trun off at 98086
send trun off at 98362
send trun off at 98637
send trun off at 98914
send trun off at 99190
ボタンを押す前から何か送信していてボタンを押しても何も変化がないようです

>シリアルモニタに出力があり、LEDが光っているなら、何かしらの信号を赤外線LEDで送信しようとはしています。
その場合、「LEDの出力が弱くて信号が装置に届いていない」か「送信している信号が壊れていて装置が受け付けない」のが原因として考えられます。

エアコンに近づけてボタンを押したんですけど何も起きませんでした

リモコンの信号を更新しても駄目でした。

Asuki Kono さんのコメント...

> シリアルモニタはこんな感じです
> send trun off at 94222

これはプログラムの下記の行が毎回実行されており、18番ピンに繋がったスイッチがずっと押されている状態とみなされています。

} else if (digitalRead(PIN_BUTTON_TURN_OFF) == LOW) {
IrSender.sendPronto(prontoDataTurnOff22Heater);
Serial.println("send trun off at " + String(millis()));
}

18番に繋がっているスイッチは取り外し、ESP32開発ボードのD0ボタン(リセットボタンでは無い方)を押して信号を送信するとどうなりますか?
また、プログラムのsetup関数直前の下記の行はご自身でリモコンから読み取った情報に書き換えていますか?

char prontoDataTurnOn22Heater[] = "";
char prontoDataTurnOff22Heater[] = "";


ESP32の開発ボードがもう1台あれば受信機を作り、ESP32のLEDがリモコンと同じ情報を発信しているか見てみるのは、確認方法の1つです。

ON さんのコメント...

はじめまして、こちらのページを参考にさせていただいて無事にリモコンのコピー&送信をすることに成功しました。ありがとうございます。
リモコンによってはバッファが400では足りないようで、エアコンの場合は750がサンプルプログラムの既定値として推奨されているようです。
https://dreamerdream.hateblo.jp/entry/2023/08/11/173000
報告として挙げさせていただきます。

Asuki Kono さんのコメント...

情報ありがとうございます
RAW_BUFFER_LENGTHが400では足りない場合があるのですね
サンプルコードで750の利用を勧めている箇所も確認しました
頂いた情報を元に、記事での推奨値を750に更新しました