気の向くままに辿るIT/ICT/IoT
webzoit.net
IoT・電子工作

ESP-01/12/ESP32で東芝エアコン大清快をWiFi操作

ウェブ造ホーム前へ次へ
サイト内検索
カスタム検索
ESP8266って?

ESP-01/12/ESP32で東芝エアコン大清快をWiFi操作

ESP-01/12/ESP32で東芝エアコン大清快をWiFi操作

自作スマートリモコンで操作する東芝 大清快RAS-E405R(W)ベースRAS-E405E6R(W)
2019/05/02

 Wi-Fi(wifi)モジュールESP8266/ESP32の開発ボードを使ってAC100V含む赤外線リモコン対応家電や自作スマートコンセントで非IRリモコン家電を無線で遠隔操作できるリモコン信号送信装置、いわゆるスマートリモコンを自作するシリーズ。

 ESP8266/ESP-WROOM-32チップ単体やピッチ変換モジュールとの併用はより省スペースではありますが、ESP8266/ESP32開発ボードを使う方が、何かと手間もなく、無難です。

 今回は、最近買って今日届いたエアコン東芝 大清快用のスマートリモコンを作りました。

 と言っても既にESP-01/12/ESP32でSHARP製エアコンをWiFi操作させており、方法は全く同じです。

 当初、パソコンやタブレット、スマホのブラウザから、これら家電を遠隔操作することを想定していましたが、今となっては、自身は、自作スマートスピーカーメインPCにも搭載の自作スマートスピーカー機能を使って音声で操作するのがメインとなっています。

 よってブラウザからの無線操作のみならず、自作ラズパイスマートスピーカーでエアコンを音声操作可能にします。

 ちなみに東芝 大清快は、WiFi対応でスマホから操作できたり、VOiCEコントローラなる専用スマートスピーカーがあるようです。

 が、前者については、外から使う予定がないこと、使うにしてもIFTTTなどを使えば済みそうなこと、利用にあたっては、有料会員登録が必要な模様であること、自作スマートリモコンを使えばよいこと、後者については、別途購入が必要な模様であること、対応は機種によるらしいのですが、対応しているのか否かよくわからないこと、これも自作のスマートスピーカーがあることから、何れも自作品を使うことにします。

操作メニュー

東芝 大清快RAS-E405RWベースRAS-E405E6RWSのリモコン

 今回はとりあえず、ecoモード、暖房、冷房、除湿、空気清浄機能(プラズマ空清)の運転・停止、設定温度の上げ下げ、節電ボタン(10秒長押し)あたりを実装することにしました。

事前準備

 スマートリモコンを作るにあたっては、全ては「操作(ボタン・メニュー)に対して、どんな並びの赤外線信号を送信するか」であり、基本的に、これら以外の違いはなく、家電による差もない為、ハードウェアもソフトウェアも共通。

 よって作り方の詳細は、冒頭の自作スマートリモコンのリンク先に譲ります。

 事前準備としては、markszabo/IRremoteESP8266などESP8266用の任意のIR信号送受信ライブラリを使い、ESP8266で送受信回路を作って機能させたい家電のリモコンから受信機に信号を送信、これを解析して(読み取って)おき、操作ボタンと信号のリストを作っておきます。

 尚、今回の家電はエアコン(東芝製)ですが、IRremoteESP8266のIRrecvDump/IRrecvDumpV2では(信号方式としては)、UNKNOWNだったので、sendRaw()関数を使って生(raw)データを送ることにしました。

回路とスケッチ・プログラム

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <Arduino.h>
#include <FS.h>
 
const char* path_root   = "/index.html";
 
  const char *ssid = "ssid";
  const char *password = "password";
 
//#define BUFFER_SIZE 16384
//uint8_t buf[BUFFER_SIZE];
 
uint16_t len;
uint16_t freq = 38;
 
ESP8266WebServer server ( 80 );
IRsend irsend(4);
 
boolean readHTML() {
  File htmlFile = SPIFFS.open(path_root, "r");
  if (!htmlFile) {
    Serial.println("Failed to open index.html");
    return false;
  }
  size_t size = htmlFile.size();
  if (size >= BUFFER_SIZE) {
    Serial.print("File Size Error:");
    Serial.println((int)size);
  } else {
    Serial.print("File Size OK:");
    Serial.println((int)size);
  }
//  htmlFile.read(buf, size);
  htmlFile.close();
  return true;
}
 
void handleRoot() {
  Serial.println("Access");
  char temp[100];
  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;
 
  snprintf ( temp, 100, "", hr, min % 60, sec % 60 );
  server.send(200, "text/html", (char *)buf);
}
 
uint16_t eco_mode[] = {
 
4476, 4352, 602, 1592, 572, 1594, 572, ...., 572, 1594, 568
};
uint16_t cooler[] = {
...
};
uint16_t heater[] = {
...
};
uint16_t dry[] = {
...
};
...
 
void Eco_Mode() {
  Serial.println("ECO");
  len = sizeof(auto_drive) / sizeof(uint16_t);
  irsend.sendRaw(auto_drive, len, freq);
  delay(10);
  irsend.sendRaw(auto_drive, len, freq);
  delay(2000);
  server.send(200, "text/html", "ECO");
}
void Cooler() {
  Serial.println("COOLER");
  len = sizeof(cooler) / sizeof(uint16_t);
  irsend.sendRaw(cooler, len, freq);
  delay(10);
  irsend.sendRaw(cooler, len, freq);
  delay(2000);
  server.send(200, "text/html", "COOLER");
}
void Heater() {
  Serial.println("HEATER");
  len = sizeof(heater) / sizeof(uint16_t);
  irsend.sendRaw(heater, len, freq);
  delay(10);
  irsend.sendRaw(heater, len, freq);
  delay(2000);
  server.send(200, "text/html", "HEATER");
}
void Dry() {
  Serial.println("DRY");
  len = sizeof(dry) / sizeof(uint16_t);
  irsend.sendRaw(dry, len, freq);
  delay(10);
  irsend.sendRaw(dry, len, freq);
  delay(2000);
  server.send(200, "text/html", "DRY");
}
...
void handleNotFound() {
 
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
 
  for ( uint8_t i = 0; i < server.args(); i++ ) {
    message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
  }
  server.send ( 404, "text/plain", message );
}
 
void setup() {
  Serial.begin(115200);
 
  SPIFFS.begin();
  if (!readHTML()) {
    Serial.println("Read HTML error!!");
  }
 
  WiFi.begin(ssid, password);
  irsend.begin();
  Serial.println("");
  // AP+STAモードの設定
//  WiFi.mode(WIFI_AP_STA);
  WiFi.mode(WIFI_STA);
 
  //wait for connection
  while ( WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  if (!MDNS.begin("esptoshibaaircon")) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
 
  server.on("/", handleRoot);
  server.on("/ECO_MODE", Eco_Mode);
  server.on("/COOLER", Cooler);
  server.on("/HEATER", Heater);
  server.on("/DRY", Dry);
  ...
  server.onNotFound(handleNotFound);
 
  server.begin();
  Serial.println("HTTP server started");
 
  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
}
 
void loop() {
  server.handleClient();
}

 ライブラリには、IRremoteESP8266を使わせて頂きました。

 自身もそうしましたが、この手のESP8266のスケッチ・プログラム概要としては、Webサーバを立てSPIFFSによりESP8266のメモリ上にトップページに各種ボタンを配置した操作画面となるHTMLファイルを置き、他に操作ごとのページ(URLだけあればよくHTMLファイルは不要)を作り、そこにアクセスするとそれぞれの操作信号を送信するという作りにするのが一般的でしょう。

 APモードにする必要はないので、ここではステーションモードとしています。

 mDNSは、仮にesptoshibaairconとしたのでesptoshibaaircon.localでPCブラウザなどからアクセスでき、SPIFFSでHTMLファイルをアップロードしていれば、例えば、操作画面が表示され、esptoshibaaircon.local/ECO_MODEにアクセスすると個別にエコモードでエアコンを起動操作できるようになっています。

 今回、エアコンで使うことになったirsend.sendRaw()の引数は、uint16_tだったため、IRrecvDump/IRrecvDumpV2ではunsigned intだったrawデータの型、データ長格納用変数もuint16_t型とし、定数であるリモコンで多く使われるという周波数38(kHz)もuint16_t型の変数に代入しました。

 ただ、htmlFile.read(buf, sizeでつまづき、SPIFFについては、(試してないのでなんですが、たぶん)未解決です。

 それでもブラウザからは操作できているので、とりあえず、よしとしました。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <Arduino.h>
#include <FS.h>
 
const char* path_root   = "/index.html";
 
  const char *ssid = "ssid";
  const char *password = "password";
 
#define BUFFER_SIZE 16384
uint8_t buf[BUFFER_SIZE];
 
uint16_t len;
uint16_t freq = 38;
 
ESP8266WebServer server ( 80 );
IRsend irsend(4);
 
boolean readHTML() {
  File htmlFile = SPIFFS.open(path_root, "r");
  if (!htmlFile) {
    Serial.println("Failed to open index.html");
    return false;
  }
  size_t size = htmlFile.size();
  if (size >= BUFFER_SIZE) {
    Serial.print("File Size Error:");
    Serial.println((int)size);
  } else {
    Serial.print("File Size OK:");
    Serial.println((int)size);
  }
  htmlFile.read(buf, size);
  htmlFile.close();
  return true;
}
 
void handleRoot() {
  Serial.println("Access");
 
  server.send(200, "text/html", (char *)buf);
 
  char message[20];
  String(server.arg(0)).toCharArray(message,20);
 
  if(server.arg(0).indexOf("ECO_MODE") != -1){
    Serial.println("ECO_MODE");
    Eco_Mode();
  }
  else if(server.arg(0).indexOf("COOLER") != -1){
    Serial.println("COOLER");
    Cooler();
  }
 ...
}
 
uint16_t eco_mode[] = {
 
4476, 4352, 602, 1592, 572, 1594, 572, ...., 572, 1594, 568
};
uint16_t cooler[] = {
...
};
uint16_t heater[] = {
...
};
uint16_t dry[] = {
...
};
...
 
void Eco_Mode() {
  Serial.println("ECO");
  len = sizeof(auto_drive) / sizeof(uint16_t);
  irsend.sendRaw(auto_drive, len, freq);
  delay(10);
  irsend.sendRaw(auto_drive, len, freq);
  delay(2000);
  server.send(200, "text/html", "ECO");
}
void Cooler() {
  Serial.println("COOLER");
  len = sizeof(cooler) / sizeof(uint16_t);
  irsend.sendRaw(cooler, len, freq);
  delay(10);
  irsend.sendRaw(cooler, len, freq);
  delay(2000);
  server.send(200, "text/html", "COOLER");
}
void Heater() {
  Serial.println("HEATER");
  len = sizeof(heater) / sizeof(uint16_t);
  irsend.sendRaw(heater, len, freq);
  delay(10);
  irsend.sendRaw(heater, len, freq);
  delay(2000);
  server.send(200, "text/html", "HEATER");
}
void Dry() {
  Serial.println("DRY");
  len = sizeof(dry) / sizeof(uint16_t);
  irsend.sendRaw(dry, len, freq);
  delay(10);
  irsend.sendRaw(dry, len, freq);
  delay(2000);
  server.send(200, "text/html", "DRY");
}
...
void handleNotFound() {
 
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
 
  for ( uint8_t i = 0; i < server.args(); i++ ) {
    message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
  }
  server.send ( 404, "text/plain", message );
}
 
void setup() {
  Serial.begin(115200);
 
  SPIFFS.begin();
  if (!readHTML()) {
    Serial.println("Read HTML error!!");
  }
 
  WiFi.begin(ssid, password);
  irsend.begin();
  Serial.println("");
  // AP+STAモードの設定
//  WiFi.mode(WIFI_AP_STA);
  WiFi.mode(WIFI_STA);
 
  //wait for connection
  while ( WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  if (!MDNS.begin("esptoshibaaircon")) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
 
  server.on("/", handleRoot);
  server.on("/ECO_MODE", Eco_Mode);
  server.on("/COOLER", Cooler);
  server.on("/HEATER", Heater);
  server.on("/DRY", Dry);
  ...
  server.onNotFound(handleNotFound);
 
  server.begin();
  Serial.println("HTTP server started");
 
  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
}
 
void loop() {
  server.handleClient();
}

[2019/05/06 訂正・追記:]
 勘違い...、太字で強調しましたが、こんな風にしたら、SPIFFSで操作パネルを表示する恰好でいけました。
 元々あった2箇所3行のコメント行をやっぱり有効にする。
 handleRoot()内にコマンドの数だけ条件分岐を追記(これを忘れてたのが元凶)。
 これは前段のスケッチでも修正済みですが、void Power()など各関数のデータ長計算時の分母の方のsizeof()の引数をuint8_tからuint16_tに修正。
 あと前掲のスケッチは、bufやBUFFER_SIZEをコメントアウトしただけで代替がないのでserver.send(200, "text/html", (char *)buf);行でエラーになります...ね、すみません。

<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
<title>ESP8266 Home Automation</title>
<style>
body { width: 90% ;background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }
form { margin:10px ;padding:10px ;width: 100% ; }
form input { margin:2px ;padding:2px ;width: 40% ;height:100px ;background-color:blue ;color:yellow ;font-size:110% ; }
h1 { font-size: 80% ; }
</style>
</head>
<body>
<h1>ESPスマートホーム操作パネル</h1>
<form action="/" method="post">
<input type="button" name="home" value="メインメニュー" class="home" onClick="location.href='http://esphamainsrv.local'">

<input type="submit" name="eco" value="エコ">
<input type="submit" name="aircon" value="冷房">
<input type="submit" name="aircleaner" value="暖房">
<!-- ... -->
</form>
</body></html>

 同じく、SPIFFSでサーバとするESP8266/ESP32ボードのメモリ上に保存するエアコン用のHTMLファイルは、こんな感じにします。

 尚、inputタグのvalue値を日本語にするとブラウザ上のボタン表記も日本になりますが、この場合、スケッチ側のindexOf()判定も同様にする必要があります(先のスケッチでは英数字になっています)。

 onClickイベントを使っている方は、inputタグのtype属性値をbuttonに、ESP8266/ESP32にデータを投げる方は、typeをsubmitにしないと機能しないので要注意です。

 ちなみにメモリ上に配置するファイルサイズさえ許容されれば、HTMLファイル上で使うCSSやJavaScriptは、あくまでスマホ、タブレットやPCのブラウザで処理されることである為、ESP8266/ESP32ボードで使えるかどうかを気にかける必要はありません。

スマホブラウザ版エアコン用操作パネル例

 前掲のスケッチでは、mDNSをesptoshibaairconとしたのでブラウザでesptoshibaaircon.localにアクセス、HTMLファイルを先のように書いてあるとこんな感じになります。

 メインメニューに戻ると例えば、メイン操作パネルのようになります。

 HTMLファイル例に書いたようにメインメニュー用のESP8266/ESP32サーバの方は、mDNSをesphamainsrvとしたものと想定し、location.hrefにesphamainsrv.localを設定しています。

 もちろん、無線なのでAC接続USB充電器やモバイルバッテリにつないだESP8266/ESP32などと相互にxxx.localでもIPアドレスでもアクセスできます。

エアコン用スマートリモコン赤外線信号発信器の設置[2019/05/18追記]

 エアコンに自作スマートリモコンのESP8266/ESP32赤外線信号発信器を設置してみました。

エアコンに設置した自作スマートリモコン信号発信器1

 配線カバーを使ってエアコン反対側面の床面付近にある壁面埋め込みコンセントからAmazonで買った3mの1口電源延長コードを這わせたところ、若干長すぎた、かつ、コンセントプラグやUSB充電器がエアコンに近いと配置しづらいと思い、迂回することに。

 ESPモジュール(開発ボード)を入れるケースには、テレビでも使うことにしたダイソーで5個108円、青色半透明のミニケースを、上面以外を白く塗るなり、白半透明、無色透明ケースを探すなりしてもよかったのですが、邪魔になる色味でもないのでよしとしました。

 USB充電器はダイソーの200円商品の白、USBケーブルは、ちょっと長いなと思いつつも100均のAndroid/iPhone共用ケーブルの白 50cmを使おうと思っていたのですが、途中、内部断線したのか、通電しなくなったので、やはり、100均の30cm平ケーブル 黒に変更しました。

エアコンに設置した自作スマートリモコン信号発信器2

 回路は、意味もなく、いつも選んでしまう220Ωのカーボン抵抗と赤外線LEDのアノード、これらをピンヘッダなしのESP開発ボードの信号用GPIO/GNDに直接はんだ付けしました。

 赤外線LEDは1個、やや上向きとは言え、正面方向を向いていますが、送信した信号は、その上の操作時アイコンなど運転状態が表示されるエアコン傾斜部分にあると思われる赤外線受信機に届いています。

 ケースは、エアコン面に白い養生テープを貼り、両面テープ付きマジックテープで固定、割りと広めに貼ったので多少なら前後左右にずらすことも可能、そうした機会はあっても稀でしょうが、ケースのフタ側を下にすることでケースを外さなくてもESPボードを取り出しやすいようにしました。

リモコンとしてのESP8266

 ESP8266とリモコン参照。

備考

 エアコン操作用自作リモコンの場合、設置位置には注意が必要です。

 赤外線LEDにもよるでしょうし、LED先端付近において光線が拡散しない工夫の有無にもよるでしょうが、自身が今回使ったものは、指向性が高い(向きが重要な)ものであり、赤外線LEDは、ブレッドボードに挿した状態で使用しました。

 赤外線LEDを3つ装着して試してみたところ、エアコン側の受光部と水平位置であれば、1m程度離れても、多少の傾きなら下方向からも操作できましたが、あまり角度を付けると操作できませんでした。

 ちなみに赤外線LEDが1つしかない専用リモコンでは、2〜3m離れても、広角でも操作できます。

 テレビなどなら受光部付近に設置しやすいですが、エアコンは、往々にして天井付近にあるので常用するなら、赤外線LEDの光線を増強して自作リモコン(赤外線発信器)置き場に自由度をもたせるか、光線が届くよう工夫して設置する必要があります。

 自身は、エアコンの受光部付近に自作の発信機を配置する予定なので信号の増強は考えていません。

 欲を言えば、エアコンに限っては、音声やスマートリモコンから操作した場合に備えて例えば、エアコン専用リモコンにON/OFFボタンなどがついていてリモコンの電源を入れたらエアコン本体の運転状態を自動受信してくれたりするとありがたいんですけどね...。

 現状だと例えば、音声操作時とエアコン専用リモコンの温度設定が異なる場合、音声(遠隔)操作後、専用リモコンを併用しようと思うと微妙な状況も...。

ウェブ造ホーム前へ次へ