BlynkとESP-01で外からエアコン制御(アンサーバック付き)

#はじめに
Blynkを使用すればインターネット経由で簡単にESP-01のコントロールが可能です。BlynkからのトリガでエアコンONの指令を出すのみですが、エアコンが正しくONしたのかが不安です。ONしようとして失敗していたのであれば大きな問題ではありませんが、反対の場合は少し問題です。
うちの三菱電機のエアコンは、リモコンで操作した後にアンサーバックとして4kHzのビープ音を返します。ESP-01から指令を出したあとこのビープ音を検出して、アンサーバックとしてBlynkに出力することにしました。
これで外部からエアコンを安心に操作できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/b556cf58-efb0-671d-7f3e-7e98012cddce.png)
ESP-01にてエアコンONのリモコンコマンドを送信し、エアコンのビープ音を検出、BlynkAppにビープ音検出を通知します。
ビープ音はDTMF検出のアルゴリズムを使用しました。

#必要なもの
##BlynkApp
ダウンロードしてきます。ある程度まではフリーで使用可能です。

##ESP-01
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/0c01efc2-6608-5289-bb85-d90ee3faf69e.png)

ESP8266を搭載したボードであればどれでも構いません。

##赤外LED部
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/fcd2d9ba-9d99-d7d0-e8e3-0970fd251dc6.png)

ESP8266の出力では心許ないためトランジスタを使用します。

##マイク部
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/a2ba92cb-0b2d-dbfc-f94d-c90507c0b21f.png)
コンデンサマイクの出力を増幅しレベル変換するために、オペアンプLM358を使用しています。単電源で使用できるものならば何でもOKです。
左側でマイク出力を100倍に増幅、右側でDCバイアス調整と更なる増幅(最大5倍)しています。
ESP8266のADCは0-1Vまでとなっているため、このDCバイアス調整が必要となります。左側はマイクモジュールでも可能ですが、0-1Vに調整(無入力で0.5V、音声入力で0-1Vまで振れるのが理想)する必要があり右側のアンプは必要となります。
ADCに1V以上を入力させないよう、クランプ用途にダイオードを入れています。ここでは整流ダイオードを2直にしているだけですので、1.2Vまで入力されてしまいます。正式には1Vのツェナダイオードを使用してください。

##ESP-01改造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/49cc7ace-ffce-0d29-64e4-3313978189b9.png)
ESP-01にはADCのピン(TOUT)が引き出されていないため、ESP8266の6PINにケーブルをハンダ付けします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/faa26d2c-7983-a812-94cc-7c0ca0c6a684.png)
0.2mmのUEW線をハンダ付けしました。そんなに難しくはありませんでした。始めからコネクタのあるESP-WROOM-02を使用するのも手です。

#調整
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/e68694e0-daec-7872-d6be-5de1423faef1.png)
基板ができたので調整に入ります。
二つのトリマを回して、無信号で0.5Vになるようにし、ビープ音がそれなりの振幅を持つようにします。上の写真は調整後でトリマが動かないようマスキングテープで保護しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/21e83d86-d33e-80ea-2a2f-429e2ffd4e03.png)
振幅はVp-p=325mVしかありませんでしたが、これで十分に検出可能でした。

#ソフト
##Blynk
様々なサイトで紹介されていますので、概略のみを記述します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/03372073-7e38-0a3b-10ae-bb57a0fd9600.png)
###PUSHスイッチを2つ用意
V1 : ONスイッチ
V2 : OFFスイッチ
###LEDを1つ用意
V3 : ON/OFFステータス表示

##Arduino
Arduinoで組みました。

```Aircon.cpp
/*
   AirCon anserback

   2020/03/15

*/


#define BLYNK_PRINT Serial

#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ir_Mitsubishi.h>

extern "C" {
#include "user_interface.h"
}
#include <Goertzel_ESP8266.h>


char auth[] = "Blynk_Auth_Code"; //Blynkから送られてきたメール内にある Auth コード
char ssid[] = "WIFI_SSID"; //ご自分のルーターの SSID
char pass[] = "WIFI_PASS"; //ご自分のルーターのパスワード

const int num_of_samples = 50;                       //it is the number of samples code will take y0u can change for sensitivity and can if large it can slow the arduino
const float threshold = 1500;            //minimum tone amplitude to be considered we can change it for more senstivity
const float sampling_freq = 9750.0;        //maximum detectable frequency is the sampling rate/2


const uint16_t kIrLed = 2;  // ESP8266 GPIO pin to use. Recommended: 4 (D2).
IRMitsubishiAC ac(kIrLed);  // Set the GPIO used for sending messages.
WidgetLED led3(V3);

void printState() {
  // Display the settings.
  Serial.println("Mitsubishi A/C remote is in the following state:");
  Serial.printf("  %s\n", ac.toString().c_str());
  // Display the encoded IR sequence.
  unsigned char* ir_code = ac.getRaw();
  Serial.print("IR Code: 0x");
  for (uint8_t i = 0; i < kMitsubishiACStateLength; i++)
    Serial.printf("%02X", ir_code[i]);
  Serial.println();
}

void setup()
{
  Serial.begin(115200);
  pinMode( kIrLed, OUTPUT );

  Blynk.begin(auth, ssid, pass);

  Serial.println("Start");

  ac.begin();
  delay(200);

  // Set up what we want to send. See ir_Mitsubishi.cpp for all the options.
  Serial.println("Default state of the remote.");
  printState();
  Serial.println("Setting desired state for A/C.");

}

void loop()
{
  Blynk.run();
}


// ON button
BLYNK_WRITE(V1)
{
  if (param.asInt() == 1)
  {
    Serial.println("Sending IR command to A/C ...");
    ac.on();
    ac.setFan(1);
    ac.setMode(kMitsubishiAcCool);
    ac.setTemp(24);
    ac.setVane(kMitsubishiAcVaneAuto);
    ac.send();
    printState();
  }
  if (check_answerback())
    led3.on();
}

// OFF button
BLYNK_WRITE(V2)
{
  if (param.asInt() == 1)
  {
    Serial.println("Sending IR command to A/C ...");
    ac.off();
    ac.send();
    printState();
  }
  if (check_answerback())
    led3.off();
}

bool check_answerback()
{
  int i = 0;
  bool bRet = false;
  while (100 > i)
  {
    if (detect_tone(4000) == true)
    {
      bRet = true;
      break;
    }
  }
  Serial.print("Answer back = ");
  Serial.println(bRet);

  return bRet;
}
bool detect_tone(float freq)
{
  Goertzel_ESP8266 goertzel = Goertzel_ESP8266(freq, num_of_samples, sampling_freq);        //initialize library function with the given sampling frequency no of samples and target freq
  goertzel.sample();                               //Will take n samples
  float magnitude = goertzel.detect();                      //check them for target_freq

  if (magnitude > threshold)                               //if you're getting false hits or no hits adjust the threshold
  {
    Serial.print(freq);
    Serial.print("\n\n");
    return true;
  }
  else
    return false;
}
```

###エアコン制御
[IRremoteESP8266ライブラリ](https://github.com/crankyoldgit/IRremoteESP8266)を使用しています。

###ビープ音検出
DTMF検出のアルゴリズム[Goertzelライブラリ](https://qiita.com/Qiita/items/c686397e4a0f4f11683d)を改造して使用してます。(ADC部分の変更くらい)
ESP8266を80MHzで動作させた場合、サンプリング周波数は9750Hzになります。Goertzel_ESP8266()の初期化で9750Hzを指定してください。(sampling_freq に指定)

以下に改造したソースを記載します。

####Goertzel_ESP8266.cpp

```Goertzel_ESP8266.cpp
/*
  The Goertzel algorithm is long standing so see 
  http://en.wikipedia.org/wiki/Goertzel_algorithm for a full description.
  It is often used in DTMF tone detection as an alternative to the Fast 
  Fourier Transform because it is quick with low overheard because it
  is only searching for a single frequency rather than showing the 
  occurrence of all frequencies.
  
  This work is entirely based on the Kevin Banks code found at
  http://www.embedded.com/design/configurable-systems/4024443/The-Goertzel-Algorithm 
  so full credit to him for his generic implementation and breakdown. I've
  simply massaged it into an Arduino library. I recommend reading his article
  for a full description of whats going on behind the scenes.

  Created by Jacob Rosenthal, June 20, 2012.
  Released into the public domain.
*/
// include core Wiring API
#include "Arduino.h"

// include this library's description file
#include "Goertzel_ESP8266.h"

float _SAMPLING_FREQUENCY;
float _TARGET_FREQUENCY;
int _num_of_samples;
float coeff;
float Q1;
float Q2;

int testData[___MAX_NUM_BUF];

Goertzel_ESP8266::Goertzel_ESP8266(float TARGET_FREQUENCY, float nNum)
{
	#if F_CPU == 80000000L		// ESP8288
		Goertzel_ESP8266(TARGET_FREQUENCY, nNum, 9750.0);
	#else
		Goertzel_ESP8266(TARGET_FREQUENCY, N, 4875.0);
	#endif
}

Goertzel_ESP8266::Goertzel_ESP8266(float TARGET_FREQUENCY, float numofsamples, float SAMPLING_FREQUENCY)
{
  
  _SAMPLING_FREQUENCY=SAMPLING_FREQUENCY;	//on 16mhz, ~8928.57142857143, on 8mhz ~44444
  _TARGET_FREQUENCY=TARGET_FREQUENCY; //should be integer of SAMPLING_RATE/N
	
  if( _num_of_samples > ___MAX_NUM_BUF){
	 _num_of_samples = ___MAX_NUM_BUF;
  }else{
	_num_of_samples = numofsamples;
  }
  
  float omega = (2.0 * PI * _TARGET_FREQUENCY) / _SAMPLING_FREQUENCY;

  coeff = 2.0 * cos(omega);

  ResetGoertzel();
}


/* Call this routine before every "block" (size=N) of samples. */
void Goertzel_ESP8266::ResetGoertzel(void)
{
  Q2 = 0;
  Q1 = 0;
}


/* Call this routine for every sample. */
void Goertzel_ESP8266::ProcessSample(int sample)
{
  float Q0;
  Q0 = coeff * Q1 - Q2 + (float) (sample - ADCCENTER);
  Q2 = Q1;
  Q1 = Q0;
}


/* Sample some test data. */
void Goertzel_ESP8266::sample()
{
//https://arduino.stackexchange.com/questions/48640/esp8266-system-adc-read-fast-always-give-1024-as-output
		system_soft_wdt_stop();
		ets_intr_lock( ); //close interrupt
		noInterrupts();
	
	for (int index = 0; index < _num_of_samples; index++)
	{
		testData[index] = system_adc_read();
	}

		interrupts();
		ets_intr_unlock(); //open interrupt
		system_soft_wdt_restart();

}


float Goertzel_ESP8266::detect()
{
  float	magnitude;

  /* Process the samples. */
  for (int index = 0; index < _num_of_samples; index++)
  {
	ProcessSample(testData[index]);
  }

  /* Do the "standard Goertzel" processing. */
  magnitude = sqrt(Q1*Q1 + Q2*Q2 - coeff*Q1*Q2);

  ResetGoertzel();
  return magnitude;
}

```

####Goertzel_ESP8266.h
```Goertzel_ESP8266.h
/*
  The Goertzel algorithm is long standing so see 
  http://en.wikipedia.org/wiki/Goertzel_algorithm for a full description.
  It is often used in DTMF tone detection as an alternative to the Fast 
  Fourier Transform because it is quick with low overheard because it
  is only searching for a single frequency rather than showing the 
  occurrence of all frequencies.
  
  This work is entirely based on the Kevin Banks code found at
  http://www.eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm 
  so full credit to him for his generic implementation and breakdown. I've
  simply massaged it into an Arduino library. I recommend reading his article
  for a full description of whats going on behind the scenes.

  See Contributors.md and add yourself for pull requests
  Released into the public domain.
*/

// ensure this library description is only included once
#ifndef Goertzel_ESP8266_h
#define Goertzel_ESP8266_h

extern "C" {
#include "user_interface.h"
}

// include types & constants of Wiring core API
#include "Arduino.h"

#define ___MAX_NUM_BUF 100
#define ADCCENTER 512

// library interface description
class Goertzel_ESP8266
{
  // user-accessible "public" interface
  public:
    Goertzel_ESP8266(float,float,float);
    Goertzel_ESP8266(float,float);
	void sample();
	float detect();

  // library-accessible "private" interface
  private:
	void GetRealImag(float*,float*);
	void ProcessSample(int);
	void ResetGoertzel(void);
	
};

#endif

```

###ソフト調整
auth : BlynkのAuthコードを入力します。
ssid : WIFIのSSIDを入力します。
pass : WIFIのPASSを入力します。

num_of_samples : DTMF検出ためのサンプル数を指定します。50で十分でした
threshold : ノイズとビープ音のしきい値を入力します。
sampling_freq : ESP8266を80MHzで動作させた場合は9750となります。遅いですね。

```調整
char auth[] = "Blynk_Auth_Code"; //Blynkから送られてきたメール内にある Auth コード
char ssid[] = "WIFI_SSID"; //ご自分のルーターの SSID
char pass[] = "WIFI_PASS"; //ご自分のルーターのパスワード

const int num_of_samples = 50;                       //it is the number of samples code will take y0u can change for sensitivity and can if large it can slow the arduino
const float threshold = 1500;            //minimum tone amplitude to be considered we can change it for more senstivity
const float sampling_freq = 9750.0;        //maximum detectable frequency is the sampling rate/2

```

#動作

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/00aa3db2-f569-3534-0df8-ac39ea951bdc.png)

ONボタンを押下すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211703/31af3031-80c8-4b2b-968b-438cf7f51fa9.png)
エアコンのビープ音に反応してLEDが表示されます。

下記はESP-01からのデバッグ情報です。Blynkサーバからの指令でリモコン出力をし、ビープ音を検出していることが解ります。

```シリアル.txt
21:11:46.800 -> [15874] Connected to WiFi
21:11:46.800 -> [15875] IP: 192.168.1.118
21:11:46.800 -> [15875] 
21:11:46.800 ->     ___  __          __
21:11:46.800 ->    / _ )/ /_ _____  / /__
21:11:46.800 ->   / _  / / // / _ \/  '_/
21:11:46.800 ->  /____/_/\_, /_//_/_/\_\
21:11:46.800 ->         /___/ v0.6.1 on ESP8266
21:11:46.800 -> 
21:11:46.800 -> [15881] Connecting to blynk-cloud.com:80
21:11:47.280 -> [16384] Ready (ping: 459ms).
21:11:47.348 -> Start
21:11:47.553 -> Default state of the remote.
21:11:59.982 -> Mitsubishi A/C remote is in the following state:
21:11:59.982 ->   Power: On, Mode: 3 (Cool), Temp: 24C, Fan: 1 (Low), Swing(V): 0 (Auto), Swing(H): 3 (UNKNOWN), Clock: 17:10, On Timer: 00:00, Off Timer: 00:00, Timer: -
21:12:00.017 -> IR Code: 0x23CB26010020180836416700000000000033
21:13:23.045 -> 4000.00
21:13:23.045 -> 
21:13:23.045 -> Answer back = 1
```


#おわりに
これで安心して外からエアコン制御ができるようになりました。