Skip to content

Простой конструктор веб интерфейса для esp8266 и ESP32

License

Notifications You must be signed in to change notification settings

code-ql-testing/GyverPortal_1

 
 

Repository files navigation

latest Foo Foo Foo

Foo

GyverPortal v3

Релиз v3 пока не вышел, но вы можете скачать репозиторий и присоединиться к тестированию.

Есть изменения в логике работы и частичная несовместимость. Список функций и методов ниже - актуальный, остальная документация ниже слегка устарела. Полная документация к v3 находится в разработке и доступна на Wiki репозитория. Также руководствуйтесь примерами.

demo

Простой конструктор веб интерфейса для ESP8266 и ESP32

  • Позволяет быстро создать универсальную вебморду для управления и настройки своего девайса
  • Возможность создания многостраничных и динамических веб-интерфейсов в несколько строк кода
  • Не требует знания HTML, CSS и JavaScript. Все стили и скрипты уже заложены в библиотеке
  • Не требует загрузки файлов в SPIFFS, но стили и скрипты можно подгружать оттуда
  • Страница собирается из готовых компонентов конструктора прямо в скетче
  • Лёгкий вес, небольшое использование динамической памяти во время генерации страницы
  • Работает на базе стандартных библиотек esp, ничего дополнительно устанавливать не нужно
  • Относительно стильный дизайн, светлая и тёмная темы, возможность кастомизации некоторых компонентов
  • Встроенные модули:
    • Автоматизированная загрузка файлов
    • Автоматизированное скачивание файлов
    • Кеширование файлов из SPIFFS памяти
    • AJAX и jQuery (опционально) обновление значений на странице
    • Автоматический опрос и обновление переменных
    • Перезагрузка страницы из скетча
    • Авторизация на сервере по логину-паролю
    • DNS сервер (для работы как точка доступа)
    • mDNS (для открытия интерфейса по заданному адресу вместо IP)
    • OTA обновление прошивки и памяти через браузер (возможна защита паролем)
  • Компоненты конструктора:
    • Оформление
      • Заголовок
      • Подпись
      • Разделитель
      • Перенос строки
    • Разметка страницы
      • Блок для объединения компонентов по вертикали (2 стиля с заголовком, 2 без)
      • Блок для объединения компонентов по горизонтали с настройкой выравнивания
      • Объединение вертикальных блоков по горизонтали + responsive
      • Макросы блоков для упрощения кода конструктора
    • Форма
      • Веб-форма
      • Кнопка submit
    • Прочие компоненты
      • Текст на цветной подложке
      • Поле ввода текста
      • Многострочное поле ввода текста
      • Поле ввода цифр
      • Поле ввода пароля
      • Спиннер (поле с цифрой и кнопками +-)
      • Галочка (чекбокс)
      • Выключатель
      • Слайдер
      • Спойлер
      • Выбор времени
      • Выбор даты
      • Выбор цвета
      • Выпадающий список (дропбокс)
      • Кнопка
      • Кнопка-ссылка
      • Кнопка-скачивание файла
      • Кнопка загрузки файла на сервер
      • "Светодиод" индикатор трёх типов
      • Окно лога для отладки (веб Serial порт)
      • Несколько типов графиков
      • FontAwesome или локальные иконки для кнопок
      • Блок динамической навигации со вкладками
      • Блок навигации со ссылками
      • Блоки для вывода изображений, видео и текстовых файлов из SPIFFS
      • Всплывающие окна: alert, prompt, confirm с отправкой действия в программу
      • Всплывающие подсказки для всех компонентов

demo

Совместимость

esp8266, esp32

Известные баги

Некоторые элементы могут некрасиво отображаться на Firefox, т.к. сделаны под Chrome, Safari, Edge, Opera

Содержание

Установка

  • Библиотеку можно найти по названию GyverPortal и установить через менеджер библиотек в:
    • Arduino IDE
    • Arduino IDE v2
    • PlatformIO
  • Скачать библиотеку .zip архивом для ручной установки:
    • Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
    • Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
    • Распаковать и положить в Документы/Arduino/libraries/
    • (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
  • Читай более подробную инструкцию по установке библиотек здесь

Обновление

  • Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
  • Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
  • Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!

Инициализация

GyverPortal portal;

Документация

Функции конструктора
// ======== СОЗДАНИЕ СТРАНИЦЫ ========
GP.BUILD_BEGIN();                   // начать построение (начальный HTML код), ширина колонки 350px
GP.BUILD_BEGIN(ширина);             // + ширина колонки (int) в px

GP.BUILD_BEGIN_FILE();              // то же самое, но файл скриптов скачается из памяти (/gp_data/scripts.js)
GP.BUILD_BEGIN_FILE(ширина);        // + ширина колонки (int) в px

GP.BUILD_END();                     // завершить построение (завершающий HTML код)

// =============== ТЕМА ==============
GP.THEME(тема);                     // установить тему из памяти прошивки (GP_LIGHT, GP_DARK)
GP.THEME_FILE(имя);                 // установить тему из файла, файл класть в /gp_data/, файл указывать без расширения (напр. "GP_DARK")

// =========== AJAX UPDATE ===========
GP.UPDATE(список);                  // обновление. Передать список компонентов через запятую без пробелов
GP.UPDATE(список, период);          // + период обновления в мс (умолч. 1000)

// =============== LOG ==============
GP.AREA_LOG();                          // окно лога
GP.AREA_LOG(строк);                     // + кол-во строк (умолч. 5)
GP.AREA_LOG(строк, период);             // + период обновления, мс (умолч. 1000)

GP.AREA_LOG(имя, лог);                  // окно лога ручное. Передать объект GPlog
GP.AREA_LOG(имя, лог, строк);           // + кол-во строк (умолч. 5)
GP.AREA_LOG(имя, лог, строк, период);   // + период обновления, мс (умолч. 1000)

// =========== JQUERY UPD ===========
GP.JQ_SUPPORT();                        // поддержка jquery. Файл скачается с https://code.jquery.com/
GP.JQ_SUPPORT_FILE();                   // поддержка jquery, файл скачается из памяти (/gp_data/jquery.js)
GP.JQ_UPDATE_BEGIN();                   // начать обновляемый блок с периодом 1 секунда (один на страницу!)
GP.JQ_UPDATE_BEGIN(период);             // + период обновления в мс
GP.JQ_UPDATE_BEGIN(период, задержка);   // + задержка клика (умолч. 100мс)
GP.JQ_UPDATE_END();                     // закончить обновляемый блок

// ============= POP UP =============
GP.ALERT(имя);                      // всплывающее окно предупреждения. Ответь на update текстом, он будет отображён
GP.ALERT(имя, период);              // + период обновления в мс (умолч. 1000)
GP.ALERT(имя, текст);               // Ответь на update цифрой 1, будет отображён текст
GP.ALERT(имя, текст, период);       // + период обновления в мс (умолч. 1000)

GP.PROMPT(имя);                     // всплывающее окно с полем вводом текста. Ответь на update текстом, он будет отображён
GP.PROMPT(имя, период);             // + период обновления в мс (умолч. 1000)
GP.PROMPT(имя, текст);              // Ответь на update цифрой 1, будет отображён текст
GP.PROMPT(имя, текст, период);      // + период обновления в мс (умолч. 1000)

GP.CONFIRM(имя);                    // всплывающее окно с кнопкой ДА и ОТМЕНА. Ответь на update текстом, он будет отображён
GP.CONFIRM(имя, период);            // + период обновления в мс (умолч. 1000)
GP.CONFIRM(имя, текст);             // Ответь на update цифрой 1, будет отображён текст
GP.CONFIRM(имя, текст, период);     // + период обновления в мс (умолч. 1000)

// ============== RELOAD =============
GP.RELOAD(имя);                     // скрытый блок перезагрузки страницы. Добавь его имя в UPDATE и ответь 1 на update, чтобы обновить страницу

// ========== ФОРМАТИРОВАНИЕ =========
GP.BREAK();                         // перенести строку
GP.HR();                            // горизонтальная линия-разделитель

// =============== СЕТКА ==============
GP.GRID_BEGIN();                    // начать сборку BLOCK блоков по горизонтальной сетке
GP.GRID_END();                      // завершить
GP.GRID_RESPONSIVE(ширина);         // располагать блоки по вертикали, если ширина меньше указанной. int, в пикселях (например 700). Указывать ПОСЛЕ GP.THEME()!!!

// ============== СПОЙЛЕР =============
GP.SPOILER_BEGIN(текст);            // начать спойлер
GP.SPOILER_BEGIN(текст, цвет);      // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.SPOILER_END();                   // завершить спойлер

// =============== БЛОК ==============
GP.BLOCK_BEGIN();                           // начать отрисовку блока (объединение компонентов по вертикали)
GP.BLOCK_BEGIN(ширина);                     // + ширина строкой "100px", "25%" и так далее (умолч. 100%)

GP.BLOCK_TAB_BEGIN(текст);                  // блок с подписью на плашке
GP.BLOCK_TAB_BEGIN(текст, ширина);          // + ширина строкой "100px", "25%" и так далее (умолч. 100%)
GP.BLOCK_TAB_BEGIN(текст, ширина, цвет);    // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")

GP.BLOCK_THIN_BEGIN();                      // блок без подложки
GP.BLOCK_THIN_BEGIN(ширина);                // + ширина строкой "100px", "25%" и так далее (умолч. 100%)

GP.BLOCK_THIN_TAB_BEGIN(текст);             // блок с подписью на плашке
GP.BLOCK_THIN_TAB_BEGIN(текст, ширина);     // + ширина строкой "100px", "25%" и так далее (умолч. 100%)

GP.BLOCK_END();                             // завершить отрисовку блока (любого выше)

// =========== ОБЪЕДИНЕНИЕ ==========
GP.BOX_BEGIN();                     // начать объединение компонентов по горизонтали
GP.BOX_BEGIN(выравнивание);         // + выравнивание (GP_CENTER, GP_LEFT, GP_RIGHT, GP_EDGES), умолч. GP_EDGES
GP.BOX_BEGIN(выравнивание, ширина); // + ширина строкой "100px", "25%" и так далее (умолч. 100%)
GP.BOX_END();                       // завершить объединение

// ============ НАВИГАЦИЯ ===========
GP.NAV_TABS_LINKS(список адресов, список подписей);         // блок с кнопками-ссылками
GP.NAV_TABS_LINKS(список адресов, список подписей, цвет);   // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")

GP.NAV_TABS(список);                // блок динамической навигации, передать список вкладок
GP.NAV_TABS(список, цвет);          // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")

GP.NAV_BLOCK_BEGIN();               // начало вкладки навигации
GP.NAV_BLOCK_END();                 // конец вкладки навигации

// ============ ПОДПИСИ =============
GP.TITLE(текст);                    // заголовок
GP.TITLE(текст, имя);               // + имя компонента (для update())

GP.LABEL(текст);                    // подпись (для кнопок, полей, чекбоксов итд)
GP.LABEL(текст, имя);               // + имя компонента (для update())

GP.SPAN(текст);                     // просто текст
GP.SPAN(текст, выравнивание);       // + выравнивание строкой "center", "right", "left", "justify" (оставь пустым "" чтобы было по центру)
GP.SPAN(текст, выравнивание, имя);  // + имя компонента (для update())

GP.LABEL_BLOCK(текст);              // яркий текстовый лейбл
GP.LABEL_BLOCK(текст, имя);         // + имя компонента (для update())
GP.LABEL_BLOCK(текст, имя, цвет);   // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")

GP.HINT(имя, текст);                // всплывающая подсказка с текстом "текст" для элемента "имя". Вызывать после добавления компонента

// ========== ИНДИКАТОРЫ ============
GP.LED(имя);                        // зелёный - вкл, красный - выкл
GP.LED(имя, состояние);             // + состояние (bool)

GP.LED_GREEN(имя);                  // зелёный - вкл, чёрный - выкл
GP.LED_GREEN(имя, состояние);       // + состояние (bool)

GP.LED_RED(имя);                    // красный - вкл, чёрный - выкл
GP.LED_RED(имя, состояние);         // + состояние (bool)

// ============= ИКОНКИ =============
GP.ICON_SUPPORT();                  // добавить поддержку иконок FontAwesome (требуется подключение к Интренет)
String GP.ICON(имя);                // вставка иконки FontAwesome. Имена - https://fontawesome.com/v4/icons/ , указывать без "fa", например "cloud"
String GP.ICON(имя, размер);        // + размер (int) в пикселях

String GP.ICON_FILE(uri, размер);   // отобразить иконку из файла SPIFFS (должнен быть настроен download), размер int в px

// ============= ФОРМА ==============
GP.FORM_BEGIN(имя);                 // начать форму с именем (имя)
GP.FORM_END();                      // завершить форму
GP.SUBMIT(текст);                   // кнопка отправки формы
GP.SUBMIT(текст, цвет);             // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.HIDDEN(имя, значение);           // скрытый элемент

GP.FORM_SUBMIT(имя, текст);         // пустая форма с кнопкой submit
GP.FORM_SUBMIT(имя, текст, цвет);   // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")

GP.FORM_SUBMIT(имя, текст, имя hidden, значение hidden);        // пустая форма с кнопкой submit и HIDDEN компонентом
GP.FORM_SUBMIT(имя, текст, имя hidden, значение hidden, цвет);  // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")

// ============= ФАЙЛЫ =============
GP.FILE_UPLOAD(имя);                    // кнопка для загрузки файла (файлов) на сервер
GP.FILE_UPLOAD(имя, текст);             // + текст на кнопке
GP.FILE_UPLOAD(имя, текст, расширения); // + разрешённые для загрузки типы данных (см тут https://www.w3schools.com/tags/att_input_accept.asp)

GP.FOLDER_UPLOAD(имя);              // кнопка для загрузки папки с файлами на сервер. Название файла будет содержать полный путь в указанной папке
GP.FOLDER_UPLOAD(имя, текст);       // + текст на кнопке

GP.OTA_FIRMWARE();                  // загрузка файла прошивки для обновления
GP.OTA_FIRMWARE(текст);             // + текст на кнопке

GP.OTA_FILESYSTEM();                // загрузка файла файловой системы для обновления
GP.OTA_FILESYSTEM(текст);           // + текст на кнопке

GP.IMAGE(ссылка);                   // картинка. Указать ссылку на файл в памяти
GP.IMAGE(ссылка, ширина);           // + ширина строкой "100px", "25%" и так далее (умолч "", т.е. авто)

GP.VIDEO(ссылка);                   // видео. Указать ссылку на файл в памяти
GP.VIDEO(ссылка, ширина);           // + ширина строкой "100px", "25%" и так далее (умолч "", т.е. авто)

GP.EMBED(ссылка);                   // текст. Указать ссылку на файл в памяти
GP.EMBED(ссылка, ширина);           // + ширина строкой "100px", "25%" и так далее (умолч "", т.е. авто)

// ============= КНОПКА =============
GP.BUTTON(имя, текст);                      // кнопка
GP.BUTTON(имя, текст, id);                  // + id компонента, данные с которого кнопка отправит данные по click
GP.BUTTON(имя, текст, id, цвет);            // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.BUTTON(имя, текст, id, цвет, ширина);    // + ширина строкой "100px", "25%" и так далее (умолч 90%)

GP.BUTTON_MINI(имя, текст);                     // мини кнопка
GP.BUTTON_MINI(имя, текст, id);                 // + id компонента, данные с которого кнопка отправит данные по click
GP.BUTTON_MINI(имя, текст, id, цвет);           // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.BUTTON_MINI(имя, текст, id, цвет, ширина);   // + ширина строкой "100px", "25%" и так далее (умолч 90%)

// ========= КНОПКА-ССЫЛКА =========
GP.BUTTON_LINK(ссылка, текст);                  // кнопка-ссылка для навигации по сайту
GP.BUTTON_LINK(ссылка, текст, цвет);            // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.BUTTON_LINK(ссылка, текст, цвет, ширина);    // + ширина строкой "100px", "25%" и так далее (умолч 90%)

GP.BUTTON_MINI_LINK(ссылка, текст);                 // мини кнопка-ссылка для навигации по сайту
GP.BUTTON_MINI_LINK(ссылка, текст, цвет);           // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.BUTTON_MINI_LINK(ссылка, текст, цвет, ширина);   // + ширина строкой "100px", "25%" и так далее (умолч 90%)

// ========= КНОПКА-СКАЧКА =========
GP.BUTTON_DOWNLOAD(ссылка, текст);                      // кнопка для скачивания файла
GP.BUTTON_DOWNLOAD(ссылка, текст, цвет);                // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.BUTTON_DOWNLOAD(ссылка, текст, цвет, ширина);        // + ширина строкой "100px", "25%" и так далее (умолч 90%)

GP.BUTTON_MINI_DOWNLOAD(ссылка, текст);                 // мини кнопка для скачивания файла
GP.BUTTON_MINI_DOWNLOAD(ссылка, текст, цвет);           // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.BUTTON_MINI_DOWNLOAD(ссылка, текст, цвет, ширина);   // + ширина строкой "100px", "25%" и так далее (умолч 90%)

// ============== ВВОД ==============
GP.NUMBER(имя, подсказка, число);                   // поле ввода числа, int
GP.NUMBER(имя, подсказка, число, ширина);           // + ширина строкой "100px", "25%" и так далее (умолч 90%)
GP.NUMBER(имя, подсказка, число, ширина, откл);     // + режим "только чтение" - true, по умолч false

GP.NUMBER_F(имя, подсказка, число);                         // поле ввода числа, float
GP.NUMBER_F(имя, подсказка, число, знаков);                 // + кол-во знаков после запятой (умолч. 2)
GP.NUMBER_F(имя, подсказка, число, знаков, ширина);         // + ширина строкой "100px", "25%" и так далее (умолч 90%)
GP.NUMBER_F(имя, подсказка, число, знаков, ширина, откл);   // + режим "только чтение" - true, по умолч false

GP.TEXT(имя);                                   // поле ввода текста
GP.TEXT(имя, подсказка);                        // + подсказка
GP.TEXT(имя, подсказка, текст);                 // + текст
GP.TEXT(имя, подсказка, текст, ширина);         // + ширина строкой "100px", "25%" и так далее (умолч 90%)
GP.TEXT(имя, подсказка, текст, ширина, откл);   // + режим "только чтение" - true, по умолч false

GP.PASS(имя);                               // поле ввода пароля
GP.PASS(имя, подсказка);                    // подсказка
GP.PASS(имя, подсказка, текст);             // + текст
GP.PASS(имя, подсказка, текст, ширина);     // + ширина строкой "100px", "25%" и так далее (умолч 90%)

GP.AREA(имя);                               // большое поле для ввода текста
GP.AREA(имя, высота);                       // + высота в количестве строк (умолч. 1)
GP.AREA(имя, высота, текст);                // + отображаемый текст
GP.AREA(имя, высота, текст, откл);          // + режим "только чтение" - true, по умолч false

// =========== ВЫКЛЮЧАТЕЛИ ===========
GP.CHECK(имя);                      // чекбокс, умолч. выключен
GP.CHECK(имя, состояние);           // + состояние
GP.CHECK(имя, состояние, откл);     // + режим "только чтение" - true, по умолч false

GP.SWITCH(имя);                     // выключатель, умолч. выключен
GP.SWITCH(имя, состояние);          // + состояние
GP.SWITCH(имя, состояние, откл);    // + режим "только чтение" - true, по умолч false

// ============ ДАТА-ВРЕМЯ ===========
GP.DATE(имя);                       // ввод даты
GP.DATE(имя, GPdate);               // + значение
GP.DATE(имя, GPdate, откл);         // + режим "только чтение" - true, по умолч false

GP.TIME(имя);                       // ввод времени
GP.TIME(имя, GPtime);               // + значение
GP.TIME(имя, GPtime, откл);         // + режим "только чтение" - true, по умолч false

// =============== ЦВЕТ ===============
GP.COLOR(имя);                      // выбор цвета, умолч. чёрный
GP.COLOR(имя, число);               // выбор цвета
GP.COLOR(имя, число, откл);         // + режим "только чтение" - true, по умолч false

// ============== ВЫБОР ==============
GP.SELECT(имя, список);                             // селектор (дропбокс)
GP.SELECT(имя, список, активный);                   // + текущий активный пункт (int)
GP.SELECT(имя, список, активный, нумерация);        // + текущий активный пункт (int)
GP.SELECT(имя, список, активный, нумерация, откл);  // + режим "только чтение" - true, по умолч false

// Здесь список:
// - Строка любого формата, пункты разделены запятой: "пункт1,пункт2,пункт3"
// - Массив String[], последняя строка должна быть пустой!
// - Массив char**, последняя строка должна быть пустой! (напр. char* names[] = {"p1", "p2", ""})

// ============== СЛАЙДЕР ==============
GP.SLIDER(имя);
GP.SLIDER(имя, значение);                                       // слайдер 0..100
GP.SLIDER(имя, значение, мин, макс);                            // слайдер с шагом 1
GP.SLIDER(имя, значение, мин, макс, шаг);                       // + шаг
GP.SLIDER(имя, значение, мин, макс, шаг, знаков);               // + кол-во знаков после запятой (умолч. 0)
GP.SLIDER(имя, значение, мин, макс, шаг, знаков, цвет);         // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.SLIDER(имя, значение, мин, макс, шаг, знаков, цвет, откл);   // + режим "только чтение" - true, по умолч false

// ============== СПИННЕР ==============
GP.SPINNER(имя); 
GP.SPINNER(имя, значение);                                      // спиннер, значение поддерживает float
GP.SPINNER(имя, значение, мин, макс);                           // + минимум максимум, поддерживает float
GP.SPINNER(имя, значение, мин, макс, шаг);                      // + шаг (умолч. 1), поддерживает float
GP.SPINNER(имя, значение, мин, макс, шаг, знаков);              // + кол-во знаков после запятой (умолч. 0)
GP.SPINNER(имя, значение, мин, макс, шаг, знаков, цвет);        // + цвет из списка (умолч. GP_GREEN) или цвет в формате PSTR("#rrggbb")
GP.SPINNER(имя, значение, мин, макс, шаг, знаков, цвет, ширина);// + ширина строкой "100px", "25%" и так далее (умолч 80px)

// ============== ГРАФИКИ ==============
// лёгкий статичный график без масштаба
GP.PLOT<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0, int height = 400);
GP.PLOT_DARK<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0, int height = 400);

// статичный график с масштабом и привязкой ко времени
GP.PLOT_STOCK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0, int height = 400, bool local = 0);
GP.PLOT_STOCK_DARK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0, int height = 400, bool local = 0);

// динамический график, вызывает update
GP.AJAX_PLOT(имя, к-во осей, к-во точек по Х, период update, int height = 400, bool local = 0);
GP.AJAX_PLOT_DARK(имя, к-во осей, к-во точек по Х, период update, int height = 400, bool local = 0);

// ============== КАСТОМ ==============
GP.PAGE_BEGIN();                    // начальный HTML код
GP.PAGE_BEGIN(ширина);              // + ширина колонки (int) в px (умолч. 350)

GP.BUILD_BEGIN_FILE();              // начальный HTML код, но скрипты берутся из FS памяти
GP.BUILD_BEGIN_FILE(ширина);        // + ширина колонки (int) в px (умолч. 350)

GP.PAGE_BLOCK_BEGIN()               // центральный div блок
GP.PAGE_BLOCK_END()                 // центральный div блок
GP.PAGE_END();                      // завершающий HTML код
GP.JS_TOP();                        // верхний блок JS скриптов (для кликов, слайдеров и т.д.)
GP.JS_TOP_FILE();                   // файл скриптов скачается из памяти (/gp_data/scripts.js)
GP.JS_BOTTOM();                     // нижний блок JS скриптов (для кликов, слайдеров и т.д.)

GP.SEND(код);                       // добавить свой код на страницу ("строка", F("строка"), String)
GP.SEND_P(pgm код);                 // добавить свой код на страницу (PGM_P, PROGMEM)

// ============ ЦВЕТА ===========
// цвет с постфиксом _B - яркий
GP_RED
GP_RED_B
GP_PINK
GP_PINK_B
GP_VIOL
GP_VIOL_B
GP_BLUE
GP_BLUE_B
GP_CYAN
GP_CYAN_B
GP_GREEN
GP_GREEN_B
GP_YELLOW
GP_ORANGE
GP_ORANGE_B
GP_GRAY
GP_BLACK


// ============= МАКРОСЫ =============
GP_MAKE_FORM(act, subm, args);

GP_MAKE_GRID(args);

GP_MAKE_BOX(args);
GP_MAKE_BOX(align, args);
GP_MAKE_BOX(align, width, args);

GP_MAKE_BLOCK(args);
GP_MAKE_BLOCK(width, args);

GP_MAKE_BLOCK_TAB(text, args);
GP_MAKE_BLOCK_TAB(text, width, args);
GP_MAKE_BLOCK_TAB(text, width, style, args);

GP_MAKE_BLOCK_THIN(args);
GP_MAKE_BLOCK_THIN(width, args);
GP_MAKE_BLOCK_THIN_TAB(text, args);
GP_MAKE_BLOCK_THIN_TAB(text, width, args);

GP_MAKE_JQ_UPDATE(name, args);
GP_MAKE_JQ_UPDATE(name, prd, args);

GP_MAKE_NAV_BLOCK(args);

GP_MAKE_SPOILER(txt, args);
GP_MAKE_SPOILER(txt, style, args);
Методы класса
// =============== СИСТЕМА ==============
void start();                   // запустить портал
void start("host");             // запуск с поддержкой mDNS, указать адрес (см. доку).
void start("host", port);       // запуск с поддержкой mDNS и указанием порта. Если mDNS не нужен - передай ""

void stop();                    // остановить портал// показать свою страницу
bool state();                   // проверить, запущен ли портал

void setBufferSize(int sz);     // задать размер буфера страницы, байт (умолч. 1000)

// ============ АВТОРИЗАЦИЯ ============
void enableAuth(char* login, char* pass);   // включить авторизацию по логину-паролю
void disableAuth();                         // отключить авторизацию

// ================ OTA ================
void enableOTA();                       // подключить OTA обновление по адресу /ota_update
void enableOTA(login, pass);            // + авторизация

// =============== ATTACH ==============
void attachBuild(func());               // подключить функцию-билдер страницы
void attachBuild(func(GyverPortal&));   // + передача объекта (см. Локальный портал)
void detachBuild();                     // отключить

void attach(func());                    // подключить функцию-обработчик действия
void attach(func(GyverPortal&));        // + передача объекта (см. Локальный портал)
void detach();                          // отключить

// =============== TICK ==============
bool tick();            // тикер портала. Вернёт true, если портал запущен

// ============== ACTIONS =============
// =============== FORM ===============
bool form();                    // вернёт true, если было нажатие на любой submit
bool form(имя);                 // вернёт true, если был submit с указанной формы
bool formSub(имя);              // вернёт true, если был submit с форм, url которых начинается с name
String formName();              // получить имя теукщей submit формы

// ============== REQUEST =============
bool request();         // вернёт true, если был http запрос
bool request(url);      // вернёт true, если был http запрос на указанный url

// =============== CLICK ==============
bool click();                   // вернёт true, если был клик по (кнопка, чекбокс, свитч, слайдер, селектор)
bool click(имя);                // вернёт true, если был клик по указанному элементу
String clickName();             // получить имя теукщего кликнутого компонента

bool clickDown(имя);            // вернёт true, если кнопка была нажата
bool clickUp(имя);              // вернёт true, если кнопка была отпущена
// =============== UPDATE ==============
bool update();                  // вернёт true, если было обновление
bool update(имя);               // вернёт true, если было update с указанного компонента
String updateName();            // вернёт имя обновлённого компонента

// автоматическое обновление. Отправит значение из указанной переменной
// Вернёт true в момент обновления
bool updateString(имя, String& f);
bool updateInt(имя, int f);
bool updateFloat(имя, float f, int dec = 2);
bool updateCheck(имя, bool f);
bool updateDate(имя, GPdate f);
bool updateTime(имя, GPtime f);
bool updateColor(имя, GPcolor f);
bool updateSelected(имя, int f);

bool updateLog(const String& n, GPlog& log);

// =============== ANSWER ==============
void answer(const String& s;    // отправить ответ на обновление
void answer(GPcolor col);       // ответ с цветом
void answer(GPdate date);       // ответ с датой
void answer(GPtime time);       // ответ со временем
void answer(int v);             // ответ с числом
void answer(float v, uint8_t dec);          // ответ с float и кол-вом знаков
void answer(int16_t* v, int am);            // массив int размерностью am, для графика
void answer(int16_t* v, int am, int dec);   // + делитель

// =============== UPLOAD ==============
bool upload();                  // вернёт true, если был запрос на загрузку файла
bool upload(имя);               // вернёт true, если был запрос на загрузку файла с указанной кнопки
void saveFile(File file);       // установить файл для загрузки
void saveFile(имя);             // установить файл для загрузки
bool uploadEnd();               // вернёт true, если завершена загрузка файла
String& uploadName();           // имя формы загрузки файла
String& fileName();             // имя файла при загрузке
void uploadAuto(bool mode);     // автоматическая загрузка файла по uri (по умолч. выкл, false)

// ============== DOWNLOAD ==============
bool download();                // вернёт true, если был запрос на скачивание файла
void sendFile(File file);       // отправить файл
void sendFile(имя);             // отправить файл
void downloadAuto(bool mode);   // автоматическое скачивание файла по uri (по умолч. выкл, false)

// ================ URI =================
String uri();                   // адрес текущей страницы
bool uri(uri);                  // true если uri совпадает
bool root();                    // открыта главная страница сайта /

// ========= ПОЛУЧЕНИЕ ЗНАЧЕНИЙ ==========
// ОПАСНЫЕ ФУНКЦИИ (не проверяют есть ли запрос). Конвертируют и возвращают значение
String getString(имя);          // получить String строку с компонента
int getInt(имя);                // получить число с компонента
float getFloat(имя);            // получить float с компонента
bool getCheck(имя);             // получить состояние чекбокса
GPdate getDate(имя);            // получить дату с компонента
GPtime getTime(имя);            // получить время с компонента
GPcolor getColor(имя);          // получить цвет с компонента
int getSelected(имя);           // получить номер выбранного пункта в дроплисте

// вариант без имени (нулевой аргумент), опрашивать только в условии, например if (p.click("name")) Serial.println(p.getString())
String getString();             // получить String строку с компонента
int getInt();                   // получить число с компонента
float getFloat();               // получить float с компонента
bool getCheck();                // получить состояние чекбокса
GPdate getDate();               // получить дату с компонента
GPtime getTime();               // получить время с компонента
GPcolor getColor();             // получить цвет с компонента
int getSelected();              // получить номер выбранного пункта в дроплисте

// БЕЗОПАСНЫЕ ФУНКЦИИ (проверяют запрос). Копируют данные из запроса в переменную
// вернёт true, если имя компонента есть в запросе. Можно использовать для form() и click()
bool copyStr(имя, char* t);
bool copyString(имя, String& t);
bool copyInt(имя, int& t);
bool copyFloat(имя, float& t);
bool copyCheck(имя, bool& t);
bool copyDate(имя, GPdate& t);
bool copyTime(имя, GPtime& t);
bool copyColor(имя, GPcolor& t);
bool copySelected(имя, int& t);

// для автоматического опроса click
bool clickStr(имя, char* t);
bool clickString(имя, String& t);
bool clickInt(имя, int& t);
bool clickFloat(имя, float& t);
bool clickCheck(имя, bool& t);
bool clickDate(имя, GPdate& t);
bool clickTime(имя, GPtime& t);
bool clickColor(имя, GPcolor& t);
bool clickSelected(имя, int& t);

// ============= IP REMOTE CLIENT ============
IPAddress clientIP();                               // вернёт IP адрес клиента
bool clientFromNet(IPAddress NetIP, uint8_t mask);   // вернёт true, если IP адрес клиента принадлежит указанной сети

// ================= ВЕБ-ЛОГ =================
GPlog();                    // пустой конструктор
GPlog(const char* name);    // задать имя
void start(int n = 64);     // запустить
void stop();                // остановить
bool state();               // лог запущен
void write(uint8_t n);      // записать байт
void print();               // отправить любые данные
void println();             // отправить любые данные
char* read();               // прочитать char*
void clear();               // очистить
bool available();           // есть данные для чтения

// ========== СПИСОК АВТООБНОВЛЕНИЯ ==========
list.init(количество);                  // инициализировать список, указать количество
list.clear();                           // очистить список
list.add(адрес, имя, тип);              // добавить переменную, указать имя компонента и тип
list.add(адрес, имя формы, имя, тип);   // добавить переменную, ИМЯ ФОРМЫ, указать имя компонента и тип

// типы для списка
T_CSTR      - массив char
T_STRING    - строка String
T_TIME      - время типа GPtime
T_DATE      - дата типа GPdate
T_CHECK     - boolean, для чекбокса
T_BYTE      - целое 1 байт
T_INT       - целое 4 байта
T_FLOAT     - float
T_COLOR     - целое 4 байта, для цвета

// ============ ПУБЛИЧНЫЕ ОБЪЕКТЫ ============
File file;
GPlist list;
GPlog log;
ESP8266HTTPUpdateServer/HTTPUpdateServer httpUpdater;
ESP8266WebServer/WebServer server;

// ========== ДЕФАЙНЫ НАСТРОЕК ==========
// объявлять ДО ПОДКЛЮЧЕНИЯ БИБЛИОТЕКИ GyverPortal
#define GP_NO_MDNS          // убрать поддержку mDNS из библиотеки (вход по хосту в браузере)
#define GP_NO_DNS           // убрать поддержку DNS из библиотеки (для режима работы как точка доступа)
#define GP_NO_OTA           // убрать поддержку OTA обновления прошивки
#define GP_NO_UPLOAD        // убрать поддержку загрузки файлов на сервер
#define GP_NO_DOWNLOAD      // убрать поддержку скачивания файлов с сервера
Хранение и изменение даты GPdate
// структура для хранения даты GPdate
// переменные
uint16_t year;
uint8_t month, day;

// инициализация
GPdate();
GPdate(int year, int month, int day);   // из трёх чисел
GPdate(String str);                     // из строки вида yyyy-mm-dd

// методы
void set(int nyear, int nmonth, int nday);     // установить
String encode();            // преобразовать в строку вида yyyy-mm-dd
void decode(String str);    // обновить из строки вида yyyy-mm-dd
Хранение и изменение времени GPtime
// структура для хранения даты GPtime
// переменные
uint8_t hour, minute, second;

// инициализация
GPtime();
GPtime(int hour, int minute, int second);   // из трёх чисел
GPtime(String str);                         // из строки вида hh:mm:ss

// методы
void set(int nhour, int nminute, int nsecond = 0);  // установить
String encode();            // преобразовать в строку вида hh:mm:ss
void decode(String str);    // обновить из строки вида hh:mm:ss
Хранение и изменение времени GPunix
// получить unix время для графика
uint32_t GPunix(год, месяц, день, час, минута, секунда);
uint32_t GPunix(год, месяц, день, час, минута, секунда, gmt);
uint32_t GPunix(GPdate d, GPtime t);
uint32_t GPunix(GPdate d, GPtime t, int8_t gmt);
// gmt - часовой пояс, по умолч. 0 (пример: Москва gmt = 3)
// месяц и день начинаются с 1, не с 0!
Хранение и изменение цвета GPcolor
// см. пример gpcolor_demo

// структура для хранения цвета GPcolor
// переменные
uint8_t r, g, b;

// инициализация
GPcolor();
GPcolor(uint32_t color);
GPcolor(byte r, byte g, byte b);
GPcolor(String s)           // из строки вида #RRGGBB

// методы
String encode();            // преобразовать в строку вида #RRGGBB
void decode(String str);    // обновить из строки вида #RRGGBB
void setRGB(r, g, b);       // установить цвет побайтно
void setHEX(uint32_t col);  // установить 24 бит цвет
uint32_t getHEX();          // получить 24 бит цвет

// к структуре можно присвоить uint32_t число
Утилиты
// получить номер, под которым name входит в list вида "val1,val2,val3"
int GPinList(const String& s, const String& list);

// получить строку, которая входит в список list "val1,val2,val3" под номером idx
String GPlistIdx(int idx, const String& li);

// получить тип файла (вида image/png) по его пути uri
String GPfileType(const String& uri);

// добавить новое значение в массив с перемоткой (для графиков)
GPaddInt(int16_t val, int16_t* arr, uint8_t am);        // новое значение, массив, размер массива
GPaddUnix(uint32_t val, uint32_t* arr, uint8_t am);     // новое значение, массив, размер массива
GPaddUnixS(int16_t val, uint32_t* arr, uint8_t am);     // добавить секунды, массив, размер массива

PGM_P GPgetAlign(GPalign a);    // получить align для flex

Таблица поддержки режимов работы компонентами

Компонент/Вызов form() click() update()
TITLE
LABEL
LABEL_BLOCK
BUTTON
BUTTON_MINI
NUMBER
TEXT
PASS
AREA
CHECK
SWITCH
DATE
TIME
COLOR
SLIDER
SPINNER
SELECT
LED_RED
LED_GREEN
LED

0. Как это работает

0.1 Немного теории

Библиотека позволяет управлять электронным устройством из браузера. Между esp и браузером устанавливаются отношения клиент-сервер. Браузер (клиент) может:

  • Запросить с esp страницу, которую нужно отобразить
  • Запросить с esp конкретные данные, которые нужно обновить на странице (текст, состояние чекбокса)
  • Отправить на esp данные со страницы (факт нажатия кнопки, текст)

Esp (сервер) может только отвечать на запросы от браузера, самостоятельно ничего отправить на страницу он не может. Но в библиотеке есть инструменты, позволяющие наладить такое взаимодействие с элементами интерфейса.

0.2 Основные механизмы

Взаимодействие с библиотекой сводится к двум основным моментам:

  • У нас есть функция, которая вызывается библиотекой, когда браузер запрашивает отображение страницы. В этой функции мы собираем страницу из готовых блоков, затем библиотека её отправляет
  • У нас есть функция, которая вызывается библиотекой, когда приходит действие с браузера. В этой функции мы получаем со страницы данные, сигналы, а также отправляем ответы на запросы

На данный момент в библиотеке есть три типа действий с браузера:

  • Форма с кнопкой: при нажатии на кнопку типа submit страница перезагружается, а в программу приходят данные со всех компонентов, входящих в форму (текст в поле ввода, положения слайдеров и чекбоксов, и так далее). Удобно для однократного ввода данных, настройки подключения и тому подобное.
  • Клик/изменение: при клике на почти любой компонент интерфейса или при изменении его состояния или значения можно получить его актуальное значение без перезагрузки страницы. Удобно для управления и настройки (галочки, кнопки, слайдеры, выбор цвета).
  • Обновление значений и состояний компонентов на странице в реальном времени без перезагрузки страницы. Удобно для индикации работы и получения текущих численных и текстовых значений из программы, вывод графиков в реальном времени, состояний чекбоксов и лампочек.

1. Начало работы

Библиотека может работать как в локальной сети (esp подключается к роутеру), так и в режиме точки доступа (смартфон подключается к esp). После организации связи нужно вызвать portal.start() для запуска сервера.

1.1 Подключение к роутеру

  WiFi.mode(WIFI_STA);
  WiFi.begin("login", "pass");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(WiFi.localIP());
  // ...
  portal.start();   // запускаем портал

Для подключения к порталу нужно зайти в браузере на IP адрес платы, который выдал ей роутер. В примере выше этот адрес выводится в монитор порта.

1.2 Обнаружение mDNS, Bonjour

В версии 2.0 библиотеки появилась встроенная возможность запуска функции обнаружения: при запуске портала передаём адрес, по которому хотим заходить свою плату в браузере или обнаруживать в локальной сети. Например portal.start("my_esp") без пробелов. Что делать дальше:

  • Windows
    • Установить утилитку Bonjour от Apple, ссылка на официальный сайт
    • Для открытия портала вместо IP адреса использовать имя.local, из примера выше - http://my_esp.local/
    • Круто же!
  • Android
    • Скачать из Play Market приложение Service Browser
    • Открыть пункт _http._tcp. (World Wide Web HTTP)
    • Увидеть там IP адрес платы напротив указанного имени хоста
    • Удобно!

1.3 Создание точки

В этом режиме портал нужно запускать start() после установки режима работы WIFI_AP: библиотека определяет текущий режим работы и запускает DNS сервер.

  WiFi.mode(WIFI_AP);
  WiFi.softAP("My Portal");
  // ...
  portal.start();   // запускаем портал

На стандартных настройках IP адрес для подключения в этом режиме будет 192.168.4.1

1.4 Защита паролем

С версии 2.1 появилась защита веб-интерфейса паролем: нужно авторизироваться для отображения страницы. Для подключения авторизации достаточно вызвать функцию и указать логин-пароль

portal.enableAuth("login", "password");

Также авторизацию можно отключить:

portal.disableAuth();

1.5 Тикер

В GyverPortal используется стандартная библиотека ESP8266WebServer, поэтому для обеспечения работы сервера нужно вызывать portal.tick() в цикле программы. Возвращает true, если сервер запущен в данный момент.

1.6 Опрос действий

Взаимодействие между платой и браузером происходит по следующему сценарию: на esp запщуен сервер, браузер является клиентом. При открытии портала в браузере esp получает запрос и отправляет страницу. При действиях пользователя (нажал на кнопку) или скриптов со стороны браузера (запрос данных) esp получает запрос на действие. В библиотеке начиная с версии 2.0 это реализовано так:

  • Создаём свою функцию вида void f() или void f(GyverPortal &p) (в этот вариант будет автоматически передан ваш экземпляр GyverPortal)
  • Подключаем эту функцию в библиотеку: portal.attach(f)

Теперь указанная функция будет автоматически вызываться при действии в браузере, их всего три:

  • Отправка формы
  • Клик по элементу
  • Запрос на обновление

1.7 Создаём GyverPortal глобально

В этом сценарии объект создан глобально и может опрашиваться в любом месте программы

GyverPortal portal;

void build() {
  // конструктор страницы
}

void action() {
  // опрос действий
  // переменная portal в области видимости
}

void setup() {
  // подключаемся к сети
  portal.attachBuild(build);
  portal.attach(action);
  portal.start();
}

void loop() {
  portal.tick();
}

1.8 Создаём GyverPortal локально

В этом сценарии объект создан локально, например в цикле. Функция-обработчик действий должна содержать переданный по ссылке объект портала, чтобы работать вне области определения!

void build() {
  // конструктор страницы
}

void action(GyverPortal &p) {
  // опрос действий
  // здесь переменная p является ссылкой на portal (ниже)
  // можно обращаться к ней точно так же, if (p.click())
}

void f() {
  GyverPortal portal;
  portal.attachBuild(build);
  portal.attach(action);
  portal.start();

  while (portal.tick());
}

В таком сценарии для выхода из цикла можно вызвать portal.stop() по таймауту или кнопке с браузера.

2. Конструктор страниц

Конструктор реализован следующим образом: есть функция, которая сама будет вызываться, когда нужно отправить в браузер страницу. В этой функции мы добавляем компоненты на страницу, обращаясь к GP с указанием элемента.

2.1. Создаём конструктор

Создаём функцию вида: void f(). Далее в ней:

  1. Запускаем конструктор: BUILD_BEGIN(). Здесь добавляется начальный HTML код
  2. (Опционально) применяем тему: GP.THEME(тема) (GP_LIGHT/GP_DARK)
  3. Строим страницу, добавляя компоненты, например GP.BUTTON(...)
  4. Завершаем работу конструктора: BUILD_END(). Здесь добавляется завершающий HTML код

Общий вид функции конструктора:

void build() {
  BUILD_BEGIN();
  GP.THEME(GP_LIGHT);
  // собираем страницу
  // ...
  BUILD_END();
}

Примечание: внутри функции конструктора не рекомендуется создавать динамические данные (String, вызовы new и malloc). Это приведёт к фрагментации памяти на время работы функции конструктора, в сильно загруженной программе из за этого может закончиться оперативная память, если страница будет большая!

2.2 Подключаем функцию конструктора

Передаём в библиотеку нашу функцию-конструктор страницы:

portal.attachBuild(build);

Библиотека сама будет вызывать её, когда потребуется отобразить страницу. Функций-конструкторов (а следовательно и страниц) может быть несколько и их можно переключать.

Также сборку страницы можно производить по "условиям", добавляя и убирая компоненты согласно сценарию работы (см. пример menuTabs)

3. Использование форм

3.1 Собираем страницу с формами

Основная суть использования форм:

  • Форма имеет своё уникальное имя, должно начинаться с /
  • Внутри формы может быть сколько угодно элементов, но только одна кнопка типа SUBMIT
  • При нажатии на SUBMIT esp получает имя формы и данные из всех элементов внутри этой формы
  • При нажатии на SUBMIT страница перезагружается, поэтому значения компонентов страницы нужно хранить в переменных и передавать при следующей сборке страницы

Пример с двумя формами, первая может передать текст из окна ввода, вторая - только факт нажатия кнопки:

форма_1
    ввод текста
    кнопка submit
форма_1

форма_2
    кнопка submit
форма_2

В конструкторе GyverPortal это будет выглядеть так:

void build() {
  BUILD_BEGIN();               // запустить конструктор
  GP.THEME(GP_LIGHT);          // применить тему

  GP.FORM_BEGIN("/login");     // начать форму, передать имя
  GP.TEXT("txt", "Login", ""); // ввод текста, подсказка Login, текста нет
  GP.BREAK();                  // перенос строки
  GP.SUBMIT("Submit");         // кнопка Submit
  GP.FORM_END();               // завершить форму

  GP.FORM_BEGIN("/exit");      // начать форму, передать имя
  GP.SUBMIT("Exit");           // кнопка Exit
  GP.FORM_END();               // завершить форму

  BUILD_END();                 // завершить построение
}

Результат работы конструктора:
demo
Все инструменты конструктора описаны в документации выше.

3.2 Опрос действий

  • При нажатии любой кнопки типа SUBMIT в браузере функция form() вернёт true
  • Функция должна опрашиваться внутри подключенной в attach() функции
  • Для поиска формы, с которой пришёл сигнал, используем form(имя) - вернёт true, если имя совпало
    • Лучше обернуть поиск в if (form()), чтобы не тратить процессорное время на сравнение строк
void action() {
  if (portal.form()) {
    Serial.print("Submit form: ");
    if (portal.form("/login")) Serial.println("Login");
    if (portal.form("/exit")) Serial.println("Exit");
  }
}

3.4 Парсинг данных

В библиотеке реализованы готовые инструменты для полученя данных из компонентов формы (см. документацию выше). Например выведем в порт содержимое поля ввода текста:

if (portal.form("/login")) Serial.println(portal.getString("txt"));
// где "txt" - имя компонента

Важно! Получать данные с компонента формы можно только внутри условия (здесь if (portal.form("/name"))), так как esp не хранит в себе код страницы, она получает конкретные данные только при действии пользователя!

4. Использование кликов

4.1 Отличие от форм

В библиотеке реализован механизм, позволяющий обрабатывать действия на странице без её перезагрузки (как при использовании форм):

  • Форма позволяет по нажатию одной кнопки получить значения с нескольких компонентов. Страница перезагрузится.
  • Клик позволяет получить текущее (изменённое) значение только с кликнутого компонента. Страница не перезагрузится.

4.2 Опрос действий

  • При клике по некоторым компонентам или изменении их значения (см. таблицу в документации) функция click() вернёт true
  • Функция должна опрашиваться внутри подключенной в attach() функции
  • Для поиска компонента, с которого пришёл сигнал, используем click(имя) - вернёт true, если имя совпало
    • Лучше обернуть поиск в if (click()), чтобы не тратить процессорное время на сравнение строк
void action() {
  if (portal.click("mybutton")) Serial.println("Click!");
}

4.4 Парсинг данных

Парсинг данных от кликов можно производить при помощи тех же функций, что и для форм.

Важно! Получать данные с компонента формы можно только внутри условия (здесь if (portal.click("/name"))), так как esp не хранит в себе код страницы, она получает конкретные данные только при действии пользователя!

4.5 Подключение кнопки на другой компонент

Кнопку (BUTTON, BUTTON_MINI) можно "подключить" к другому компоненту: при клике по кнопке будет вызван сигнал click с именем кнопки и данными с указанного компонента. Для подключения нужно указать имя компонента третьим аргументом при добавлении кнопки:

GP.BUTTON(имя кнопки, текст кнопки, имя компонента);
GP.BUTTON_MINI(имя кнопки, текст кнопки, имя компонента);

Пример, клик по кнопке отправляет текст из поля txt:

GP.TEXT("txt", "");
GP.BUTTON_MINI("btn", "Send", "txt");

5. Использование обновлений

В библиотеке реализован механизм скриптовых запросов со страницы по таймеру. Это позволяет обновлять значения некоторых компонентов и надписей (см. таблицу в документации) без обновления страницы в браузере.

5.1 Подключение обновлений

Для включения режима обновлений нужно добавить в начало страницы блок AJAX_UPDATE:

void build() {
  BUILD_BEGIN();
  GP.AJAX_UPDATE("name1,name2,name3");
  // ...
  GP.LABEL("NAN", "val");  // будем обновлять текст
  BUILD_END();
}
  • Функция AJAX_UPDATE принимает список имён компонентов, разделённых запятой.
  • ПРОБЕЛ ПОСЛЕ ЗАПЯТОЙ НЕ СТАВИМ.
  • Также можно указать период запросов на обновления в миллисекундах GP.AJAX_UPDATE("name1,name2", 5000);, по умолчанию - 1000 (1 секунда).
  • Не все компоненты поддерживают режим обновлений (см. таблицу в документации).

5.2 Опрос обновлений

  • При наступлении обновления функция update() вернёт true
  • Функция должна опрашиваться внутри подключенной в attach() функции
  • Для поиска компонента, с которого пришёл сигнал, используем update(имя) - вернёт true, если имя совпало
    • Лучше обернуть поиск в if (update()), чтобы не тратить процессорное время на сравнение строк
  • Нужно ответить на запрос обновления при помощи функции answer(). В неё передаётся актуальное значение для компонента
  • Если не ответить на обновление - библиотека ответит пустым ответом, чтобы страница не зависла
void action() {
  if (portal.update("val")) portal.answer(random(1000));
}

6. Автоматическое обновление переменных

[См. примеры demoSubmitAuto и demoClickAuto] Вместо ручного парсинга можно указать библиотеке переменные, которые будут автоматически получать новые значения с указанных компонентов страницы. Это работает как для форм, так и для кликов.

  • Инициализируем список, вызвав .list.init(количество), передаём размер списка в количестве переменных.
  • Добавляем переменную по её адресу:
    • .list.add(&переменная, имя, тип) - с указанием имени компонента и его типа
    • .list.add(&переменная, форма, имя, тип) - с указанием имени формы, имени компонента и типа

Указанные переменные обновят свои значения при действии с формы с указанным именем или при клике. Если имя формы не указано - компонент будет парситься при действии с любой формы. Для работы с кликами не нужно указывать имя формы.

6.1 Поддержка фичи компонентами, связь с типами

Тип данных Тип/Компонент TEXT/NUMBER PASS CHECK SWITCH DATE TIME SLIDER COLOR SELECT AREA
char[] T_CSTR
String T_STRING
GPtime T_TIME
GPdate T_DATE
bool T_CHECK
byte, char T_BYTE
int, long T_INT
float T_FLOAT
uint32_t T_COLOR

7. Графики

7.1 Общие особенности

Совместимость

Графики AJAX_PLOT и PLOT_STOCK несовместимы в одном интерфейсе!

Вывод дробных данных

У всех трёх типов графиков есть аргумент dec, по умолчанию равен 0. Это делитель, на который (если отличен от 0) будут делиться значения точек графика и переводиться в float. Таким образом можно отображать данные с плавающей точкой и не хранить в памяти лишние 2 байта. Получили температуру 22.5 градусов, умножаем на 10 и сохраняем в массив. Вызываем график с dec, равным 10.

Несколько осей

Все графики поддерживают вывод по нескольким осям (общая ось X).

Подписи

Подписи храним в массиве char, например так:

const char *names[] = {"kek", "puk",};

Обновление статических графиков

Статические графики отображают данные при перезагрузке страницы. Таким образом в конструктор должен быть передан массив с актуальными значениями.
В библиотеке реализованы функции для удобного добавления нового значения к массиву (с автоматической "перемоткой"):

GPaddInt(int16_t val, int16_t* arr, uint8_t am);        // новое значение, массив, размер массива
GPaddUnix(uint32_t val, uint32_t* arr, uint8_t am);     // новое значение, массив, размер массива
GPaddUnixS(int16_t val, uint32_t* arr, uint8_t am);     // добавить секунды, массив, размер массива

Например, есть массив int arr[2][20] - хранит 20 значений для двух осей графика. Можно обновлять его и хранить в EEPROM, обеспечивая бесперерывную работу. Для добавления нового значения делаем по своему таймеру:

GPaddInt(новое, arr[0], 20);
GPaddInt(новое, arr[1], 20);

В конструктор передаём как

GP.PLOT<2, 20>("table", names, arr);

Обновление динамических графиков

Динамический график вызывает update, отвечаем ему новыми значениями и он строит график в реальном времени. Для передачи значений по нескольким осям используем answer(массив, размер) или answer(массив, размер, dec), где dec имеет смысл делителя (см. выше).

7.2 График PLOT

Лёгкий статический график без масштаба
[См. пример staticPlot]
demo

GP.PLOT<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0)
GP.PLOT_DARK<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0)

7.3 График PLOT_STOCK

Статический график с масштабом и привязкой ко времени
[См. пример stockPlot]
demo

GP.PLOT_STOCK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0)
GP.PLOT_STOCK_DARK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0)

Данный график требует для отображения массив даты и времени типа uint32_t, содержащий время в формате unix.

7.4 График AJAX_PLOT

Динамический график, вызывает update по своему имени, требует ответа
[См. пример ajaxPlot]
demo

GP.AJAX_PLOT(имя, к-во осей, к-во точек по Х, период update);
GP.AJAX_PLOT_DARK(имя, к-во осей, к-во точек по Х, период update);

8. Лог

В библиотеке реализована возможность делать print() в специальное окно лога на странице:

  • Окно лога можно создать только одно
  • Обновление происходит автоматически, раз в секунду
  • Страница не обновляется
  • Можно отправлять любые данные, как Serial

8.1 Подключение окна лога

Добавляем GP.AREA_LOG(к-во строк) в нужное место страницы

8.2 Запуск лога

Вызываем log.start(размер буфера). Размер буфера по умолчанию 64 символа

  • Примечание: это размер буфера на стороне библиотеки, то есть ограничение на количество символов на одну отправку на страницу (раз в секунду). У страницы браузера свой буфер для отображения текста!

8.3 Вывод данных

Просто вызываем log.print() или log.println() как у обычного Serial. См. пример demoLog.

9. OTA Обновление

Добавлено в версии 2.1. Представляет собой страницу, на которой можно выбрать бинарник и обновить прошивку и SPIFFS. Включается функцией

  • enableOTA() - страница обновления без пароля
  • enableOTA("login", "pass") - страница обновления требует авторизации с указанным логином-паролем

Страница обновления доступна по адресу x.x.x.x/ota_update. Для обновления нужен .bin файл прошивки, его можно получить нажав Скетч/Экспорт бинарного файла. Файл появится в папке со скетчем.

Для обновления данных в SPIFFS памяти понадобится плагин для Arduino IDE, который сгенерирует бинарный файл:

При нажатии кнопки Data Upload (и отключенной плате) снизу в логе находим путь к папке билда. Например:

  • C:\Users\Alex\AppData\Local\Temp\arduino_build_232786

Оттуда и берём bin файл с датой.

10. Многостраничность

Логика создания многостраничного интерфейса следующая:

  • Внутри вашей функции-конструктора функция portal.uri() возвращает адрес открытой в браузере "страницы" (часть после IP адреса)
  • Страницы создаются виртуально по условиям: при совпадении адреса добавляем в страницу нужные компоненты
  • Навигация может осуществляться как из адресной строки в браузере, так и при помощи кнопок-ссылок
  • При создании страницы, на которой присутствует форма form, виртуальный адрес этой страницы должен совпадать с именем формы, чтобы после отправки формы браузер получил ту же самую страницу!

Пример, интерфейс с тремя виртуальными страницами:

  • / - главная страница с кнопками-ссылками на остальные страницы
  • /save - здесь форма form с текстовым полем и кнопкой сохранения, а также кнопкой-ссылкой "назад"
  • /status - здесь мигает "светодиод" при помощи инструмента update, а также кнопка-ссылка "назад"
void build() {
  BUILD_BEGIN();
  GP.THEME(GP_DARK);

  // страница с формой
  if (portal.uri() == "/save") {
    GP.FORM_BEGIN("/save");
    GP.TEXT("txt", "", ""); GP.BREAK();
    GP.SUBMIT("Save");
    GP.FORM_END();
    GP.BUTTON_LINK("/", "Back");

    // страница с лампочкой
  } else if (portal.uri() == "/status") {
    GP.AJAX_UPDATE("led");
    GP.LABEL("LED: ");
    GP.LED_RED("led", 0);   GP.BREAK();
    GP.BUTTON_LINK("/", "Back");

    // главная страница "/"
  } else {
    GP.BUTTON_LINK("/save", "Save page");      GP.BREAK();
    GP.BUTTON_LINK("/status", "Status page");
  }

  BUILD_END();
}

void action() {
  if (portal.form("/save")) Serial.println(portal.getString("txt"));
  static bool led;
  if (portal.update("led")) portal.answer(led = !led);
}

11. Свои компоненты, API

11.1 Кастомный конструктор

Конструктор GyverPortal ничем не ограничивает построение страницы: достаточно прибавить к строке любой HTML код между запуском GP_BUILD() и завершением конструктора GP_SHOW(). Страница собирается во внутреннюю строку _GP, но доступна она по указателю:

void build() {
  GP_BUILD();
  // собираем страницу
  *_GP += F("some HTML code");
  GP_SHOW();
}

Для справки:
Стандартный BUILD_BEGIN() внутри состоит из:

  GP.PAGE_BEGIN();
  GP.AJAX_CLICK();
  GP.PAGE_BLOCK_BEGIN();

Стандартный BUILD_END() внутри состоит из:

 GP.PAGE_BLOCK_END();
 GP.PAGE_END();

11.2 Свой код

Достаточно прибавить любой HTML код к строке, например:

*_GP += F("<input type=\"email\" class=\"myClass\">");

Можно обернуть в F macro, чтобы не занимать оперативку.

11.3 API

Для обеспечения работоспособности механизмов библиотеки в кастомных компонентах нужно соблюдать следующие моменты:

  • Если нужна поддержка кликов - добавить в страницу GP.AJAX_CLICK()
  • У компонентов формы должен быть указан атрибут name для передачи данных через submit.
  • У кликабельных компонентов должен быть указан атрибут onclick с параметром-функцией: onclick="GP_click(this)". Библиотека сама перехватит вызов и направит в click().
  • У компонентов, с которых нужен сигнал click() по изменению данных, должен быть указан атрибут onchange с параметром-функцией: onchange="GP_click(this)". Библиотека сама перехватит вызов и направит в click().
  • У компонентов, для которых нужны обновления update(), должен быть указан атрибут id. Его значение также нужно передать в GP.AJAX_UPDATE().
  • Если нужен клик, который передаёт данные с другого компонента, указываем атрибут с функцией onclick="GP_clickid(btn,tar)", где btn - имя (для библиотеки) кликающего компонента, а tar - атрибут id целевого компонента, с которого нужно передать данные.
  • Для ручной передачи в библиотеку сигнала о клике нужно отправить http POST запрос вида GP_click?имя=значение
  • Для ручной передачи в библиотеку сигнала об обновлении нужно отправить http GET запрос вида GP_update?id_компонента=

Версии

  • v1.0

  • v1.1 - улучшил графики и стили

  • v1.2

    • Блок NUMBER теперь тип number
    • Добавил большое текстовое поле AREA
    • Добавил GPunix
    • Улучшил парсинг
    • Добавил BUTTON_MINI
    • Кнопки могут передавать данные с других компонентов (кроме AREA и чекбоксов)
    • Добавил PLOT_STOCK - статический график с масштабом
    • Добавил AJAX_PLOT_DARK
    • Изменён синтаксис у старых графиков
    • Фичи GPaddUnix и GPaddInt для графиков
    • Убрал default тему
    • Подкрутил стили
    • Добавил окно лога AREA_LOG и функцию лога в целом
  • v1.3 - переделал GPunix, мелкие фиксы, для списков можно использовать PSTR

  • v1.4 - мелкие фиксы, клик по COLOR теперь отправляет цвет

  • v1.5 - добавил блок "слайдер+подпись"

  • v1.5.1 - мелкий фикс копирования строк

  • v1.5.2 - добавлен meta charset="utf-8", английский README (спасибо VerZsuT)

  • v1.6 - добавлены инструменты для работы c цветом. Добавил answer() для даты, времени и цвета

  • v1.7 - поддержка ESP32

  • v2.0: Большое обновление! Логика работы чуть изменена, обнови свои скетчи!

    • Много оптимизации/облегчения/ускорения
    • Полная поддержка ESP32
    • Переделана логика опроса действий (более правильно и оптимально + работает на ESP32) с сохранением легаси
    • Убран DateTimeP (не используется в библиотеке) и вынес отдельно в библиотеку DatePack
    • Переделан и облегчен модуль лога (log)
    • Добавлен MDNS, чтобы не искать IP платы в мониторе порта (см. доку)
    • Автоопределение режима работы WiFi. Переделан start() с сохранением легаси (см. доку)
    • Упрощён билдер, строку создавать и передавать не нужно (см. доку)
    • Объект билдера теперь называется GP (вместо add) с сохранением легаси
    • Пофикшены варнинги
    • Добавлены удобства для работы с цветом GPcolor, датой GPdate и временем GPtime
    • Удалены старые функции преобразования цвета и даты-времени (см. доку)
    • Портал теперь возвращает цвет в формате GPcolor, автообновление переменных тоже работает с GPcolor
    • Все примеры протестированы на esp8266 и esp32
  • v2.1

    • Вернул функции root() и uri() для удобства создания многостраничности
    • Добавлен пример организации многостраничности
    • Добавлена кнопка-ссылка BUTTON_LINK
    • Добавлена авторизация по логину-паролю (см. доку)
    • Добавлено OTA обновление прошивки из браузера, в т.ч. с паролем (см. доку)
  • v3.0: Очень много всего нового, всё не смог перечислить =)

    • Огромное спасибо DenysChuhlib и DAK85 за идеи и наработки!
    • Добавлен "объектный" режим работы, в котором компоненты удобнее конфигурируются, автоматически получают новые значения и код программы становится сильно компактнее
    • Полностью переписан механизм конструктора, сборка занимает во много раз меньше памяти в SRAM за счёт отправки страницы частями
    • Переделан механизм добавления кастомного кода на страницу
    • Аргументы конструктора теперь принимают const String& - можно передавать строки, const строки, F macro строки
    • Переделаны строковые утилиты
    • Полностью переделан слайдер
    • Убран вариант слайдера с текстом и компонент LABEL_MINI
    • Добавлена возможность задания ширины некоторым компонентам
    • У некоторых компонентов появилась опция "только чтение"
    • Редизайн светодиодов LED GREEN/RED, добавлен LED (красно-зелёный)
    • Добавлен компонент BOX_BEGIN/BOX_END, позволяющий удобно собирать компоненты в группы с нужным размером и выравниванием
    • Добавлен блок LABEL_BLOCK для выделения текста
    • Внутренний AJAX_CLICKS заменён на JS_TOP
    • Переделан основной контейнер страницы для удобства кастомизации под любую ширину интерфейса
    • Добавлен элемент навигации по динамическим вкладкам NAV_TABS (+ NAV_BLOCK_BEGIN и NAV_BLOCK_END)
    • Добавлен элемент навигации с кнопками-ссылками NAV_TABS_LINKS
    • Добавлена поддержка FontAwesome иконок для кнопок и панели навигации https://fontawesome.com/v4/icons/
    • Пофикшена бага при использовании старого сценария опроса действий
    • AJAX_UPDATE переименован в UPDATE с сохранением легаси
    • Добавлен блок FILE_UPLOAD для загрузки файлов на сервер
    • Добавлен удобный механизм скачивания файлов из SPIFFS памяти с поддержкой 33 типов файлов
    • Добавлены блоки для вывода изображений, видео и текстовых файлов из SPIFFS
    • Примеры переименованы и сгруппированы по смыслу, добавлены новые примеры
    • Добавлен механизм request
    • Подключаемым функциям добавлены варианты с адресом на GyverPortal
    • Добавлены более удобные варианты компонента SELECT и способы его опроса (getSelectedIdx)
    • Механизм update теперь работает с SELECT блоками
    • Добавлен шаблон для удобного создания кастомных блоков
    • Исправлена работа кликов и обновлений на подстраницах
    • Добавлена мини кнопка-ссылка + кнопки для скачивания файлов
    • Добавлен оффлайн-режим для графиков (не нужно подключение к Интернет)
    • Добавлен блок для добавления стилей из spiffs
    • SLIDER теперь умеет работать с float, добавлен NUMBER_F для float
    • Добавлен элемент SPINNER
    • AREA теперь отсылает сигнал click
    • Добавлены макросы для удобной сборки блоков
    • И прочее прочее

Баги и обратная связь

При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru Библиотека открыта для доработки и ваших Pull Request'ов!

При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:

  • Версия библиотеки
  • Какой используется МК
  • Версия SDK (для ESP)
  • Версия Arduino IDE
  • Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
  • Какой код загружался, какая работа от него ожидалась и как он работает в реальности
  • В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код

About

Простой конструктор веб интерфейса для esp8266 и ESP32

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 65.6%
  • C 24.4%
  • CSS 8.8%
  • JavaScript 1.2%