КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Управляем светодиодной лентой со смартфона при помощи Piranha ESP32

Общие сведения:

В этом уроке мы научимся управлять яркостью светодиодной ленты со смартфона при помощи Piranha ESP32

После загрузки скетча в Piranha ESP32 достаточно будет зайти по ссылке http://rgb.local с браузера смартфона или любого другого устройства в локальной сети. Страница управления лентой будет выглядеть следующим образом:



Для компиляции скетча используются два файла: index.h и rgbWebsocket.ino. Текст файла index.h - это веб страница, на которой запускается протокол WebSocket при подключении нового клиента. При помощи этого протокола браузер клиента и скетч Piranha ESP32 обмениваются данными. Обмен происходит в обе стороны, при подключении клиента Piranha ESP32 отправляет текущие данные и следит за их обновлением.

В данном примере будет использоваться библиотека ESPAsyncWebServer. Её необходимо скачать и установить. О том как это сделать можно прочитать в этой статье

Видео:

Нам понадобится:

Подключение:

Не забудьте перед сборкой настроить напряжение на выходе понижающего преобразователя. Оно должно составлять 5В.

RGB Лента Модуль силовых ключей
+12V +Vin
B К1
R К2
G К3
Модуль силовых ключей Piranha ESP32
SDA SDA
SCL SCL
Vcc 5V
GND GND
Схема RGB ленты с управлением через Wifi

Скетч проекта:

Перед компиляцией создайте файл 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>
)=====";

Ссылки




Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями