アレクサ、お風呂沸かして

published_with_changes 更新日: event_note 公開日:

labelamazon alexa labelESP8266

アレクサにやってもらっていることで、一番便利に使えてるのがこれかぁと思っています。これは、自作Alexaデバイスの2作目になります。

アレクサに頼んでいる動画です。



仕様

  • 給湯器のリモコンをAlexaデバイスにする
  • Alexaへ頼むのは、運転(入/切)、自動(湯はり入/切)、おいだき(入/切)
  • 各々の入/切は状態を確認できるようにする

しくみ

私は結構なAmazonファンで、映像、音楽のサブスクリプション サービスの利用を始め、Echoデバイスを家中に置いて、どこからでもAlexaに話しかけれるようにしています。テレビとかエアコンは市販のIOTデバイスを利用して操作できるようになっています。これを自作Alexaデバイスでも行おうという内容です。

しくみを説明するのに最適な図と文があったので、拝借させていただきます。

fauxmoESPを単純にalexaと連携させて、aleaxからの音声制御にてESP8266のポートをon、offさせて他の機器を制御するという簡単な仕組みを目指します。<省略>
alexaで蛍光灯を制御する全体図は↓こんな感じ。第3世代のamazon echoを使うので、ESP8266とalexaを連携するライブラリはfauxmoESPというライブラリを使用します。
引用元:ESP8266とalexaで家電を制御する fauxmoESP編 ?照明を音声コントロール?


Arduino類の小型マイコンでAlexaデバイスを作れるライブラリがGitHubにあります。ESP8266とEPS32用のライブラリが揃っているようです。今回はESP8266を使います。

Echoデバイスを通してAlexaアプリが命令を音声認識すると、小型マイコンのESP8266と連携して給湯器のリモコンをON/OFF操作するという流れになります。

給湯器リモコンの改造

概要

我が家の給湯器リモコンはリンナイのMC-220Vという機種で、当たり前ですが、指でスイッチのON/OFFを行います。このスイッチと並列にフォトカプラを設け、ESP8266の信号でフォトカプラの一次側(1アノードと2カソード間)をON/OFFすると、2次側(3エミッタと4コレクタ間)の回路がスイッチングするように改造します。

フォトカプラは一次側と二次側が電気的に絶縁されているので、一次側の信号が二次側に紛れ込んで誤動作させるという不具合を防ぐことができます。給湯器というガスを使う製品ですから安全最優先で考えます。

フォトカプラ TLP785 (TOSHIBA製)

リモコン分解

リモコンのMC-220Vはドライバーなしで分解できました。裏から赤のツメを外すと基板は黄色のツメで固定されているだけです。


表面(液晶面)にスイッチが3つ並んでいます。右から、次のようになっていました。スイッチの一次電圧は5Vでした。
  • 運転入/切         SW4 スイッチONで信号線(5V)をGNDと短絡
  • 自動(風呂給湯)SW3 スイッチONで信号線(5V)をGNDと短絡
  • おいだき          SW2 スイッチONで信号線(5V)をGNDと短絡


SW4には結線可能なピン穴(黄色丸)がありましたが、他のSWには適切なピン穴がなく、各々の信号線はタクトスイッチの足に半田付けすることにしました。


スイッチ検出箇所と動作

安全最優先の考えを徹底するため、2次側のスイッチの通電/非通電の状態を検出するようにします。このリモコンはスイッチONでそれぞれのLEDが点灯するタイプでしたので、スイッチ動作の検出用にこのLED信号を利用することにしました。ESP8266がこの状態を読みとり、不要な操作やありえない操作、あってはならない操作を禁止するソフトにします。LEDへかかる電圧を調べたところ、信号電圧は下表の値でしたので、ESP8266はLow/Highで読み取りが可能であることがわかりました。

スイッチ信号検出箇所ON時OFF時
運転入/切LED点燈信号0V3.9V
自動(湯はり)LED点燈信号0V 0.99sec3.9V 1.2sec繰り返し
おいだきLED点燈信号0V 0.99sec3.9V 1.2sec繰り返し

運転入/切

自動(湯はり)

おいだき

基板改造

スイッチ信号3つとひとつのLED信号は基板の表から取り出しました。


残り2つのLED信号は、運転と自動(湯はり)ですが、基板裏から取り出します。


リード線はリモコン背面で束ねてESP8266へつなぎます。ハンダが剥離しないようリード線は要所要所をホットボンドで基板に固定しておきました。



制御回路

ことば使いは大袈裟ですが、フォトカプラ側の回路を示します。

予備実験

フォトカプラはTLP785を使用します。まず、フォトカプラでうまくリモコンスイッチが代行できるのか確認実験を行います。

リモコンのタクトスイッチの電流を5mAと仮定して、1kΩの抵抗をフォトカプラのエミッタ、コレクタ間につないだバイパス回路を作って試してみました。フォトカプラの一次側のON/OFFで、うまくリモコンが操作でき給湯器が作動しました。

2次側(リモコンスイッチ置き換え)

回路を簡単にするために一次側のLEDの電流制限抵抗(順電流を与える抵抗)を1個で考えます。というのは、このリモコンはモメンタリーなプッシュスイッチです。3つ並列に並んでいますが、それらを同時に押すことはありません。フォトカプラではONがモメンタリーになるようにON時間を決めます。すなわちフォトカプラが3個並んでいますが、実際はひとつづつしかONにしませんので一つの電流制限抵抗で設計します。

利用するESP8266(Wemos D1 mini)のデジタルpin出力はHighで3.3V、電流は最大12mAです。

データシートによるとコレクタ電流(2次側電流)5mAを流せる一次側の順電流は3mAです。また、推奨の順電流は16mA(標準)、順電圧1.3V(最大)です。デジタルpinの電流制限を考慮して9mA(12mAの-25%)流すことを考えます。よって、下記図の回路にしてフォトカプラをひとつだけ使うときは9mAが流れます。

順電流9mAを流せる抵抗は、(3.3-1.3V)/(9/1000)=222Ω なので、手持ちの220Ωにします。


制御回路基板

できた基板です。ESP8266はヘッダpinで基板に重ね付けします。決めたピン配は下表に示します。


機能リード線HeaderESP8266
pin配置(上から)pin (D#)
運転 入/切 LED113
湯はり自動 LED214
おいだき LED35
Vccオレンジ45V
GND5GND
おいだき SW616
湯はり自動 SW712
運転 入/切 SW815

ソフトウエアをつくる

シミュレートツール

ソフト開発に給湯器リモコンを使いっぱなしにするわけにはいかないので、リモコンを模擬できる回路をブレッドボードで組みました。スイッチ3個、LED3個がArduino Leonardoで動きます。下に映っているESP8266が給湯器リモコン用のAlexaデバイスになります。フォトカプラはもちろんこのESP8266で動かします。


Arduino初心者だったので、タクトスイッチのチャタリングに結構悩まされました。安定して再現性良くスイッチが切り換ってくれないとソフトの検証になりません。チャタリング対策に<Switches.h>というライブラリを利用させていただきました。このライブラリのおかげて安定した再現性のあるタクトスイッチの切り換えができるようになりました。

シミュレートツールのスケッチ


/*

  Toggle.ino

  トグルスイッチを押すごとに、LEDの点灯と消灯を繰り返す。
  LED1は点灯/消灯。
  LED2とLED3は1秒間隔の点滅/消灯
  
*/

#include <Switches.h>

Switches sw1(7, false, SWITCH_HtoL);
Switches sw2(6, false, SWITCH_HtoL);
Switches sw3(5, false, SWITCH_HtoL);
//           ^   ^         ^
//           |   |         |
//           |   |         +- SWITCH_LEVEL:  ピンがHのとき、trueを返す。
//           |   |            SWITCH_NLEVEL: ピンがLのとき、trueを返す。
//           |   |            SWITCH_HtoL:   ピンの値がHからLに変化したとき(省略時)、trueを返す。
//           |   |            SWITCH_LtoH:   ピンの値がLからHに変化したとき、trueを返す。
//           |   +- true: enable pullup register, false: disable
//           |
//           +--------- pin number

const int ledPin1 = 12;
const int ledPin2 = 11;
const int ledPin3 = 10;
bool val1 = LOW;
bool val2 = LOW;
bool val3 = LOW;

boolean state2 = false;        // LED2のステータス用
boolean state3 = false;        // LED3のステータス用

void setup() {
  pinMode(ledPin1, HIGH);
  digitalWrite(ledPin1, val1);
  pinMode(ledPin2, HIGH);
  digitalWrite(ledPin2, val2);
  pinMode(ledPin3, HIGH);
  digitalWrite(ledPin3, val3);

}

void loop() {
  if (sw1()) {
    val1 = !val1;
    digitalWrite(ledPin1, val1);
  }

    if (sw2()) {state2=!state2;}

    if (state2){
    val2 = !val2;
    digitalWrite(ledPin2, val2);
    delay(1000);}
    else {
    digitalWrite(ledPin2, HIGH);}

  if (sw3()) {state3=!state3;}

  if(state3){
    val3 = !val3;
    digitalWrite(ledPin3, val3);
    delay(1000);}
  else {
    digitalWrite(ledPin3, HIGH);}
}
   


Alexaライブラリ

ESP8266をAlexaデバイスにするには専用のライブラリを使う必要があります。

最初に引用させていただいたブログ始め、よく利用されているライブラリは『fauxmoESP』です。fauxmoESPについて調べていくと、「fauxmoESPは10台以上のデバイスでは使えない」らしいです。ひとつのライブラリ(Arduino 1個)で10台が使えないのか、Alexaアプリ全体で10台が使えないのか、ちゃんと調べる前に制限のない他のライブラリを探しました。その時点でAlexaアプリに登録しているデバイスは、ライトやエアコンなど10台を超えていました。今になって思えば、きっと前者の制限だろうと思うのですが、検証もその後の調査もしていません。

Espalexa

そこで、デバイス数の制限のない「Espalexa」というライブラリを利用することにしました。 ESP8266とESP32で使えるようです。使ってみてわかったのですが、Phillips社のHueというスマート電球向けのライブラリのようです。Alexaアプリには照明デバイスとして登録されます。

ロジックについて考えてみた

リモコンスイッチ事象を考えてみたのが、次のメモです。赤文字の条件でスイッチを作動させることにしました。



Alexaデバイスのスケッチ

if文で個々のスイッチが有効になる条件を決めていますが、基本はサンプルスケッチを組み合わせただけです。wifiで繋がります。

/*
 * Espalexaでデバイスを制御する基本形(シンプルな1個のON/OFFスイッチ)を利用して
 * 給湯器をAlexaでonn/offする */ 
 
#ifdef ARDUINO_ARCH_ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Espalexa5.h>

#include <Switches.h>

// prototypes
boolean connectWifi();

//callback functionsコールバック関数のプロトタイプ
void untenChanged(uint8_t brightness);//シンプルなon/offだが、brightnessを使ってみる
void jidouChanged(EspalexaDevice* dev);
void oidakiChanged(EspalexaDevice* dev);

// Change this!!
const char* ssid = "********";
const char* password = "********";

boolean wifiConnected = false;

Espalexa espalexa;

const int untenPin =15;     //D8; output 一次側 フォトカプラ pin#1
const int untLEDPin =13;    //D7; input 二次側(状況のモニター)LED (+)
const int jidouPin =12;     //D6; output 一次側  フォトカプラ pin#1
const int jidLEDPin =14;    //D5; input 二次側(状況のモニター)LED (+)
const int oidakiPin =16;     //D0; output 一次側  フォトカプラ pin#1
const int oidLEDPin =5;     //D1; input 二次側(状況のモニター)LED (+)

const int holdTime = 2000;

unsigned long prev, curr, interval;

Switches sw1(untLEDPin, false, SWITCH_LEVEL);//お湯LED H(消灯) がtrue
Switches sw2(jidLEDPin, false, SWITCH_HtoL);//お風呂LED HからL がtrue
Switches sw3(jidLEDPin, false, SWITCH_LEVEL);//お風呂LED H(消灯) がtrue
Switches sw4(oidLEDPin, false, SWITCH_LEVEL);//おいだきLED H(消灯) がtrue
//           ^   ^         ^
//           |   |         |
//           |   |         +- SWITCH_LEVEL:  ピンがHのとき、trueを返す。
//           |   |            SWITCH_NLEVEL: ピンがLのとき、trueを返す。
//           |   |            SWITCH_HtoL:   ピンの値がHからLに変化したとき(省略時)、trueを返す。
//           |   |            SWITCH_LtoH:   ピンの値がLからHに変化したとき、trueを返す。
//           |   +- true: enable pullup register, false: disable
//           |
//           +--------- pin number

//EspalexaDevice* device3; //this is optional

void setup()
{

  interval = 60*60*1000;   // おいだきを有効とする時間を設定(分)

  Serial.begin(115200);
  
  pinMode(untLEDPin,INPUT); 
  pinMode(untenPin,OUTPUT);
  pinMode(jidLEDPin,INPUT); 
  pinMode(jidouPin,OUTPUT);
  pinMode(oidLEDPin,INPUT); 
  pinMode(oidakiPin,OUTPUT);

  
  // Initialise wifi connection
  wifiConnected = connectWifi();
  
  if(wifiConnected){
    
    // Define your devices here. 
	espalexa.addDevice("Unten", untenChanged); //simplest definition, default state off最 も単純な定義、デフォルトの状態はオフ
	espalexa.addDevice("Jidou", jidouChanged, EspalexaDeviceType::onoff); //non-dimmable device オンオフのみ
  espalexa.addDevice("Oidaki", oidakiChanged, EspalexaDeviceType::onoff); //non-dimmable device オンオフのみ


    espalexa.begin();
    
  } else
  {
    while (1) {
      Serial.println("Cannot connect to WiFi. Please check data and reset the ESP.");
      delay(2500);
    }
  }
}
 
void loop()
{
   espalexa.loop();
   delay(1);
   if (sw2()){prev=millis();
     //Serial.println("Start to count new elapsed time");
   }
}


//our callback functionsコールバック関数
void untenChanged(uint8_t brightness) {
//  if (d == nullptr) return; //this is good practice, but not requiredこれは良いやり方ですが、必須ではありません

  //do what you need to do hereここで必要なことをしてください
  //EXAMPLE

  Serial.print("Unten SW changed to ");
  if (brightness > 128 && sw1()){ 
    Serial.println((String)"ON, brightness "+brightness);
    digitalWrite(untenPin,HIGH);
    delay(holdTime);
    digitalWrite(untenPin,LOW);
    Serial.println((String)"Switch on for "+holdTime/1000+" sec");     
  }
  else if (brightness <= 128 && !sw1()) {
    Serial.println((String)"OFF, brightness "+brightness);
      digitalWrite(untenPin,HIGH);
      delay(holdTime);
      digitalWrite(untenPin,LOW);
    Serial.println("Not receiving signal now !"); 
  }
  else {return;}
}


void jidouChanged(EspalexaDevice* d) {
  if (d == nullptr) return; //this is good practice, but not requiredこれは良いやり方ですが、必須ではありません

  //do what you need to do hereここで必要なことをしてください
  //EXAMPLE

  Serial.print("Jidou changed to ");
  if (d->getValue()){

      if ( sw1() && sw3() ){ //お湯LEDが消灯 かつ お風呂LEDが消灯
        Serial.println("ON");
        digitalWrite(untenPin,HIGH); //お湯LEDを点灯
        delay(holdTime);
        digitalWrite(untenPin,LOW);
        Serial.println((String)"お湯 Switch on for "+holdTime/1000+" sec"); 
        delay(holdTime);

        Serial.print("and ");
        digitalWrite(jidouPin,HIGH);
        delay(holdTime);
        digitalWrite(jidouPin,LOW);
        Serial.println((String)"お風呂 Switch on for "+holdTime/1000+" sec");
      }  
      else if ( !sw1() && sw3() ){ //お湯LEDが点灯 かつ お風呂LEDが消灯
        Serial.println("ON");
        digitalWrite(jidouPin,HIGH);
        delay(holdTime);
        digitalWrite(jidouPin,LOW);
        Serial.println((String)"お風呂 Switch on for "+holdTime/1000+" sec");     
      }
  }
  else {
      //if (sw2()) {//お風呂LEDが点灯
        Serial.println("OFF");
        digitalWrite(jidouPin,HIGH);
        delay(holdTime);
        digitalWrite(jidouPin,LOW);
        Serial.println("Not receiving signal now !"); 
      //}
    //  else if (sw3()) {return;}
  }
}

void oidakiChanged(EspalexaDevice* d) {
  if (d == nullptr) return; //this is good practice, but not requiredこれは良いやり方ですが、必須ではありません

  //do what you need to do hereここで必要なことをしてください
  //EXAMPLE
  Serial.print("Oidaki changed to ");
  if (d->getValue()){
    //Serial.println((String)"d->getValue: "+d->getValue());
    if (!sw1() && sw4() ){ //お湯LEDが点灯 かつ おいだきLEDが消灯
    //Serial.println((String)"!sw1: "+!sw1()+" sw4: "+sw4());
      curr = millis();    // 現在時刻を取得
      //Serial.println((String)"curr: "+curr+" prev: "+prev);
      //Serial.println((String)"sw3: "+sw3()+" Elapsed time: "+(curr - prev)/1000+" sec");
      if ( sw3() && (curr - prev) <= interval) {  // お風呂LEDが消灯 かつ お風呂をつけてから所定時間以下ならば
          Serial.println("ON"); //おいだきスイッチを入れる
          digitalWrite(oidakiPin,HIGH);
          delay(holdTime);
          digitalWrite(oidakiPin,LOW);
          Serial.println((String)"Switch on for "+holdTime/1000+" sec");     
      }
    }
  }
  else {
  // if (!sw4()){ //おいだきLEDが点灯しているならば
  //Serial.println((String)"!sw4:"+!sw4());
    Serial.println("OFF");
    digitalWrite(oidakiPin,HIGH);
    delay(holdTime);    
    digitalWrite(oidakiPin,LOW);
    Serial.println("Not receiving signal now !"); 
  }
  
}


// connect to wifi ? returns true if successful or false if not
boolean connectWifi(){
  boolean state = true;
  int i = 0;
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
  Serial.println("Connecting to WiFi");

  // Wait for connection
  Serial.print("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (i > 20){
      state = false; break;
    }
    i++;
  }
  Serial.println("");
  if (state){
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }
  else {
    Serial.println("Connection failed.");
  }
  return state;
}

Alexaアプリへの登録

登録の仕方はAlexa対応のライトやエアコンと同じです。「アレクサ、デバイスを探して」と話しかけるか、アプリの「デバイスを追加」をタップします。






スケッチで設定した名前のデバイスが見つかるので、Alexaアプリでわかりやすい「お湯」「お風呂」「おいだき」に名称変更しました。

  • espalexa.addDevice("Unten", .....
  • espalexa.addDevice("Jidou", .....
  • espalexa.addDevice("Oidaki", .....

アプリ上では下記のように表示されます。照明として認識されますが、espalexaライブラリのON/OFFしか使わないので問題ないです。ただし、アレクサに話しかけるとき、スイッチが想定される言葉しか有効になりません。(アレクサが言うことを聞きません。)

例えば、

お湯をつけて、お湯を入れて、お風呂をいれて、お風呂を消してはOK
お湯を出して、お風呂を沸かしてはNG




Powered by Blogger | Designed by QooQ

keyboard_double_arrow_down

keyboard_double_arrow_down