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

# ESP32でwebserverを立ち上げてRGBLEDをスマホから調光する
初めまして。wak-techと申します。初投稿です。
普段は[wak-tech.com](https://wak-tech.com/)に電子工作系の記事を投稿しています。

今回はタイトルの通り**スマホからRGBLEDの値を変えて調光**していきます。

## 動いている様子

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ESP32開発ボードでRGBLEDをスマホから操作できました。完全フルカラー対応です！！🏮📗📘 <a href="https://t.co/wyzMKezmDV">pic.twitter.com/wyzMKezmDV</a></p>&mdash; 𝘄𝗮𝗸-𝘁𝗲𝗰𝗵 (@wak_tech) <a href="https://twitter.com/wak_tech/status/1074192293500862464?ref_src=twsrc%5Etfw">2018年12月16日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<iframe width="560" height="315" src="https://www.youtube.com/embed/G6fFIzwMnow" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## 参考にしたサイト
この記事の内容は殆ど「[ESP32 Servo Motor Web Server with Arduino IDE](https://randomnerdtutorials.com/esp32-servo-motor-web-server-arduino-ide/)」と同じです。参考記事においてはサーボモーターをスマホから操作していましたが、その部分を本記事では**RGBLED**へと変更しています。
それに伴ってスライドバーも３つ付けました。
## 回路

回路は以下の通りで、[ESP32でRGBLEDを使う](https://wak-tech.com/archives/748)と全く同じです。RGBLEDの使い方・購入がまだな方はこちらの記事を先にご覧ください。

![](https://i.imgur.com/1kGaWom.png)

## ソースコード（ほぼコピペで動きます）

```C++
#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】](https://wak-tech.com/archives/719)と全く同様です。こちらの記事をご覧になり、HTTPリクエスト関係の理解を深めましょう。

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

### RGBLEDの接続
[ESP32でRGBLEDを使う](https://wak-tech.com/archives/748)で使用した**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](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"/>
```
とすることで以下のようになります。

![](https://i.imgur.com/Tq7vWAe.png)
後でこのバーの値を取得するため、**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); 
}  
```

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

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

```
ledcWrite(0, valueString.toInt());
```
これで無事赤色LEDがスライドバーの値で点灯するようになりました。

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

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

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

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