Общие сведения:
В этом уроке мы научимся управлять яркостью светодиодной ленты со смартфона при помощи Piranha ESP32
После загрузки скетча в Piranha ESP32 достаточно будет зайти по ссылке http://rgb.local с браузера смартфона или любого другого устройства в локальной сети. Страница управления лентой будет выглядеть следующим образом:
Для компиляции скетча используются два файла: index.h
и rgbWebsocket.ino
. Текст файла index.h
- это веб страница, на которой запускается протокол WebSocket при подключении нового клиента. При помощи этого протокола браузер клиента и скетч Piranha ESP32 обмениваются данными. Обмен происходит в обе стороны, при подключении клиента Piranha ESP32 отправляет текущие данные и следит за их обновлением.
В данном примере будет использоваться библиотека ESPAsyncWebServer. Её необходимо скачать и установить. О том как это сделать можно прочитать в этой статье
Видео:
Нам понадобится:
- Piranha ESP32
- Модуль силовых ключей: N-канал если светодиодная лента с общим плюсом, или P-канал - если с общим минусом.
- Светодиодная RGB лента
- Источник питания 12V (сила тока в зависимости от мощности ленты)
- Понижающий преобразователь
- Коннектор power jack Мама с клемником
- Провода
Подключение:
Не забудьте перед сборкой настроить напряжение на выходе понижающего преобразователя. Оно должно составлять 5В.
RGB Лента | Модуль силовых ключей |
---|---|
+12V | +Vin |
B | К1 |
R | К2 |
G | К3 |
Модуль силовых ключей | Piranha ESP32 |
---|---|
SDA | SDA |
SCL | SCL |
Vcc | 5V |
GND | GND |
Скетч проекта:
Перед компиляцией создайте файл index.h
в папке скетча и скопируйте в него содержимое из раздела "Файл index.h" или скачайте все файлы проекта
// Подключаем библиотеки #include <WiFi.h> #include <ESPAsyncWebServer.h> #include <ESPmDNS.h> #include <iarduino_I2C_Relay.h> #include "index.h" // Создаём объект силовых ключей Flash-I2C с установленным адресом iarduino_I2C_Relay fets(0x09); // Создаём глобальные переменные яркости цветов светодиодной ленты uint8_t currR = 0; uint8_t currG = 0; uint8_t currB = 0; // Настройки WiFi - необходимо ввести данные Вашего WiFi const char* ssid = "название точки доступа"; const char* password = "пароль точки доступа"; // Локальный адрес сервера const char* host = "rgb"; // Создаём объекты сервера и WebSocket AsyncWebServer server(80); AsyncWebSocket ws("/"); // Callback-функция при получении события WebSocket void onWsEvent( AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len ) { // Если событие - подключение клиента if(type == WS_EVT_CONNECT){ Serial.println("Подключился новый клиент"); // Формируем строку для отправки String resp = (String)"{\"R\":" + currR + "," + "\"G\":" + currG + "," + "\"B\":" + currB + "}"; delay(500); // Отправляем строку client->text(resp); } // Если событие - отключение клиента else if(type == WS_EVT_DISCONNECT){ Serial.println("Клиент отключился"); Serial.println("-----------------------"); } // Если событие - данные else if(type == WS_EVT_DATA){ // Преобразуем байты в объект строки String received = String((char*) data); // Если строка начинается с интересующих нас символов if (received.startsWith("R:")) { // записываем инндекс следующей запятой int next_index = received.indexOf(','); // записываем часть строки, отвечающий за красный цвет в строку R String R = received.substring(received.indexOf('R')+2, next_index); // Преобразуем и записываем данные currR = (uint8_t)R.toInt(); // Записываем индекс следующей запятой next_index = received.indexOf(',', received.indexOf('G')); // записываем часть строки, отвечающий за зелёный цвет в строку G String G = received.substring(received.indexOf('G')+2, next_index); // Преобразуем и записываем данные currG = (uint8_t)G.toInt(); // Записываем индекс точки с запятой next_index = received.indexOf(';'); // записываем часть строки, отвечающий за синий цвет в строку B String B = received.substring(received.indexOf('B')+2, next_index); // Преобразуем и записываем данные currB = (uint8_t)B.toInt(); } } } void setup() { // Инициируем работу с силовыми ключами fets.begin(); // Выключаем все каналы fets.digitalWrite(ALL_CHANNEL, LOW); // Инициируем UART Serial.begin(115200); // Инициируем WiFi WiFi.begin(ssid, password); Serial.print("Подключаемся к WiFi.."); while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.print('.'); } Serial.println(); // Выводим текущий IP адрес Serial.println(WiFi.localIP()); // Инициируем Multicast DNS MDNS.begin(host); // Указываем callback функцию при получении события WebSocket ws.onEvent(onWsEvent); server.addHandler(&ws); // Указываем серверу действие при подключении к корневому каталогу server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/html", String(main_page)); }); // Инициируем сервер server.begin(); } void loop() { // Приводим значения глобальных переменных uint16_t red = map(currR, 0, 100, 0, 4095); uint16_t green = map(currG, 0, 100, 0, 4095); uint16_t blue = map(currB, 0, 100, 0, 4095); // Выводим ШИМ на каналы модуля fets.analogWrite(1, blue); fets.analogWrite(2, red); fets.analogWrite(3, green); // Даём процессору переключится на другие задачи delay(2); }
Файл index.h
:
const char main_page[] PROGMEM = R"=====( <!DOCTYPE html> <html> <head> <link rel="icon" href="data:,"> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>RGB CONTROL</title> <style type="text/css"> *{margin:0;padding:0;border:0;box-sizing:border-box;font-family:'PT Sans',sans-serif}h1{margin-top:30px;color:#333;font-size:24px}.width100{width:100%;box-sizing:border-box}.width1366{width:100%;max-width:1366px;margin:0 auto;box-sizing:border-box;padding-left:10px;padding-right:10px}.hover{text-align:center}.hover .logo{max-height:120px;margin:15px 20px}.BoardPanel{justify-content:left;margin:20px auto;max-width:460px}.BoardPanel .item{background-color:#fff;position:relative;background:#fff;-webkit-box-shadow:0 1px 2px 0 rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15);padding:26px 0 0 0;border-radius:6px;border:1px solid rgba(34,36,38,.15);min-height:130px;margin:10px 5px;width:100%}.BoardPanel .item .variable_name{text-align:left;width:100%;position:absolute;margin:0;top:0;left:0;padding:.75em 1em;border-radius:6px 6px 0 0;display:inline-block;line-height:1;vertical-align:baseline;background-color:#e8e8e8;background-image:none;padding:5px;color:rgba(0,0,0,.87);text-transform:none;font-weight:700;font-size:16px;border:0 solid transparent}.BoardPanel .item .variable_date{width:100%;position:absolute;margin:0;bottom:0;right:0;padding:.75em 1em;display:inline-block;line-height:1;vertical-align:baseline;background-image:none;padding:5px;color:rgba(0,0,0,.4);text-transform:none;font-size:12px;border:0 solid transparent}.BoardPanel .item .variable_value{text-align:center;margin-top:34px;font-weight:700;color:#0071bc;font-size:26px;line-height:1;vertical-align:baseline;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.BoardPanel .item .variable_button{text-align:center;margin-top:10px;vertical-align:baseline}.BoardPanel .item .variable_button .text{font-weight:700;color:#0071bc;font-size:20px;margin-bottom:10px}.BoardPanel .item .slider_value{text-align:center;margin-top:16px;font-weight:700;color:#0071bc;font-size:26px;line-height:1;vertical-align:baseline;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.button{cursor:pointer;display:inline-block;min-height:1em;outline:0;border:none;vertical-align:baseline;background:#e0e1e2 none;color:rgba(0,0,0,.6);font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;margin:0 .25em 0 0;padding:.78571429em 1.5em .78571429em;text-transform:none;text-shadow:none;font-weight:700;line-height:1em;font-style:normal;text-align:center;text-decoration:none;border-radius:.28571429rem;-webkit-box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:opacity .1s ease,background-color .1s ease,color .1s ease,background .1s ease,-webkit-box-shadow .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,background .1s ease,-webkit-box-shadow .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease,-webkit-box-shadow .1s ease;will-change:'';-webkit-tap-highlight-color:transparent}.slidecontainer{margin-top:8px}.slidecontainer input{width:80%}.slider{-webkit-appearance:none;width:100%;height:15px;border-radius:5px;background:#d3d3d3;outline:0;opacity:.7;-webkit-transition:.2s;transition:opacity .2s}.slider:hover{opacity:1}.slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:25px;height:25px;border-radius:50%;background:#4caf50;cursor:pointer}.slider::-moz-range-thumb{width:25px;height:25px;border-radius:50%;background:#4caf50;cursor:pointer}.red{background-color:#db2828;color:#fff;text-shadow:none;background-image:none}.green{background-color:#21ba45;color:#fff;text-shadow:none;background-image:none} </style> </head> <body onload="OpenWebsocket()"> <div class="width100" > <div class="width1366" style='text-align: center;'> <h1>RGB CONTROL</h1> <div id="BoardPanel" class="BoardPanel"> <div class="item"> <div> <div class="variable_name">RED</div> <div class="slider_value" id='valR'></div> <div class="slidecontainer"> <input type="range" min="0" max="100" value="50" class="slider" id="slideR"> </div> </div> </div> <div class="item"> <div> <div class="variable_name">GREEN</div> <div class="slider_value" id='valG'></div> <div class="slidecontainer"> <input type="range" min="0" max="100" value="50" class="slider" id="slideG"> </div> </div> </div> <div class="item"> <div> <div class="variable_name">BLUE</div> <div class="slider_value" id='valB'></div> <div class="slidecontainer"> <input type="range" min="0" max="100" value="50" class="slider" id="slideB"> </div> </div> </div> </div> </div> </div> <script type = "text/javascript"> var ws = null; // Функция WebSocket соединения function OpenWebsocket() { // Открываем соединение ws = new WebSocket("ws://" + location.hostname); // Вызываем функцию при получении данных ws.onmessage = function(e) { // Парсируем полученные данные const received = JSON.parse(e.data); let index = 0; // Проходим по полученным данным for (const key in received) { // Записываем значения слайдеров slider_values[index].value = received[key]; // Записываем отображение значений disp_values[index].innerHTML = received[key]; index++; } }; } // Функция закрытия соединения function CloseWebsocket() { if (ws != null) ws.close(); } // Функция отправки данных function SendData() { // Создаём массив значений цветов const colors = []; // Добавляем цвета в массив for (const s_val of slider_values) { colors.push(s_val.value); } // Если соединение установлено if (ws.readyState === WebSocket.OPEN) { // Формируем строку для отправки const textToSend = "R:" + colors[0] + "," + "G:" + colors[1] + "," + "B:" + colors[2] + ";" + "\n\r"; // Отправляем строку ws.send(textToSend); } } /* Создаём коллекции HTML по имени класса */ // Значение ползунка const slider_values = document.getElementsByClassName("slider"); // Отображение значения цифрами const disp_values = document.getElementsByClassName("slider_value"); // Объекты ползунка const sliders_div = document.getElementsByClassName("slidecontainer"); // Проходим по всем ползункам for (const slider of sliders_div) { // Заполняем поле изменения значения функцией slider.onchange = function() { // Меняем отображение значения в соответствии со положением ползунка for (let j = 0; j < disp_values.length; j++) { disp_values[j].innerHTML = slider_values[j].value; } // Отправляем данные SendData(); }; } </script> </body> </html> )=====";
Обсуждение