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調光_明るさをテキストで設定」からの主な変更箇所をハイライト。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
 *    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 件のコメント:

コメントを投稿