Help us understand the problem. What is going on with this article?

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

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

初めまして。wak-techと申します。初投稿です。
普段はwak-tech.comに電子工作系の記事を投稿しています。

今回はタイトルの通りスマホから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ポートのピンを登録しています。
どのピンがどのポートなのか等はhttps://ht-deko.com/arduino/esp-wroom-32.htmlピンアウトの項目に詳しく記載されています。

これに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で新着記事が自動通知されるようになっているのでフォローしていただけると喜びます。

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

wak_tech
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした