【IoT】ESP32でwebserverを立ち上げてRGBLEDをスマホから操作する【wak-tech Advent Calender 2018】

ESP32でwebserverを立ち上げてRGBLEDをスマホから調光する

こんにちは。wak-tech Advent Calender 2018 7日目です。
今日はタイトルの通りスマホからRGBLEDの値を変えて調光していきます。

動いている様子

参考にしたサイト

この記事の内容は殆ど「ESP32 Servo Motor Web Server with Arduino IDE」と同じです。参考記事においてはサーボモーターをスマホから操作していましたが、その部分を本記事ではRGBLEDへと変更しています。
それに伴ってスライドバーも3つ付けました。

回路

回路はESP32でRGBLEDを使うと全く同じです。RGBLEDの使い方・購入がまだな方はこちらの記事を先にお試しください。

ソースコード(ほぼコピペで動きます)

#include <WiFi.h>

// 使用するWi-Fiとそのパスワードに書き換えてください
const char* ssid     = "ssid";
const char* password = "password";

// ポート80番を使用
WiFiServer server(80);

// HTTPリクエストを格納する変数
String header;

// 値の設定に使用する変数
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

//ledpin
const int ledR = A18;
const int ledG = A4;
const int ledB = A5;

void setup() {
  //ledc setting
  //R
  ledcSetup(0, 12800, 8);
  // ledRをチャネル0へ接続
  ledcAttachPin(ledR, 0);

  //G
  ledcSetup(1, 12800, 8);
  // ledGをチャネル1へ接続
  ledcAttachPin(ledG, 1);

  //B
  ledcSetup(2, 12800, 8);
  // ledBをチャネル2へ接続
  ledcAttachPin(ledB, 2);

  Serial.begin(115200);

  // Wi-Fiに接続
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // ローカルIPを表示(このIPにスマホなどからアクセスします)
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // 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("Connection: close");
            client.println();

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>ESP32 RGB LED controller</title>");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial; margin-left:auto; margin-right:auto;}");
            client.println("#servoPosR{color: red;}");
            client.println("#servoPosG{color: green;}");
            client.println("#servoPosB{color: blue;}");
            client.println(".slider { width: 300px; }</style>");
            client.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>");
                     
            // Web Page
            client.println("</head><body><h1>ESP32 with RGB LED</h1>");
            //R slide bar
            client.println("<p>Brightness of R: <span id=\"servoPosR\"></span></p>");          
            client.println("<input type=\"range\" min=\"0\" max=\"255\" class=\"slider\" id=\"servoSliderR\" onchange=\"servo(this.value,'R')\" value=\""+valueString+"\"/>");
            //G slide bar
            client.println("<p>Brightness of G: <span id=\"servoPosG\"></span></p>");          
            client.println("<input type=\"range\" min=\"0\" max=\"255\" class=\"slider\" id=\"servoSliderG\" onchange=\"servo(this.value,'G')\" value=\""+valueString+"\"/>");
            //B slide bar
            client.println("<p>Brightness of B: <span id=\"servoPosB\"></span></p>");          
            client.println("<input type=\"range\" min=\"0\" max=\"255\" class=\"slider\" id=\"servoSliderB\" onchange=\"servo(this.value,'B')\" value=\""+valueString+"\"/>");

            client.println("<script>");
            //send R value
            client.println("var sliderR = document.getElementById(\"servoSliderR\");");
            client.println("var servoPR = document.getElementById(\"servoPosR\"); servoPR.innerHTML = sliderR.value;");
            client.println("sliderR.oninput = function() { sliderR.value = this.value; servoPR.innerHTML = this.value; }");

            //send G value
            client.println("var sliderG = document.getElementById(\"servoSliderG\");");
            client.println("var servoPG = document.getElementById(\"servoPosG\"); servoPG.innerHTML = sliderG.value;");
            client.println("sliderG.oninput = function() { sliderG.value = this.value; servoPG.innerHTML = this.value; }");
            //send B value
            client.println("var sliderB = document.getElementById(\"servoSliderB\");");
            client.println("var servoPB = document.getElementById(\"servoPosB\"); servoPB.innerHTML = sliderB.value;");
            client.println("sliderB.oninput = function() { sliderB.value = this.value; servoPB.innerHTML = this.value; }");

            //HTTP getのための関数
            client.println("$.ajaxSetup({timeout:1000}); function servo(pos,color) { ");
            client.println("$.get(\"/?value\" + color + \"=\" + pos + \"&\"); {Connection: close};}</script>");
           
            client.println("</body></html>");     

            //HTTPリクエストの処理部分
            //GET /?value=180& HTTP/1.1
            //Rの値をledの出力に変換
            if(header.indexOf("GET /?valueR=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1+1, pos2);
              
              //LEDをvalueStringの値で点灯
              ledcWrite(0, valueString.toInt());
              Serial.println(valueString); 
            }  
            //Gの値をledの出力に変換
            if(header.indexOf("GET /?valueG=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1+1, pos2);
              
              //LEDをvalueStringの値で点灯
              ledcWrite(1, valueString.toInt());
              Serial.println(valueString); 
            }  

            //Bの値をledの出力に変換
            if(header.indexOf("GET /?valueB=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('&');
              valueString = header.substring(pos1+1, pos2);
              
              //LEDをvalueStringの値で点灯
              ledcWrite(2, valueString.toInt());
              Serial.println(valueString); 
            }
            // HTTPレスポンスの終了
            client.println();
            // Break out of the while loop
            break;
          } else {
            currentLine = "";
          }
        } else if (c != '\r') {
          currentLine += c;
        }
      }
    }
    // Clear the header variable
    header = "";
    // 接続を切断
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

ソースコードの解説

まずwebserver部分ですが、これはESP32を使ってスマホからLチカ(LED点滅)する【webserver】と全く同様です。こちらの記事をご覧になり、HTTPリクエスト関係の理解を深めましょう。

今回肝となるのが、スライドバーの値の送信です。そこを詳しく解説します。

RGBLEDの接続

ESP32でRGBLEDを使うで使用したledcWrite系の関数を使います。

//ledpin
const int ledR = A18;
const int ledG = A4;
const int ledB = A5;

でそれぞれのLEDがつながっているAポートのピンを登録しています。
これにsetup()内で

//R
  ledcSetup(0, 12800, 8);
  // ledRをチャネル0へ接続
  ledcAttachPin(ledR, 0);

とすることで、赤色LEDをチャンネル0に登録しました。よって赤色LEDを点灯させたい場合は

ledcWrite(0,value);

とすればvalueの分だけ明るく光ります。valueの最大値は8bitを指定したので255です。

スライドバーを表示する

コード上では一行ずつ表示されているので非常に見にくいですね。html形式だけで取り出してみると、表示されるページの部分は以下のようになります。

<!DOCTYPE html><html>
<head><meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>body { text-align: center; font-family: "Trebuchet MS", Arial; margin-left:auto; margin-right:auto;}
.slider { width: 300px; }
#servoPosR{color: red;}
#servoPosG{color: green;}
#servoPosB{color: blue;}
</style>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head><body><h1>ESP32 with RGB LED</h1>
<p>Brightness of R: <span id="servoPosR"></span></p>
<input type="range" min="0" max="255" class="slider" id="servoSliderR" onchange="servo(this.value,'R')" value="5"/>
<p>Brightness of G: <span id="servoPosG"></span></p>
<input type="range" min="0" max="255" class="slider" id="servoSliderG" onchange="servo(this.value,'G')" value="5"/>
<p>Brightness of B: <span id="servoPosB"></span></p>
<input type="range" min="0" max="255" class="slider" id="servoSliderB" onchange="servo(this.value,'B')" value="5"/>
<script>

var sliderR = document.getElementById("servoSliderR");
var servoPR = document.getElementById("servoPosR"); servoPR.innerHTML = sliderR.value;
sliderR.oninput = function() { sliderR.value = this.value; servoPR.innerHTML = this.value; }

var sliderG = document.getElementById("servoSliderG");
var servoPG = document.getElementById("servoPosG"); servoPG.innerHTML = sliderG.value;
sliderG.oninput = function() { sliderG.value = this.value; servoPG.innerHTML = this.value; }

var sliderB = document.getElementById("servoSliderB");
var servoPB = document.getElementById("servoPosB"); servoPB.innerHTML = sliderB.value;
sliderB.oninput = function() { sliderB.value = this.value; servoPB.innerHTML = this.value; }

$.ajaxSetup({timeout:1000}); function servo(pos,color) { 
$.get("/?value" + color + "=" + pos + "&"); {Connection: close};}</script>
</body></html>

まず

<p>Brightness of R: <span id="servoPosR"></span></p>
<input type="range" min="0" max="255" class="slider" id="servoSliderR" onchange="servo(this.value,'R')" value="5"/>

とすることで以下のようになります。


後でこのバーの値を取得するため、idを付けておきます。スライドバーのRなのでservoSliderRとしました。
また、スライドバーの値が確定した時にonchange属性にある関数が呼び出されます。onchangeの属性に値をESP32へ送信するservo() 関数を指定しておきます。名前は参考にした記事の名残なので、適当に変えて構いません。

スライダーの値を更新する

スライダーの値を更新しないと、動かしても元に戻ってしまいます。これはjavascriptで解決します。

<script>
var sliderR = document.getElementById("servoSliderR");
var servoPR = document.getElementById("servoPosR"); servoPR.innerHTML = sliderR.value;
sliderR.oninput = function() { sliderR.value = this.value; servoPR.innerHTML = this.value; }
</script>

最初にスライダーの要素をsliderRに、スライダーの上にある値を表示する要素をservoPRに入れます。

次にservoPRの値をsliderRの値に変更します。これで初期設定ができました。5という初期設定値が指定されるはずです。

また、スライダーが変更された時に起こるsliderR.oninputを設定します。これは見ての通りですが、sliderRの値をinputの値(this.value)
に、そしてその値をservoPRの値にも入れ込み表示します。

スライダーの値をHTTP GETで送る

$.ajaxSetup({timeout:1000}); function servo(pos,color) { 
$.get("/?value" + color + "=" + pos + "&"); {Connection: close};}</script>
</body></html>

ここでやっとajaxを使うときが来ました。ajaxの使い方はここでの開設の範囲を超えるので、各自お調べくださいm(__)m

私はここで色の情報を入れ込むために、参考ページでのservo関数の引数にcolorを追加しました。これを指定してHTTP GETの中身を

GET /?valueR=200& HTTP/1.1

のように送信することにしました。

HTTPリクエストを処理する

//Rの値をledの出力に変換
if(header.indexOf("GET /?valueR=")>=0) {
  pos1 = header.indexOf('=');
  pos2 = header.indexOf('&');
  valueString = header.substring(pos1+1, pos2);

  //LEDをvalueStringの値で点灯
  ledcWrite(0, valueString.toInt());
  Serial.println(valueString); 
}  

2行目でそのGETの中身の判定をしています。これで、RGBの出力を分けて認識できるようになりました
次の行は=と&の間にある値を抜き出すものです。これでスライドバーの値が200の時はvalueStringに200が入ります。

この値は文字列として入るので、実際の値にするにはtoInt()メソッドを使います。

ledcWrite(0, valueString.toInt());

これで無事赤色LEDがスライドバーの値で点灯するようになりました。

他の色

他の色に関しては、ソースを見てもらえば分かりますが全てRの場合をコピペしただけです。がんばればもっときれいなソースコードになる。絶対。ごめんなさい。

応用

今回使った技術でいろいろなことができます。
まずinnerHTML辺りの物を使えばセンサーの値を表示できますし、当然扇風機の風量コントロールなんかもできますね。

今回のLEDの部分を様々なモーターやサーボに換えれば簡単にラジコンもできるでしょう。応用は無限大です。何か作ったら是非twitter等でお声掛け下さい!
twitterで新着記事が自動通知されるようになっているのでフォローしていただけると喜びます。

ご覧いただきありがとうございました!

1件のコメント

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です