ESP-32 Arduino のAmbient利用とWebページのグローバル化とESP32の消費電力の話

前回までの話↓

また、そろそろ暑くなって来たので、エアコンの外部制御を行う季節がやって来ますね。

なので、昨年(2022年)に作成したものが稼働するか確認しています。

で、見直していると、自分で作成した温度とかをESP-32で測定するもの(自分の中の名称は、「温度取り君」としています。)ですが、以前の作成したときの宿題を思い出しました。

1.ローカルのWebページをグローバル表示できないか?

2.取ったデータのグラフ化

の2点です。

今年はその課題を克服していきましょう。

では、簡単な方から行きますね。

1.ローカルネットワークでのみ、見れていたWebサイトをグローバルにて見れるようにする方法

これは案外簡単でした。

以前、このESP32で作成したプログラムですが、ポートを80で指定しましたが、それを適当なポートへ変更します。

例えば、51151ポートを指定します。

で、ルーターでその 51151ポート を開放します。

そして、グローバルIPアドレスで見ると見れるようになります。

例えば、ローカルIPアドレス:192.168.1.11 Port:51151で設定します。

で、グローバルでアドレス指定するには、xxx.xxx.xxx.xxx:51151とポート指定してやると、見れるようになります。

グローバルIPアドレスはルーターで表示されるので、確認して見てください。

先日、購入したASUSのRT-AX3000↓

このルーターはなかなか優秀でして、上図のように、指定したIPアドレスの機器のポートをポート変換してくれるので助かります。

これで、ようやくグローバルで見れるようになりました。

↓ この画面ですね。↓ グローバルで見れるとまた感慨深い。

上のやり方だと内部ポートを80としていても、port:51151に変換してくれることもできるようですね。

後で試してみるようかな。

いずれにしてもport:80を避けたいのは、自分がこのブログを持っているためport:80はこのサイトに占有されているので、使えないからです。

なので、ブログを持っていない方はport:80のままで見れるはずです。

で、その見れるようになったアドレスは公開しないのかというと、ウチの場合グローバルのIPアドレスって、固定IPアドレスでの契約していないので、変わってしまうのですよね。

なので、ここに書いてみても、また変わってしまうので、意味ないから書かないつもりです。

それを避けるにはDNS設定ですが、ESP32でもできるみたいですけど、ちょっと面倒そうなので今はパスでいいかなぁ。。

気が向いたら考えましょう。

安全性というかセキュリティですが、コンパイルしたESP32を乗っ取れることは無いだろうし、コンパイルのできるPCに接続されているわけではないので、(コンパイルができない状況なので、)まぁ、別段心配することはないだろうと思ってます。

Webサイトにボタンとかがあると、パスワードとか考えないとダメでしょうが、データ表示だけなので、まぁ、大丈夫だと思っています。

ボタン付けてエアコンのon/offの赤外線コントロールするのでも良いかもしれませんね。

その場合はパスワードロックをしないと電気代が大変になりそうですけど。

2.グラフ化 Ambientの利用

グラフ化ですが、ゆくゆくは自分のデータベースから自分で作ったWebページへ表示したいのですが、PHPを学んでいる時間が今は無いので、とりあえずAmbientという便利なサイトを利用させてもらいました。

以下のリンクがそのサイトの自分の家の温度と湿度と気圧などです。↓

Ambientは便利ですね。簡単で助かります。

いずれは自分で作成したサイトに表示したいですね。

PHPを頑張って覚えますか。

今回、プログラムを見直していると、気圧の表記が気になったので、hPa表記にしました。

それと標高が間違っているので補正しました。(ここは適当ですけど。)

あと、Ambientへのデータ渡しを追記しています。

Void loopで15分毎にAmbientへデータを送りつつ、Wifiの接続チェックと復旧をしなければならないという所が、今回のポイントでしょうか。

太字部分が訂正と追加した部分になります。

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <DHT.h>                    // DHTセンサー用
#include <Adafruit_BMP085.h>        // BMP180センサー用 ESP32の場合 SCL信号→D22 SDA信号→D21へ接続する
#include <Ambient.h>

/* Function Prototype */
String getTemperature();
String getHumidity();
String getTemp();
String getPressure();
String getAltitude();
String getRPressure();
String processor(const String&);
void doInitialize();
void connectToWifi();
Adafruit_BMP085 bmp;   
WiFiClient client;      // 書き忘れ無きように
Ambient ambient;       // 書き忘れ無きように


// ルーター接続情報
#define WIFI_SSID "SSID"
#define WIFI_PASSWORD "PASSWORD"

/* 基本属性定義  */
#define SPI_SPEED   115200          // SPI通信速度
#define DHTTYPE     DHT11           // DHTセンサーの型式

// Webサーバーオブジェクト
#define HTTP_PORT 51151
AsyncWebServer server(HTTP_PORT);

/* DHTセンサー*/
const int DHTPin = 16;                  // DHTセンサーの接続ピン
DHT   dht(DHTPin, DHTTYPE);             // DHTクラスの生成

/* Ambient情報 */
unsigned int channelId = チャンネルID; // AmbientのチャネルID(数字)
const char* writeKey = "ライトキー"; // ライトキー

/* void loop の準備 */
int i = 0;

/* HTMLページ */
const char* strHtml = R"rawliteral(
<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      html { font-family: Helvetica; display: inline-block; margin: 0px auto;text-align: center;} 
      h1 {font-size:28px;}
      body {text-align: center;} 
      table { border-collapse: collapse; margin-left:auto; margin-right:auto;}
      th { padding: 12px; background-color: #0000cd; color: white; border: solid 2px #c0c0c0;}
      tr { border: solid 2px #c0c0c0; padding: 12px;}
      td { border: solid 2px #c0c0c0; padding: 12px;}
      .value { color:blue; font-weight: bold; padding: 1px;}
    </style>
  </head>
  <body>
    <h1>温度測定中</h1>
    <p style='color:brown; font-weight: bold'>計測値はアクセスが無いときは1時間ごとに自動更新</p>
    <p><table>
      <tr><th>要素</th><th>値</th><th>単位</th></tr>
      <tr><td>温度</td><td><span id="temperature" class="value">%TEMPERATURE%</span></td><td>℃</td>
      <tr><td>湿度</td><td><span id="humidity" class="value">%HUMIDITY%</span></td><td>%</td>
      <tr><td>温度</td><td><span id="temp" class="value">%TEMP%</span></td><td>℃</td>
      <tr><td>気圧</td><td><span id="Pressure" class="value">%Pressure%</span></td><td>hPa</td>
      <tr><td>高度</td><td><span id="Altitude" class="value">%Altitude%</span></td><td>m</td>
      <tr><td>海面気圧</td><td><span id="RPressure" class="value">%RPressure%</span></td><td>hPa</td>
      </td></tr>
    </table></p>
  </body>
  <script>
    var getTemperature = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("temperature").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/temperature", true);
      xhr.send(null);
    }
    var getHumidity = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("humidity").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/humidity", true);
      xhr.send(null);
    }
    var getTemp = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("temp").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/temp", true);
      xhr.send(null);
    }
    var getPressure = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("Pressure").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/Pressure", true);
      xhr.send(null);
    }
    var getAltitude = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("Altitude").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/Altitude", true);
      xhr.send(null);
    }
    var getRPressure = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("RPressure").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/RPressure", true);
      xhr.send(null);
    }

    setInterval(getTemperature, 3600000);
    setInterval(getHumidity, 3600000);
    setInterval(getTemp, 3600000);
    setInterval(getPressure, 3600000);
    setInterval(getAltitude, 3600000);
    setInterval(getRPressure, 3600000);
  </script>
</html>)rawliteral";

/*****************************************************************************
 *                          Predetermined Sequence                           *
 *****************************************************************************/
void setup(){
    ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化                
    Serial.begin(SPI_SPEED);           // 初期化処理
    dht.begin();                       // DHTセンサーを起動
    Serial.print("Connecting to Wi-Fi ");   // Wi-Fiルーターに接続する
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");

    // モニターにローカル IPアドレスを表示する
    Serial.println("WiFi connected.");
    Serial.print("  *IP address: ");
    Serial.println(WiFi.localIP());
    server.begin();

    // GETリクエストに対するハンドラーを登録して
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/html", strHtml, editPlaceHolder);
    });
    server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getTemperature().c_str());
    });
    server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getHumidity().c_str());
    });
    server.on("/temp", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getTemp().c_str());
    });
    server.on("/Pressure", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getPressure().c_str());
    });
    server.on("/Altitude", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getAltitude().c_str());
    });
    server.on("/RPressure", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getRPressure().c_str());
    });
    
    if (!bmp.begin()) {
    Serial.println("Could not find a valid BMP085 sensor, check wiring!");
    while (1) {}
    }
    server.begin();     // サーバーを開始する
}
 
// void loop(void)
void loop(void) {
  
  if (i >= 9000){             //15分毎にambientへ送信
    ambient.set(1, getTemperature().c_str()); // 温度をデータ1にセット
    ambient.set(2, getHumidity().c_str()); // 湿度をデータ2にセット
    ambient.set(3, getTemp().c_str()); // 気圧をデータ3にセット
    ambient.set(4, getPressure().c_str()); // 温度をデータ1にセット
    ambient.set(5, getAltitude().c_str()); // 湿度をデータ2にセット
    ambient.set(6, getRPressure().c_str()); // 気圧をデータ3にセット
    ambient.send(); // データをAmbientに送信
    i = 0;
  }
  
  if (i >= 1){
    int i;                  // WiFi Lan 接続を監視します。切れたら復旧
    if(WiFi.status() !=3){
    }
    if(WiFi.status() != WL_CONNECTED){
      WiFi.disconnect();
      delay(50);
      WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
      i=50;
      while (WiFi.status() != WL_CONNECTED) {
        delay(250);
        i--;
        if(i<0){
          delay(250);
          ESP.restart();
        }
      }
    }
  }
  i++;
  delay(100);
}


/* 計測処理ロジック */
String getTemperature() {
    float t = dht.readTemperature();
    if (isnan(t)) {    
        Serial.println("Failed to get temperature!");
        return "--";
    }
    else {
        Serial.println(t);
        return String(t) ;
    }
}

String getHumidity() {
    float h = dht.readHumidity();
    if (isnan(h)) {
        Serial.println("Failed to get humidity!");
        return "--";
    }
    else {
        Serial.println(h);
        return String(h) ;
    }
}

String getTemp() {
    float t2 = bmp.readTemperature();
    if (isnan(t2)) {
        Serial.println("Failed to get BMP180 temp!");
        return "--";
    }
    else {
        Serial.println(t2);
        return String(t2) ;
    }
}

String getPressure() {
    float p = bmp.readPressure()/100;
    if (isnan(p)) {
        Serial.println("Failed to get Pressure!");
        return "--";
    }
    else {
        Serial.println(p);
        return String(p) ;
    }
}

String getAltitude() {
    float a = bmp.readAltitude(bmp.readPressure())+40;
    if (isnan(a)) {
        Serial.println("Failed to get Altitude!");
        return "--";
    }
    else {
        Serial.println(a);
        return String(a) ;
    }
}

String getRPressure() {
    float s = bmp.readSealevelPressure()/100;
    if (isnan(s)) {
        Serial.println("Failed to get SealevelPressure!");
        return "--";
    }
    else {
        Serial.println(s);
        return String(s) ;
    }
}

/* プレースホルダー処理 */
String editPlaceHolder(const String& var){
    if(var == "TEMPERATURE"){
        return getTemperature();
    }
    if(var == "HUMIDITY"){
        return getHumidity();
    }
    if(var == "TEMP"){
        return getTemp();
    }
    if(var == "Pressure"){
        return getPressure();
    }
    if(var == "Altitude"){
        return getAltitude();
    }
    if(var == "RPressure"){
        return getRPressure();
    }
}

3.消費電力の話(追記、2023/12/17)

昨今の電気代が高騰している状況でのESP-32の消費電力も気になります。

さて、どんなものかと思い計測してみました。

さすがですね。省電力凄いですね。

ディープスリープとかしていないのに、この低消費電力。

電気代を計算するサイトがあるので、そこを利用させてもらいます。

上の写真は見方が二通りあります。

① 瞬間的な見え方

写真は瞬間的なもので、正しいか分かりませんが、

(5.03v)X(0.02A)=0.1006w(ワット数を求める)

瞬間値のワット数は、0.1wですね。

② 消費電力の数値からみると

(25 03tとあるのが25時間と3分かな?、00606mAhとあるので、)

25時間で0.6Ahを使用したということですので、

1時間当たりを求めると、0.6A/25h=0.024Ahということになります。

Aを求めるます、5vX0.024Ah=0.12A

となるので、ワット数は0.12Ax5v=0.6Wとなります。

かなり差が出ましたが、恐らく大きい方が正しい数字ではないかなとおもいます。

まぁ、大きめに見ておくのが間違いないと思います。

一応、ESP32の消費電力をみると、以下に参考になる所が出て来ます。

モデムスリープモードで25mAとあります。

ほぼ、1時間当たり消費電力を求めた結果の0.024Ahと同じですね。

なので、これが恐らく正しいように思います。

なので、結論です。

0.6wを24時間、1年間(365日)利用したときの電気代は、驚きの192円、月16円ですねぇ~。

凄いですね。優秀です。これなら常時ONでいいや。となりますね。

まぁ、この測定機がどれほどのものかは、分かりませんが、大きく見て月20円程度を見ていれば大丈夫なのでしょうかね。

実は、スマートプラグなんてどうなの?使った方がいいかな?と考えていました。

(ラズパイ3b+DACで利用しました)下記のヤツを買ったのですよね。

調べて見たら、スマートプラグがOFFの時の消費電力と同等で、ONすると2倍強となるので、繋げっぱなしの方が消費電力が少ないので、全然、必要ないという結果になりました。

なので、常時電源ONで、ESP32で何か利用するというのはホント、アリですよね。スマートな電源管理になりますね。

ディープスリープを覚えると更によいのでしょうねぇ。