Ⅳ. ESP32WebサーバーのLEDをスマホから点灯/消灯

 今回も計測用デバイスをWebサーバーにします。今度は、クライアントのWebブラウザーから積極的にサーバーを制御してみましょう。ブラウザー上に配置したボタンを操作することで、計測用デバイスの黄色LEDを点けたり消したりします。同時に、ボタン押下時のLEDの状態と温度・湿度を表示させることにします。


 ※スケッチのダウンロードは右側の[Download]ボタンをクリックしてください。    


1.HTMLコードの作成

 今回も、『Ⅱ. ESP32の計測情報をブラウザーに表示する』で行ったのと同じように、まずテキストエディターなどでHTMLコードを書いて、Webブラウザーで表示させながらデザインを整えます。test3.htmlというファイルに以下のようなコードを書いてドキュメントルートに保存してみました。
 ボタンにはONとOFFがあり、それぞれにアイドルとクリック(active)の状態があるので、CSSのスタイルで3つのクラス属性を定義しています(9~13行)。

<!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;} 
      .btn_on { padding:12px 30px; text-decoration:none; font-size:24px; background-color:
        #668ad8; color: #FFF; border-bottom: solid 4px #627295; border-radius: 2px;}
      .btn_on:active { -webkit-transform: translateY(0px); transform: translateY(0px);
        border-bottom: none;}
      .btn_off { background-color: #555555; border-bottom: solid 4px #333333;}
    </style>
  </head>

  <body><h1>Web Server</h1>
    <p>LED State : OFF</p>
    <p>( 00.0℃, 00.0% )</p>
    <p><a href="/ON"><button class="btn_on"> ON </button></a></p>
    <p><a href="/OFF"><button class="btn_on btn_off">OFF</button></a></p>
  </body>
</html>


 ブラウザーを立ち上げてアドレスバーから「http://local/test3.html」を入力すると、次のような画面が表示されます。デザインを確認・修正してHTMLを手早く完成させます。


 完成したHTMLコードから、スケッチのHTML部分を「生の文字リテラル」で記述します。まずは、HTMLのコードをそのままコピーしてページ・ヘッダー部を定義します。

const String strHtmlHeader = 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;} 
      .btn_on { padding:12px 30px; text-decoration:none; font-size:24px; background-color:
        #668ad8; color: #FFF; border-bottom: solid 4px #627295; border-radius: 2px;}
      .btn_on:active { -webkit-transform: translateY(0px); transform: translateY(0px);
        border-bottom: none;}
      .btn_off { background-color: #555555; border-bottom: solid 4px #333333;}
    </style>
  </head>
)rawliteral";

 次にボディー部を定義しますが、HTMLのページタイトルや測定値、表示オブジェクトなど、実行時に表示内容を書き換えたい部分は、両側を%マークで挟んだプレースホルダーを設定しておきます。ボタンはON/OFFいずれかを表示するので「%BUTTON_STATE%」としています。

const String strHtmlBody = R"rawliteral(
  <body><h1>%PAGE_TITLE%</h1>
    <p>LED State : %LED_STATE%</p>
    <p>%MEASURED_VALUE%</p>
    <p>%BUTTON_STATE%</p>
  </body>
</html>
)rawliteral";

 そして、ONとOFFそれぞれのボタンは別々に定義しています。このどちらかを「%BUTTON_STATE%」に設定して表示させることになります。

const String strButtonOn = R"rawliteral(
    <a href="/ON"><button class="btn_on"> ON </button></a> )rawliteral";
const String strButtonOff = R"rawliteral(
    <a href="/OFF"><button class="btn_on btn_off">OFF</button></a> )rawliteral";


2.ボタンとLEDの動作

 HTMLのONとOFFのボタン記述をもう少していねいに見てみましょう。ボタンとしての基本的な機能をもたせるために<BUTTON>タグを使っていて、適用するCSSのスタイルをclassで指定しています。これで色・形が決まります。重要なのはaタグのhref属性でリンク先を指定している点です。これによって、ボタンをクリックすると"/ON"や"/OFF"をリダイレクトできるわけです。

        <a href="/ON"><button class="btn_on"> ON </button></a>
        <a href="/OFF"><button class="btn_on btn_off">OFF</button></a>

 HTTPリスン処理は、前々回に作成した「HTTPリスン専用メソッド void httpListen()」をそのまま使用しますが、そのフローチャートの「HTTPリクエスト処理」の部分でクリックされたボタンの種別を判定します。"GET /ON"がリダイレクトされていればLEDを点灯し、"GET /OFF"がリダイレクトされていればLEDを消灯します。
 続く「HTTPレスポンス処理」で、ボタン種別を再評価してボタンの状態を表示すると共に、ONであればOFFボタンを、OFFであればONボタンを表示するようにHTMLボディー部を書き換えて(プレースホルダーを編集して)クライアントに送信します。


3.コードの解説

①ヘッダーファイルとデータ等の定義

・31~32行 環境に合った情報を設定してください!
・38行目 計測値の編集結果を格納します。
・51行目 52行目以降にHTMLのページ全体を記述しています。
#include <Arduino.h>
#include <WiFi.h>
#include <DHT.h>                    // DHTセンサー用

/* Function Prototype */
void doInitialize();
void doMeasurement();
void httpListen();
void httpRequestProccess(String*);
void httpSendRespons(WiFiClient*);
void connectToWifi();
bool isPushbuttonClicked();

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

// ルーター接続情報
#define WIFI_SSID "your_ssid"
#define WIFI_PASSWORD "your_password"

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

String measuredString = "";             // 計測結果

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

/* LEDピン */
const int ledPin = 25;                  // LED出力接続ピン
String ledState = "OFF";                // 出力ピンの状態

/* HTMLレスポンスヘッダーとページヘッダー */
const String strResponseHeader = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n"
        "Connection:close\r\n\r\n";
/* HTMLページ */
const String strHtmlHeader = 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;} 
      .btn_on { padding:12px 30px; text-decoration:none; font-size:24px; background-color:
        #668ad8; color: #FFF; border-bottom: solid 4px #627295; border-radius: 2px;}
      .btn_on:active { -webkit-transform: translateY(0px); transform: translateY(0px);
        border-bottom: none;}
      .btn_off { background-color: #555555; border-bottom: solid 4px #333333;}
    </style>
  </head>
)rawliteral";

// (ページボディー部)
const String strHtmlBody = R"rawliteral(
  <body><h1>%PAGE_TITLE%</h1>
    <p>LED State : %LED_STATE%</p>
    <p>%MEASURED_VALUE%</p>
    <p>%BUTTON_STATE%</p>
  </body>
</html>
)rawliteral";

// (ON/OFFボタン)
const String strButtonOn = R"rawliteral(
    <a href="/ON"><button class="btn_on"> ON </button></a> )rawliteral";
const String strButtonOff = R"rawliteral(
    <a href="/OFF"><button class="btn_on btn_off">OFF</button></a> )rawliteral";


②定型のsetup()とloop()

・setup()では初期化処理とルーター接続の関数を実行します。
・loop()では「HTTPリスン処理」を実行し続けます。
void setup() {
    doInitialize();             // 初期化処理をして
    connectToWifi();            // Wi-Fiルーターに接続する
}

void loop() {
    httpListen();               // HTTP手順を制御する
}


③初期化処理

・シリアルポートを設定し、LEDをLOWにしてDHTセンサーを起動します。
void doInitialize() {
    Serial.begin(SPI_SPEED);
    pinMode(ledPin, OUTPUT);           // GPIO設定:LED
    digitalWrite(ledPin, LOW);
    dht.begin();                       // DHTセンサーを起動
}


④計測処理

 温度と湿度を取得して表示文字列を形成します。
void doMeasurement() {
    // 温度と湿度を読み取る
    float t = dht.readTemperature();
    float h = dht.readHumidity();

    // 読み取りに失敗したかどうかを確認し、失敗なら再試行させる
    if (isnan(h) || isnan(t)) {
        Serial.println("Failed to read DHT!");
        return;
    }
    //温度と湿度の測定値を文字列に変換して
    char temperatureTemp[7];
    dtostrf(t, 6, 2, temperatureTemp);
    char humidityTemp[7];
    dtostrf(h, 6, 2, humidityTemp);
    // 表示文字列を形成する
    measuredString = "( " + String(temperatureTemp) + "℃, " + String(humidityTemp) + "% )";
    Serial.println(measuredString);
}


⑤HTTPリスン処理

・「HTTPリスン定型処理」です。
・143行目 「HTTPリクエスト処理」を実行します。
・144行目 「HTTPレスポンス処理」を実行します。
void httpListen() {
    String strBuffer = "";
    WiFiClient client = server.available();

    if (client) {                         // クライアントから着信があれば
        Serial.println("New Client.");
        String currentLine = "";
        while (client.connected()) {      // 接続中に以下を繰り返す
            if (client.available()) {     // 着信データがあれば
                char c = client.read();   // 1バイト読み込んで
                strBuffer += c;           // 受信文を形成する
                if (c == '\n') {
                    // 復帰改行文字で受信領域が空なら、リクエスト処理とレスポンス
                    // を送信してループを脱出する
                    if (currentLine.length() == 0) {
                        httpRequestProccess(&strBuffer);
                        httpSendResponse(&client);
                        break;
                    } else {              // 後続文字があればラインバッファをクリア
                        currentLine = "";
                    }
                } else if (c != '\r') {   // 改行以外ならラインバッファに結合する
                    currentLine += c;
                }
            }
        }
        // コネクションを閉じる
        client.stop();
        Serial.println("Client disconnected.\n");
    }
}


⑥HTTPリクエスト処理

・リクエストメッセージを解析してアクションを起こします。
・163行目 ONボタンがクリックされていればLEDを点灯します。
・167行目 OFFボタンがクリックされていればLEDを消灯します。
void httpRequestProccess(String* strbuf) {
    // 受信メッセージを判別してLEDをオン/オフする
    if (strbuf->indexOf("GET /ON") >= 0) {
        Serial.println("GET /ON");
        ledState = "ON";
        digitalWrite(ledPin, HIGH);
    } else if (strbuf->indexOf("GET /OFF") >= 0) {
        Serial.println("GET /OFF");
        ledState = "OFF";
        digitalWrite(ledPin, LOW);
    }
}


⑦HTTPレスポンス処理

・HTTPレスポンスヘッダー、ページヘッダー、ボディー部を順番にクライアントへ送信します。
・186行目 編集のために、生の文字リテラルで定義したボディー部を作業エリアに取り込みます。
・191行目 クリックされているボタンによって次のボタン表示を設定します。
void httpSendResponse(WiFiClient* client) {
    // HTTPレスポンスヘッダーを送信
    client->println(strResponseHeader);

    // ページヘッダーを送信
    client->println(strHtmlHeader);
    
    // 計測を実行して
    doMeasurement();

    // ページボディー部を編集して送信する
    String buf = strHtmlBody;
    buf.replace("%PAGE_TITLE%", "Web Server");
    buf.replace("%LED_STATE%", ledState);
    buf.replace("%MEASURED_VALUE%", measuredString);
    //(ON/OFFボタンの設定)
    if (ledState=="OFF") {
        buf.replace("%BUTTON_STATE%", strButtonOn);
    } else {
        buf.replace("%BUTTON_STATE%", strButtonOff);
    }
    client->println(buf);
    client->println();    // 最後に、HTTP終端の空行を送信する
}


⑧コネクション確立用

・以前と同じ定型のロジックです。
/* Wi-Fiルーターに接続する */
void connectToWifi() {
    Serial.print("Connecting to 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();
}


4.動作検証

 シリアルモニターを立ち上げて実行を開始します。Wi-Fi接続が完了すると「割り当てられたIPアドレス」が表示されます(ここでは192.168.0.32)。
 パソコンまたはスマートフォンのWebブラウザーを起動して、アドレスバーに「割り当てられたIPアドレス」を入力すると写真・左下のように表示されます。
  ・LED State: OFF
  ・(温度、湿度)
  ・[ON]ボタン
 この状態で[ON]ボタンをクリックまたはタップすると、写真・右のようにボード上の黄色LEDが点灯してボタンが[OFF]に変わります。LED StateはONに、温度と湿度はボタンクリック時の計測値で更新されます。[OFF]をタップするとLEDが消灯し、LED Stateと温湿度が更新されます。ボタン操作の都度、シリアルモニターには経過が表示されます。
 ちなみに、スケッチの138行目前後に次のコードを追加してリコンパイルすると、シリアルモニターでHTTP制御情報の全容を見ることができます。

        Serial.print(c);

 BLEなどに比べてとても大量の情報をやり取りしていることがわかりますね。


 スケッチ全体は、ページ先頭の[Download]ボタンでダウンロードしてください。
 定型の制御構造を利用することで、比較的簡単にWebブラウザーからデバイスを制御できることがわかりました。次回はさらにスライダーを追加して、LEDの輝度を制御してみましょう。お楽しみに!


 
Copyright (C) 2011-2024 Marchan, All rights reserved.