Общие сведения:
В этом уроке мы научимся управлять яркостью светодиодной ленты со смартфона при помощи 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>
)=====";

Обсуждение