---
# System prepended metadata

title: LAB3 遠端控制 RGB LED 燈光秀
tags: [嵌入式系統]

---

# LAB3 遠端控制 RGB LED 燈光秀

## demo
<iframe width="560" height="315" src="https://www.youtube.com/embed/kZ1sO3Nf2ws?si=lOWUC2_PqTIMbk3c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## 電路圖
![image](https://hackmd.io/_uploads/BJUjEDbJyl.png)

## 程式碼說明
- 首先是wifi的設定
    ```c=
    void setwifi(){
  
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
      }
      Serial.println(" Successfully Connected to WiFi");
      Serial.print(" Your IP Address: ");
      Serial.println(WiFi.localIP());
    }
    ```
- 需求上有幾點
    1. Slidebar 調整顏色 
    2. 燈光秀
    - 因此可以稍微規劃一下架構
        ![image](https://hackmd.io/_uploads/S19guvWykx.png)
    - 主要控制頁面
        ```c++=
        server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
            request->send(200,"text/html",HTMLpage());
          });
        ```
        - 頁面放在函數中了，不然全部放在一起很可怕
        - 話說Python是好工具，字串處理有夠好用，感謝王老闆orz，教我把HTML轉成cpp
    - API介面
        - 更換顏色
            ```c++=
            server.on("/change_color", HTTP_GET, [](AsyncWebServerRequest *request){
                if (request->hasArg("r") && request->hasArg("g") && request->hasArg("b")){
                  red_val = request->arg("r").toInt();
                  green_val = request->arg("g").toInt();
                  blue_val = request->arg("b").toInt();

                  color(red_val,green_val,blue_val);
                }
                request->redirect("/");
          });
            ```
        - 開啟燈光秀
            ```c++=
            server.on("/show/on", HTTP_GET, [](AsyncWebServerRequest *request){
                show = true;
                request->redirect("/");
            });
            ```
        - 關閉燈光秀
            ```c++=
            server.on("/show/off", HTTP_GET, [](AsyncWebServerRequest *request){
                show = false;
                red_val = 128;
                green_val = 128;
                blue_val = 128;
                delay(4500);
                color(red_val,green_val,blue_val);
                request->redirect("/");
            });
            ```
    - 燈光秀
        - 燈光秀由global varible控制，如果是真，就會顯示燈光秀
        ```c++=
        if (show){
              // 隨機變化顏色並產生閃爍效果
            for (int i = 0; i < 10; i++) {
                int red = random(0, 256);   
                int green = random(0, 256); 
                int blue = random(0, 256);

                // 快速閃爍效果
                color(red, green, blue);
                delay(random(50, 150));  // 延遲時間隨機，模擬不同頻率閃爍效果

                // 暫時關閉LED，模擬閃爍
                color(0, 0, 0);
                delay(random(50, 100));  // 閃爍的間隔時間
            }

            // 漸變效果
            for (int brightness = 0; brightness <= 255; brightness += 5) {
                color(brightness, 0, 255 - brightness); // 由紅漸變到藍
                delay(20);
            }
            for (int brightness = 255; brightness >= 0; brightness -= 5) {
                color(brightness, 0, 255 - brightness); // 由藍漸變到紅
                delay(20);
            }
        }
        ```
    - 燈光調節請求
        ```js=
        function updateRGB() {
          var red = document.getElementById("redSlider").value;
          var green = document.getElementById("greenSlider").value;
          var blue = document.getElementById("blueSlider").value;

          // Update the text display
          document.getElementById("redValue").innerHTML = red;
          document.getElementById("greenValue").innerHTML = green;
          document.getElementById("blueValue").innerHTML = blue;

          // 改變顯示 RGB 顏色的區塊
          document.getElementById("colorDisplay").style.backgroundColor = 'rgb(' + red + ',' + green + ',' + blue + ')';
          document.getElementById("colorDisplay2").style.backgroundColor = 'rgb(' + (255-red) + ',' + (255-green) + ',' + (255-blue) + ')';

          // Send the RGB values to the server
          var xhr = new XMLHttpRequest();
          xhr.open("GET", "/change_color?r=" + red + "&g=" + green + "&b=" + blue, true);
          xhr.send();
        }
        ```
        
## 程式碼
```c++=
/*
 * 使用WiFi.h函數庫，預設
 * 使用analogWrite.h函數庫，預設
 * 使用ESPAsyncWebServer.h，需要安裝
*/
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <analogWrite.h>


#define R1_PIN 13
#define G1_PIN 27
#define B1_PIN 26

#define R2_PIN 25
#define G2_PIN 33
#define B2_PIN 32
 
const char* ssid     = "xxxxxxxxxxxxxx";
const char* password = "xxxxxxxxxxxxxx";

bool show = false;

unsigned char red_val = 0;
unsigned char green_val = 0;
unsigned char blue_val = 0;


AsyncWebServer server(80);

void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:
  setwifi();
  color(128,128,128);
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200,"text/html",HTMLpage());
  });
  server.on("/change_color", HTTP_GET, [](AsyncWebServerRequest *request){
    if (request->hasArg("r") && request->hasArg("g") && request->hasArg("b")){
      red_val = request->arg("r").toInt();
      green_val = request->arg("g").toInt();
      blue_val = request->arg("b").toInt();

      color(red_val,green_val,blue_val);
    }
    request->redirect("/");
  });

  server.on("/show/on", HTTP_GET, [](AsyncWebServerRequest *request){
    show = true;
    request->redirect("/");
  });

  server.on("/show/off", HTTP_GET, [](AsyncWebServerRequest *request){
    show = false;
    red_val = 128;
    green_val = 128;
    blue_val = 128;
    delay(4500);
    color(red_val,green_val,blue_val);
    request->redirect("/");
  });
  
  server.begin();
}

void loop() {
  if (show){
      // 隨機變化顏色並產生閃爍效果
    for (int i = 0; i < 10; i++) {
        int red = random(0, 256);   
        int green = random(0, 256); 
        int blue = random(0, 256);
        
        // 快速閃爍效果
        color(red, green, blue);
        delay(random(50, 150));  // 延遲時間隨機，模擬不同頻率閃爍效果
        
        // 暫時關閉LED，模擬閃爍
        color(0, 0, 0);
        delay(random(50, 100));  // 閃爍的間隔時間
    }

    // 漸變效果
    for (int brightness = 0; brightness <= 255; brightness += 5) {
        color(brightness, 0, 255 - brightness); // 由紅漸變到藍
        delay(20);
    }
    for (int brightness = 255; brightness >= 0; brightness -= 5) {
        color(brightness, 0, 255 - brightness); // 由藍漸變到紅
        delay(20);
    }
  }
  delay(50);
}
void setwifi(){
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println(" Successfully Connected to WiFi");
  Serial.print(" Your IP Address: ");
  Serial.println(WiFi.localIP());
}
void color (unsigned char red, unsigned char green, unsigned char blue) {
  analogWrite(R1_PIN, red);
  analogWrite(G1_PIN, green);
  analogWrite(B1_PIN, blue);

  analogWrite(R2_PIN, 255-red);
  analogWrite(G2_PIN, 255-green);
  analogWrite(B2_PIN, 255-blue);
}
String HTMLpage(){
  String html     = "";
  String disabled = "";  
  if (show){
    disabled = "disabled";
  }
  html += "<!DOCTYPE html>\n";
  html += "<html  lang=\"zh-tw\">\n";
  html += "<head>\n";
  html += "  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n";
  html += "  <meta charset=\"utf-8\">\n";
  html += "  <link rel=\"icon\" href=\"data:,\">\n";
  html += "  <style>\n";
  html += "    html { \n";
  html += "      font-family: Helvetica; \n";
  html += "      display: inline-block; \n";
  html += "      margin: 0px auto; \n";
  html += "      text-align: center;\n";
  html += "    }\n";
  html += "    body {\n";
  html += "      background-color: #f2f2f2; /* 保持淡色背景 */\n";
  html += "    }\n";
  html += "    .button-container {\n";
  html += "      text-align: center;\n";
  html += "    }\n";
  html += "    .button-container p {\n";
  html += "      display: inline-block;\n";
  html += "      margin: 10px;\n";
  html += "    }\n";
  html += "    .button {\n";
  html += "      background-color: #000000;\n";
  html += "      border: none;\n";
  html += "      color: white;\n";
  html += "      padding: 16px 40px;\n";
  html += "      text-decoration: none;\n";
  html += "      border-radius: 5px;\n";
  html += "      font-size: 20px;\n";
  html += "      cursor: pointer;\n";
  html += "    }\n";
  html += "    .button2 {\n";
  html += "      background-color: #555555;\n";
  html += "    }\n";
  html += "    /* 自定義滑桿樣式 */\n";
  html += "    input[type=range] {\n";
  html += "      -webkit-appearance: none;\n";
  html += "      width: 30%;\n";
  html += "      height: 15px;\n";
  html += "      border-radius: 5px;\n";
  html += "      background: #ddd;\n";
  html += "      outline: none;\n";
  html += "      opacity: 0.7;\n";
  html += "      -webkit-transition: .2s;\n";
  html += "      transition: opacity .2s;\n";
  html += "    }\n";
  html += "    input[type=range]:hover {\n";
  html += "      opacity: 1;\n";
  html += "    }\n";
  html += "    /* 滑塊樣式 */\n";
  html += "    input[type=range]::-webkit-slider-thumb {\n";
  html += "      -webkit-appearance: none;\n";
  html += "      appearance: none;\n";
  html += "      width: 25px;\n";
  html += "      height: 25px;\n";
  html += "      border-radius: 50%;\n";
  html += "      background: #4CAF50;\n";
  html += "      cursor: pointer;\n";
  html += "    }\n";
  html += "    input[type=range]::-moz-range-thumb {\n";
  html += "      width: 25px;\n";
  html += "      height: 25px;\n";
  html += "      border-radius: 50%;\n";
  html += "      background: #4CAF50;\n";
  html += "      cursor: pointer;\n";
  html += "    }\n";
  html += "    /* 根據 RGB 通道設置不同背景顏色 */\n";
  html += "    #redSlider {\n";
  html += "      background: linear-gradient(to right, red, #fff);\n";
  html += "    }\n";
  html += "    #greenSlider {\n";
  html += "      background: linear-gradient(to right, green, #fff);\n";
  html += "    }\n";
  html += "    #blueSlider {\n";
  html += "      background: linear-gradient(to right, blue, #fff);\n";
  html += "    }\n";
  html += "    .color-display-container {\n";
  html += "      display: flex;\n";
  html += "      justify-content: center; /* 將兩個顏色區塊置中對齊 */\n";
  html += "      gap: 20px; /* 減少兩個區塊之間的間距 */\n";
  html += "    }\n";
  html += "      \n";
  html += "    /* 新增顯示 RGB 顏色的區塊 */\n";
  html += "    .color-display {\n";
  html += "      width: 100px;\n";
  html += "      height: 100px;\n";
  html += "      margin: 0px ;\n";
  html += "      border-radius: 10px;\n";
  html += "      border: 2px solid #000;\n";
  html += "      background-color: rgb(128, 128, 128); /* 初始顏色 */\n";
  html += "      transition: background-color 0.5s ease;\n";
  html += "    }\n";
  html += "    /* 七彩燈光秀按鈕動畫 */\n";
  html += "    .rainbow-button {\n";
  html += "      padding: 16px 40px;\n";
  html += "      font-size: 20px;\n";
  html += "      border: none;\n";
  html += "      border-radius: 5px;\n";
  html += "      color: white;\n";
  html += "      cursor: pointer;\n";
  html += "      text-decoration: none;\n";
  html += "      background-size: 400% 400%;\n";
  html += "      background: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet);\n";
  html += "      animation: rainbow 3s ease infinite;\n";
  html += "    }\n";
  html += "  </style>\n";
  html += "</head>\n";
  html += "<body>\n";
  html += "  <h1>ESP32 Web Server</h1>\n";
  html += "  <!-- RGB LED Control -->\n";
  html += "  <h2>RGB LED Control</h2>\n";
  html += "  <!-- 顯示 RGB 顏色的區塊 -->\n";
  html += "  <div class=\"color-display-container\">\n";
  html += "    <div class=\"color-display\" id=\"colorDisplay\"></div>\n";
  html += "      <div class=\"color-display\" id=\"colorDisplay2\"></div>\n";
  html += "  </div>\n";
  html += "  <!-- Red Slider -->\n";
  html += "  <p>Red: <span id=\"redValue\">128</span></p>\n";
  html += "  <input type=\"range\" min=\"0\" max=\"255\" value=\"128\" class=\"slider\" id=\"redSlider\" oninput=\"updateRGB()\""  + disabled +  ">\n";
  html += "  <!-- Green Slider -->\n";
  html += "  <p>Green: <span id=\"greenValue\">128</span></p>\n";
  html += "  <input type=\"range\" min=\"0\" max=\"255\" value=\"128\" class=\"slider\" id=\"greenSlider\" oninput=\"updateRGB()\"" + disabled +  ">\n";
  html += "  <!-- Blue Slider -->\n";
  html += "  <p>Blue: <span id=\"blueValue\">128</span></p>\n";
  html += "  <input type=\"range\" min=\"0\" max=\"255\" value=\"128\" class=\"slider\" id=\"blueSlider\" oninput=\"updateRGB()\""  + disabled +  ">\n";
  html += "  <!-- Display current state and ON/OFF buttons for GPIO 26 & GPIO 27 -->\n";
  html += "  <div class=\"button-container\">\n";
  html += "    <p><a href=\"/show/off\"><button class=\"button\">停止🛑</button></a></p>\n";
  html += "    <p><a href=\"/show/on\"><button class=\"rainbow-button\">燈光秀</button></a></p>\n";
  html += "  </div>\n";
  html += "  <!-- JavaScript to handle slider value and update display -->\n";
  html += "  <script>\n";
  html += "    function updateRGB() {\n";
  html += "      var red = document.getElementById(\"redSlider\").value;\n";
  html += "      var green = document.getElementById(\"greenSlider\").value;\n";
  html += "      var blue = document.getElementById(\"blueSlider\").value;\n";
  html += "      document.getElementById(\"redValue\").innerHTML = red;\n";
  html += "      document.getElementById(\"greenValue\").innerHTML = green;\n";
  html += "      document.getElementById(\"blueValue\").innerHTML = blue;\n";
  html += "      document.getElementById(\"colorDisplay\").style.backgroundColor = 'rgb(' + red + ',' + green + ',' + blue + ')';\n";
  html += "      document.getElementById(\"colorDisplay2\").style.backgroundColor = 'rgb(' + (255-red) + ',' + (255-green) + ',' + (255-blue) + ')';\n";
  html += "      var xhr = new XMLHttpRequest();\n";
  html += "      xhr.open(\"GET\", \"/change_color?r=\" + red + \"&g=\" + green + \"&b=\" + blue, true);\n";
  html += "      xhr.send();\n";
  html += "    }\n";
  html += "  </script>\n";
  html += "</body>\n";
  html += "</html>\n";

  return html;
}
```