ESP8266(ESPrDeveloper)+Google-Home-Notifer+NGINXリバプロで 外部から 自由発声マシン！？

##もっと便利に・・

https://qiita.com/ABK28/items/1672fe26837c364a8de3
の続きです。
前回は、ESP8266とGoogle-Home-Notiferで、GoogleHomeを自由に喋らせるマシンを作りました。
でも、ローカル環境（LAN内）でしか使えません。外にいるときに家にいる人に呼びかけられるように、外部インターネットからも繋げられるようにしてみます。
（電話すればイイじゃん。ってのは おいといて・・）

##Serveo.net
最初、Serveo.netってを使えば簡単にできそう、って思ったんですが、最近ずっと、
ーーー
Serveo is temporarily disabled due to phishing.
Serveo will return in a few days with a few new restrictions to help dissuade abuse. Thanks for your patience!
ーーー
な状態で試すこともできません。なので、別のことを考えました↓。

##SSL/TLS化
8080でつながるようなWEBサーバにしていたので、家のルータに穴を開けただけだと、データ丸見えです。なのでSSL化しようとしました。
でも、高価な証明書は買えないし、定期的に更新が必要な無料の「Let's Encrypt」ですと、証明書を埋め込むArduinoでは不便すぎます。
そこで・・・

##GCP Always Free でリバースプロキシ構築
NGINXでリバプロを作って、SSL化しようと考えました。
でも、お金をかけなくないので、調べていたら、GCPに永久無料枠があるとのこと。
f1.microで、アメリカのバージニア州以外のリージョンで、とか条件がありますが、個人で使う分には、「無問題」ですので、それで作ることに・・。
早速、Google Cloudで、環境を作ります。
（作り方は、ここで書くと長くなるので、ググってください。）

###VM構築とNGINXインストール
・ubintu18.04LTSでVMを作りました。尚、固定IPにしています。無料VMで使う分には、固定IPも無料枠に入ってるみたいです。構築手順は省略します・・。
・NGINXとcertbotをインストールしました。
・ついでに、timezoneがMSTだったので、JSTにもしました。

```
$sudo su
#timedatectl set-timezone Asia/Tokyo
#apt update
#apt upgrade
#apt install nginx
#apt install certbot
#apt install python-certbot-nginx
```

###mydns.jpでドメイン設定
名前でアクセスしたいので、いつも使ってるmydns.jpで、ドメインを設定しました。IPアドレスでなくてもドメイン名でアクセスできます。
mydns.jpの設定は省略します・・
そして、cronに下記をセットしました。こんな感じ・・

```
0 1 * * * curl -u mydnsのID:mydnsパスワード https://www.mydns.jp/login.html
```
これ以降では「yourhogehohe.com」でアクセスできるようになったとします。

尚、自分は、家のインターネットもmydns.jpを使っています。以降は、仮に「househogehohe.com」となってる前提で記載しますね。


###certbotでSSLサーバ構築
certbotを使うと、SSL設定とか自動でやってくれます。楽ちんです。

```
#certbot --nginx -d yourhogehoge.com
```
途中で、httpをhttpsにリダイレクトするか？って聞いてきたので、そうしました。
完了したら、
ーーー
You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=yourhogehoge.com
ーーー
って出てきたので、そのサイトでSSLになってるか確認できます。

Let's Encryptは、定期的に証明書更新が必要なのですが、その設定も自動でやってくれてます。
念のため、下記に「certbot」があるか確認しました。

```
#ls /etc/cron.d
```

###リバーシプロキシの設定
/etc/nginx/site-available/default
を直接メンテしちゃいます。
server の中のlocationの上あたりに下記を埋め込みました。

```
    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
```

locationの中には、

```
proxy_pass    http://家のドメイン名:8080/;
```
を入れました。↓結果、このあたりは、こんな感じ。

```
    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

    location / {
        proxy_pass    http://家のドメイン名:8080/;
        try_files $uri $uri/ =404;
    }
```

###家のルータの設定
ここは、それぞれのルータで設定が違うと思いますが、家のルータは、F660Aでしたので、下記のようにしました。あ、そういえば、ESP8266マシンは、ルータのMACアドレス制御で、固定IP化にしています。
<img width="785" alt="f660aスクショ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186659/56ab9c44-abb7-12d6-1fd6-94cc4daaaf90.png">

###接続試験
nginxを起動して、さぁ、動くかなぁ・・

```
#nginx
```

https://yourhogehoge.com/

##自由発声マシンの改造
以前のプログラムだとリバプロがうまく動きません。
どうも、「/speech」で発声するようにしているところが、うまくプロキシーしてくれないみたいなんです。
そこで全部「/」で動くように修正しました。

また、この状態ですと、URLを知っていれば誰でも、家のGoogleHomeを喋らせられちゃいます。
それはよろしくないので、BASIC認証をつけました。

また、起動時にIPアドレスを発声するとか、うるさくなってきたのでコメントアウトしました。
その他、マルチWiFi化とか、こまこま改良しました。
詳しくは、前回の記事＋改良版のソースで・・・
なんか間違ってる気もしますが・・。

```
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiClient.h>
#include <esp8266-google-home-notifier.h>
#include <ESP8266WiFiMulti.h>

// Google Home
GoogleHomeNotifier ghn;
const char displayName[] = "あなたのデバイスネーム";
String mes;

// WiFi
ESP8266WiFiMulti wifiMulti;
const char* ssid1="あなたのSSIDその１";
const char* ssid2="あなたのSSIDその２";
const char* ssid3="あなたのSSIDその３";
const char* password1="あなたのパスワードその１";
const char* password2="あなたのパスワードその２";
const char* password3="あなたのパスワードその３";

// Web Server
ESP8266WebServer server(8080);

const char* www_username = "BASIC認証のログインID";
const char* www_password = "BASIC認証のパスワード";

static const char* htmlroot = "<html>"
"<head><meta charset=\"utf-8\"><title>free speaking machine</title></head>"
"<body style='background-color:#ffffde;font-family:sans-serif;font-size:32px;'>"
"<h1>Free Speaking Machine</h1>"
"<form action='/' method='get' name='phrase'>"
"<input type='radio' name='lang' value='ja' checked style='font-family:sans-serif;font-size:30px;height:30px;'>&nbsp日本語<br>"
"<input type='radio' name='lang' value='en' style='font-family:sans-serif;font-size:30px;height:30px;'>&nbsp英語<br>"
"<input type='text' name='phrase' style='font-family:sans-serif;font-size:30px;width:400px;height:80px;'>"
"&nbsp&nbsp"
"<input type=submit value='　Speech　' name='button' style='font-family:sans-serif;font-size:30px;height:40px;'>"
"</form>"
"</body>"
"</html>";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("");

  //WiFi
  Serial.println("");
  Serial.println("connecting to Wi-Fi");
  WiFi.mode(WIFI_STA);
  wifiMulti.addAP(ssid1, password1);
  wifiMulti.addAP(ssid2, password2);
  wifiMulti.addAP(ssid3, password3);

  while (wifiMulti.run() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected.");
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //Print the local IP
  Serial.println("");

  /*
  //Google Home speak SSID.
  mes = "I am Free Speaking Machine.";
  mes += "SSID is   ";
  mes += WiFi.SSID();
  mes += ".";  
  googleHomeConnection( "en", mes.c_str() );
  */

  if (!MDNS.begin("freespeaking")) {
    Serial.println("Error setting up MDNS responder!");
    googleHomeConnection( "ja", "mDNSエラーです" );
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started." );
 
  // Web Server 
  server.on("/", handleSpeechPath);
  server.begin();

}

void loop() {
  // put your main code here, to run repeatedly:
  MDNS.update();
  server.handleClient();
  //  必要ならば再接続
  while (wifiMulti.run() != WL_CONNECTED) {
    Serial.println("Re-connecting...");
    delay(1000);
  }
}

void googleHomeConnection( String lang_str1, String talk_str1 ){
  Serial.println("connecting to Google Home...");
  if (ghn.device( displayName, lang_str1.c_str() ) != true) {
    Serial.println(ghn.getLastError());
    return;
  }
  Serial.print("found Google Home(");
  Serial.print(ghn.getIPAddress());
  Serial.print(":");
  Serial.print(ghn.getPort());
  Serial.println(")");
 
  if (ghn.notify( talk_str1.c_str() ) != true) {
    Serial.println(ghn.getLastError());
    return;
  }
  Serial.println("");
}

String ipToString(uint32_t ip){
    String result = "";
    result += String((ip & 0xFF), 10);
    result += ".";
    result += String((ip & 0xFF00) >> 8, 10);
    result += ".";
    result += String((ip & 0xFF0000) >> 16, 10);
    result += ".";
    result += String((ip & 0xFF000000) >> 24, 10);
    return result;
}

void handleSpeechPath() {
  String lang = server.arg("lang");
  String phrase = server.arg("phrase");
  if (!server.authenticate(www_username, www_password)) {
      return server.requestAuthentication();
  }
  if (phrase == "") {
    server.send(200, "text/html", htmlroot);
    MDNS.update();
    return;
  }
  Serial.println("handleSpeechPath");
  Serial.println(lang.c_str());
  Serial.println(phrase.c_str());
  googleHomeConnection( lang.c_str(), phrase.c_str() );
  delay(1000);
  server.send(200, "text/html", htmlroot);
  MDNS.update();
}
```