2018年8月18日土曜日

ESP32/arduino:LED調光_スライダーで明るさを調整

目的:
ESP32基板のLED(IO2) を PWM で調光し、明るさを WEBのスライダーで調整する。

方法:
スライダーは form の <input> で type を 'range' にする。
スライダーの操作時にリアルタイムで値をESP32基板に送信するには、スライダーの値が変わる度に JavaScript で XMLHttpRequest を使用して値を送信する。(非同期通信)
また、ESP32で受信した値をブラウザに送り、ブラウザの表示を更新する様にする。
2019/06/26追加
XHR を多重で発行すると通信がハングアップする事がある様子。
修正 内容は、こちらを参照。
 → 本問題は、
Arduino core for the ESP32 の Ver 1.0.2 の問題と思われる。
   Ver.1.0.3 以降では問題無い。


例:


スライダーを動かすと 値を ESP32基板に送信し、ESP32から戻った値でテキストボックスを更新する。送信された値に応じてESP32基板のLEDの明るさが変化する。
UPボタンで値を +1 し、OFF ボタンで値を 0 にする。
テキストボックスに直接値を設定することも可能。
以下、「LED調光_明るさをテキストで設定」からの主な変更箇所をハイライト。

/*
 *    WiFi LED ON/OFF TEST
 *     PWM Control
 *     slider control
 */
 
#include <WiFi.h>

//#define DEBUG

const char* ssid = "hogehoge";
const char* password = "hogehogepaswd";

IPAddress ip(192, 168, 1, 32);           // for fixed IP Address
IPAddress gateway(192,168, 1, 1);        //
IPAddress subnet(255, 255, 255, 0);      //
IPAddress DNS(192, 168, 1, 90);          //

WiFiServer server(80);

byte led_brightness = 0 ;

void setup()
{
    Serial.begin(115200);
    pinMode(2, OUTPUT);      // set the LED pin mode
    
    WiFi.config(ip, gateway, subnet, DNS);   // Set fixed IP address
    delay(10);

    // We start by connecting to a WiFi network -----------------------------
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected.");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    
    server.begin();

    // for LED PWM Control ---------------------------------------------------
    sigmaDeltaSetup(0, 312500);    // setup channel 0 with frequency 312500 Hz
    sigmaDeltaAttachPin(2,0);      // attach pin 2 to channel 0
    sigmaDeltaWrite(0, 0);         // initialize channel 0 to off     
}

void loop(){
  WiFiClient client = server.available();     // listen for incoming clients
  int pos ;
  int val ;
  int xhr ;
  String cmd = "" ;
  
  if (client) {                                               // if you get a client,
    # ifdef DEBUG
        Serial.println("***** Client access start *****");       // print a message out the serial port
    #endif
    xhr = 0 ;
    while (client.connected()) {                              // loop while the client's connected
      if (client.available()) {                               // if there's bytes to read from the client,
        String line = client.readStringUntil('\n');           // Get Line data until '\n'
        # ifdef DEBUG
            Serial.println(line);
        #endif
        if ((pos= line.indexOf("GET /?slid")) != -1) {
          pos += 11 ;
          while((line.charAt(pos) >='0') & (line.charAt(pos) <='9')) { 
            cmd += line.charAt(pos++) ;
          }
          val = cmd.toInt() ;
          if (val>256) val = 255 ;
          led_brightness = (byte)val ;
          xhr=1;
          # ifdef DEBUG
              Serial.print("led_brightness : ");
              Serial.println(led_brightness) ;
          #endif
        }
        if ((pos= line.indexOf("GET /?led_v")) != -1) {
          pos += 12 ;
          while((line.charAt(pos) >='0') & (line.charAt(pos) <='9')) { 
            cmd += line.charAt(pos++) ;
          }
          val = cmd.toInt() ;
          if (val>256) val = 255 ;
          led_brightness = (byte)val ;
          # ifdef DEBUG
              Serial.print("led_brightness : ");
              Serial.println(led_brightness) ;
          #endif
        }
        if ((pos=line.indexOf("GET /?on")) != -1) {                 // Client request was "GET /?on" 
          led_brightness += 1 ;
          # ifdef DEBUG
              Serial.print("led_brightness : ");
              Serial.println(led_brightness);
          #endif
        }
        if ((pos=line.indexOf("GET /?off")) != -1) {                // Client request was "GET /?off"
          led_brightness = 0 ;
          # ifdef DEBUG
              Serial.print("led_brightness : ");
              Serial.println(led_brightness);
          #endif
        }
        sigmaDeltaWrite(0, led_brightness) ;                        // set PWM value to channel#0

        if (line.length() == 1 && line[0] == '\r'){         // end of HTTP request
          if (xhr == 0)
            send_response(client) ;                                 // send response to client
          else
            send_response2(client) ;                                // send response to client
          break;                                                    // break while loop
        }
      }
    }
    delay(1);                                        // give the web browser time to receive the data
    // close the connection:
    client.stop();
    # ifdef DEBUG
        Serial.println("Client Disconnected.");
        Serial.println("--------------------------------------------------");
    #endif
  }
}

// ------------------------------------------------------------------

void send_response(WiFiClient client) {
    // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
    // and a content-type so the client knows what's coming, then a blank line:
    client.println("HTTP/1.1 200 OK");
    client.println("Content-type:text/html");
    client.println();
    // the content of the HTTP response follows the header:
   
    client.println("<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>" ) ;
    client.println("<style>input.button  {margin:8px;width:100px;}" ) ;
    client.println("       input.button2 {margin-left:8px; width:40px;}" ) ;
    client.println("       input.text    {margin-left:8px; width:25px;}" ) ;
    client.println("       input.slid    {width:230px;}" ) ;
    client.println("       div   {font-size:16pt;text-align:center;width:250px;border:solid 4px #93ff93;} " ) ;
    client.println("       </style>" ) ;
    client.println("<title>Color LED Controller</title></head>" ) ;
    client.println("" ) ;
    client.println("<body>" ) ;
    client.println("<div><p>LED ON/OFF</p>" ) ;
    client.println("  <form method='get' style='text-align:left' > " ) ;
    client.println("    <span style='padding-left:15pt; font-size:8pt ;text-align:left'> LED brightness (0-255)</span> " ) ;
    client.println("    <input class='text'  type='text' name='led_v' id='led_v' value=" ) ;
    client.println(led_brightness ) ;
    client.println("    >" ) ;
    client.println("    <input class='button2' type='submit' name='set' value='SET'>" ) ;
    client.println("  </form> " ) ;
    client.println("" ) ;
    client.println("  <form name='slidform' method='get' style='text-align:left'> " ) ;
    client.print("    <input class='slid' type='range' name='led_s' value=" ) ;
    client.print(led_brightness ) ;
    client.println(" min='80' max='255' step='1' onchange='setval(this.value)' oninput='setval(this.value)' >" ) ;
    client.println("  </form> " ) ;
    client.println("" ) ;
    client.println("  <form method='get'>" ) ;
    client.println("    <input class='button' type='submit' name='on' value='ON'><input class='button' type='submit' name='off' value='OFF'><br>" ) ;
    client.println("  </form>" ) ;
    client.println("" ) ;
    client.println("  </div>" ) ;
    client.println("" ) ;
    client.println("" ) ;
    client.println("<script>" ) ;
    client.println("function setval(ledval){" ) ;
    client.println("    var xhr = new XMLHttpRequest();" ) ;
    client.println("    xhr.open('get', '?slid='+ledval );" ) ;
    client.println("    xhr.timeout = 1000 ;" ) ;
    client.println("    xhr.setRequestHeader('Cache-Control', 'no-cache');" ) ;
    client.println("    xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');" ) ;
    client.println("    xhr.responseType = 'document' ;" ) ;
    client.println("" ) ;
    client.println("    xhr.onreadystatechange = function() {" ) ;
    client.println("        if( (xhr.readyState == 4) && (xhr.status == 200) ) {" ) ;
    client.println("            document.getElementById('led_v').value = xhr.response.getElementById('output1').innerHTML;" ) ;
    client.println("        }" ) ;
    client.println("    }" ) ;
    client.println("    xhr.ontimeout = function(e) {" ) ;
    client.println("        xhr.abort() ;" ) ;
    client.println("    }" ) ;
    client.println("    xhr.send();" ) ;
    client.println("}" ) ;
    client.println("" ) ;
    client.println("</script>" ) ;
    client.println("</body>" ) ;
    client.println("</html>" ) ;
    client.println("") ;
    Serial.println( " --- send responce --- ");
}

void send_response2(WiFiClient client) {
    client.println("HTTP/1.1 200 OK");
    client.println("Content-type:text/html");
    client.println();
    client.println("<!DOCTYPE html><html lang='ja'>" ) ;
    client.println(" <head> <title>Color LED Controller</title></head>") ;
    client.println("<body>") ;
    client.print("<output id='output1'>") ;
    client.print(led_brightness ) ;
    client.println(" </output> ") ;
    client.println("</body>") ;
    client.println("</html>") ;
    # ifdef DEBUG
        Serial.println( " --- send responce2 --- ");
    #endif
}

以下、XMLHttpRequest を使用して非同期通信する場合の覚書など。
--- ESP32 の処理 ( Loop 処理 等 )---
 ( 行# 64,66,71,73,96,99,103,106,110,113,129,132 )
 スライダー操作時のタイムラグをなるべく小さくするため、デバッグ用の表示はデバッグ時のみにする様、ifdef で制御する。
 ( 行# 74-87)
  XMLHttpRequest での get要求を 受けた場合の処理。送信された値を取得し、XMLHttpRequestでの要求であることを覚えておく。(xhr=1;)
 ( 行# 118-122)
  html全体の送信  又は XMLHttpRequest への応答 を選択する。

--- HTML 送信 ----
 ( 行# 150)
 スライダーの幅を設定。
 ( 行# 165-169)
 スライダーを設置。
 値が80未満の場合は LED がほとんど光らなかったため、スライダーの値は 80~255 とした。
 スライダーの値が変化した時(onchange, oninput) 、javascript の setval を実行。(スライダーの値を引数とする。setval で XMLHttpRequest を使用して値を送信処理する)。
 IE と その他で onchange, oninput の動作が異なるため、両方とも setval を実行する様にする。
 ( 行# 179-199)
 非同期通信用の javascript。 主に以下の順に処理を行う。
  var xhr = new  XMLHttpRequest() ;  で新規にオブジェクトを生成。
  xhr.open('get', '?slid='+ledval );" ) ;  で HTTPメソッド、アクセス先URLを指定。
  xhr.onreadystatechange = function() { ...} で応答が返った時の処理を登録。
  xhr.send() ; で 処理を実行 ( リクエストを送信 ) する。
 尚、
  xhr.open の 第2引数で リクエストを送信する url を指定するが、ここに GET で送信するデータ(?slid=<スライダの値>) を入れる。(ESP32基板では ?slid の受信で XMRHttpRequest を受け取ったと判定する。)
  非同期で複数の値を通信した場合、応答が遅れて受信する順序が入れ替わる場合があったため、一定時間以上(ここでは 1秒)はタイムアウトとする様に、xhr.timeout を設定。timeout 時は 処理をアボートする様、xhr.ontimeout に設定する。
  (IE の場合 ?) ブラウザのキャッシュが効いて ESP32 にデータを送信しなくなる場合があったため、キャッシュを無効にする様に設定。
    xhr.setRequestHeader('Cache-Control', 'no-cache');" ) ;
      xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');" ) ;
  XMRHttpRequest の 応答を HTMLで行い、パースするため、 responseType を'document' に設定する。
  応答が返った時の処理内容 :
   readystate=4 かつ status = 200 の時、
   応答の 'output1' の値を html の led_v (テキストボックス) の値に代入する。

--- XMRHttpRequest への応答 ----
 ( 行# 206-221)
  XMRHttpRequest(?slid) を受け取った時の応答
  html で id = 'output1' として led_brightness の値を 送信する。

本例では、XMRHttpRequest の応答として HTML で 応答したが、JSON で応答する様にした方が良いかもしれない。

0 件のコメント:

コメントを投稿