2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-06

##もっと便利に・・

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化にしています。
f660aスクショ.png

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

#nginx

##自由発声マシンの改造
以前のプログラムだとリバプロがうまく動きません。
どうも、「/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その1";
const char* ssid2="あなたのSSIDその2";
const char* ssid3="あなたのSSIDその3";
const char* password1="あなたのパスワードその1";
const char* password2="あなたのパスワードその2";
const char* password3="あなたのパスワードその3";

// 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();
}
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?