Баги
На ESP32 некорреткно работает режим обновлений, дублирует элементы. А также клики по элементам приходят асинхронно с обновлением страницы (как в примере menuTabs). Пока не уверен, что получится починить.
Простой конструктор веб интерфейса для esp8266 и ESP32
- Простой конструктор - делаем страницы без знаний HTML и CSS
- Библиотека является обёрткой для стандартной ESP8266WebServer
- Позволяет быстро создать вебморду для управления и настройки своего девайса
- Компактный читаемый код в "скетче", никаких внешних обработчиков и лямбда-функций
- Конструктор использует стандартные HTML формы, CSS и javascript
- Элементы конструктора хранятся во Flash памяти
- Никаких глобальных буферов, всё генерируется на лету
- Приятный дизайн из коробки + тёмная тема
- Адаптировано под мобильные устройства и ПК
- Встроенные инструменты для удобного парсинга значений с формы
- Возможность настроить автоматическое обновление значений переменных по действию со страницы
- Встроенные жабаскрипты для AJAX, работа без обновления всей страницы:
- Клики по компонентам, изменение их значений
- Обновление компонентов по таймеру
- График в реальном времени
- Текстовое окно отладки (отправляем из программы)
- Компоненты конструктора:
- Заголовок
- Подпись
- Разделитель
- Перенос строки
- Блок для объединения компонентов
- Веб-форма (блок)
- Кнопка submit (для форм)
- Поле ввода текста
- Многострочное поле ввода текста
- Поле ввода пароля
- Галочка (чекбокс)
- Выключатель
- Слайдер
- Слайдер с подписью
- Выбор времени
- Выбор даты
- Селектор (дропбокс)
- Кнопка
- Мини кнопка
- "Светодиод" индикатор
- Окно лога для отладки (веб Serial порт)
- Несколько типов графиков (требуется интернет)
Библиотека позволяет генерировать динамический веб интерфейс для управления своим электронным устройством из браузера (нужно зайти на IP адрес платы в строке адреса). Три основных способа взаимодействия с интерфейсом и сценарии использования:
- Форма и кнопка отправить: при нажатии на кнопку submit страница перезагружается, а в программу приходят данные со всех компонентов, входящих в форму (текст в поле ввода, положения слайдеров и чекбоксов, и так далее). Удобно для однократного ввода данных (настройки подключения и тому подобное).
- Клик по компоненту: при клике на почти любой компонент интерфейса (при изменении его состояния или значения) можно получить его актуальное значение без перезагрузки страницы. Удобно для управления и настройки (галочки, кнопки, слайдеры).
- Обновление значений и состояний компонентов в реальном времени без перезагрузки страницы. Удобно для индикации работы и получения текущих численных и текстовых значений из программы, вывод графиков в реальном времени.
esp8266, esp32
- Библиотеку можно найти по названию 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 библиотеку… и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек здесь
GyverPortal portal;
Функции конструктора
// создание страницы
BUILD_BEGIN(строка); // начать построение. Добавляет начальный HTML код
BUILD_END(); // завершить построение. Добавляет завершающий HTML код и открывает страницу
// темы
add.THEME(тема); // установить тему
GP_LIGHT - светлая тема
GP_DARK - тёмная тема
// создание формы
add.FORM_BEGIN(имя); // начать форму с именем (имя)
add.FORM_END(); // завершить форму
// пустая форма с одной кнопкой
add.FORM_SUBMIT(имя, текст); // форма с именем (имя) кнопкой (текст)
// компоненты формы
add.BUTTON(имя, текст); // кнопка
add.BUTTON(имя, текст, id); // кнопка (id компонента, данные с которого кнопка отправит данные по click)
add.BUTTON_MINI(имя, текст); // мини кнопка
add.BUTTON_MINI(имя, текст, id); // мини кнопка (id компонента, данные с которого кнопка отправит данные по click)
add.NUMBER(имя, подсказка, число); // поле ввода текста, число
add.TEXT(имя, подсказка, текст); // поле ввода текста
add.PASS(имя, подсказка, текст); // поле ввода пароля
add.CHECK(имя); // чекбокс, умолч. выключен
add.CHECK(имя, состояние); // чекбокс
add.SWITCH(имя); // выключатель, умолч. выключен
add.SWITCH(имя, состояние); // выключатель
add.DATE(имя); // ввод даты, умолч. пустой
add.DATE(имя, GPdate); // ввод даты
add.TIME(имя); // ввод времени, умолч. пустой
add.TIME(имя, GPtime); // ввод времени
add.SELECT(имя, список); // селектор (дропбокс), элементы списка разделены запятой. Список может быть PSTR
add.SELECT(имя, список, активный); // + текущий активный пункт
add.SLIDER(имя, число, мин, макс); // слайдер
add.SLIDER(имя, число, мин, макс, шаг); // слайдер
add.SLIDER(имя, подпись, число, мин, макс); // слайдер с подписью
add.SLIDER(имя, подпись, число, мин, макс, шаг); // слайдер с подписью
add.COLOR(имя); // выбор цвета, умолч. чёрный
add.COLOR(имя, число); // выбор цвета
add.SUBMIT(текст); // кнопка отправки формы
add.LED_RED(имя); // красный светодиод-индикатор
add.LED_GREEN(имя); // зелёный светодиод-индикатор
// прочее для оформления
add.TITLE(текст); // заголовок
add.TITLE(текст, имя); // + имя компонента (для update())
add.LABEL(текст); // подпись (для кнопок, полей, чекбоксов итд)
add.LABEL(текст, имя); // + имя компонента (для update())
add.LABEL(число); // подпись, число
add.LABEL(число, имя); // + имя компонента (для update())
add.AREA(имя, к-во строк, текст); // большое поле ввода текста
add.AREA(имя, к-во строк); // большое поле ввода текста
add.BREAK(); // перенести строку
add.HR(); // горизонтальный разделитель
add.BLOCK_BEGIN(); // начать отрисовку блока
add.BLOCK_END(); // завершить отрисовку блока
// прочее
add.AJAX_UPDATE(список, период); // передать список обновления компонентов
// для списка можно использовать макрос PSTR
// большое поле для ввода текста
add.AREA(имя, к-во строк, текст);
add.AREA(имя, к-во строк);
add.AREA(имя);
// графики
// лёгкий статичный график без масштаба
add.PLOT<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0)
add.PLOT_DARK<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0)
// статический график с масштабом и привязкой ко времени
add.PLOT_STOCK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0)
add.PLOT_STOCK_DARK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0)
// динамический график, вызывает update
add.AJAX_PLOT(имя, к-во осей, к-во точек по Х, период update);
add.AJAX_PLOT_DARK(имя, к-во осей, к-во точек по Х, период update);
// создание кастомной страницы
GP_BUILD(строка); // запустить конструктор (можно вызывать где угодно)
GP_SHOW(); // отобразить страницу (вызывать только внутри функции конструктора!)
add.PAGE_BEGIN(); // начальный HTML код
add.PAGE_BLOCK_BEGIN() // центральный div блок
add.PAGE_BLOCK_END() // центральный div блок
add.PAGE_END(); // завершающий HTML код
add.AJAX_CLICK(); // обработчик кликов
Методы класса
// система
void start(); // запустить портал
void start(WIFI_AP); // запустить портал с DNS сервером (для AP)
void stop(); // остановить портал// показать свою страницу
void showPage(String&); // показать свою страницу
void show(); // вызвать конструктор и показать страницу
// подключение
void attachBuild(void*); // подключить функцию-билдер страницы
void attachForm(void*); // подключить функцию, которая вызывается при нажатии submit
void attachClick(void*); // подключить функцию, которая вызывается при клике (кнопка, чекбокс, свитч, слайдер, селектор)
void attachUpdate(void*); // подключить функцию, которая вызывается при AJAX обновлении со страницы
// опрос
bool tick(); // тикер портала. Вернёт true, если портал запущен
bool form(); // вернёт true, если было нажатие на любой submit
bool form(char* name); // вернёт true, если был submit с указанной формы
String& formName(); // получить имя теукщей submit формы
bool click(); // вернёт true, если был клик по (кнопка, чекбокс, свитч, слайдер, селектор)
bool click(char* name); // вернёт true, если был клик по указанному элементу
String& clickName(); // получить имя теукщего кликнутого компонента
const String& clickText(); // получить имя текст кликнутого компонента
bool update(); // вернёт true, если было обновление
bool update(char* name); // вернёт true, если было update с указанного компонента
String& updateName(); // вернёт имя обновлённого компонента
void answer(String& s); // отправить ответ на обновление
void answer(int s);
void answer(char* s);
void answer(int16_t* v, int am); // массив int размерностью am, для графика
void answer(int16_t* v, int am, int dec);// + делитель
void answer(GPcolor col); // ответ с цветом
void answer(GPdate date); // ответ с датой
void answer(GPtime time); // ответ со временем
bool root(); // вернёт true, если открыта главная страница (/)
String& uri(); // адрес текущего запроса
// список автообновления
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 байта, для цвета
// парсеры
String getString(char* name); // получить String строку с компонента name
char* getChars(char* name); //получить char* строку с компонента name
void copyStr(char* name, char* dest); // переписать char строку с компонента name к себе в dest
long getInt(char* name); // получить целое число с компонента name
float getFloat(char* name); // получить float число с компонента name
bool getCheck(char* name); // получить состояние чекбокса с компонента name
GPdate getDate(char* name); // получить дату с компонента name в формате GPdate
GPtime getTime(char* name); // получить время с компонента name в формате GPtime
uint32_t getColor(char* name); // получить цвет с компонента name
uint8_t getSelected(char* name, char* list); // получить номер выбранного пункта в дроплисте list (может быть PSTR)
Хранение и изменение времени
// структура для хранения даты
struct GPdate {
int16_t year;
uint8_t month, day;
};
// структура для хранения времени
struct GPtime {
uint8_t hour, minute, second;
};
// получить unix время для графика
uint32_t GPunix(год, месяц, день, час, минута, секунда);
uint32_t GPunix(год, месяц, день, час, минута, секунда, gmt);
// gmt - часовой пояс, по умолч. 0 (пример: Москва gmt = 3)
// месяц и день начинаются с 1, не с 0!
Хранение и изменение цвета
// см. пример gpcolor_demo
// структура для хранения цвета
struct GPcolor {
uint8_t r, g, b;
};
// инициализация
GPcolor color;
GPcolor color(uint32_t color);
GPcolor color(byte r, byte g, byte b);
// к структуре могут быть применены методы
void setRGB(r, g, b); // установить цвет побайтно
setHEX(uint32_t col); // установить 24 бит цвет
uint32_t getHEX(); // получить 24 бит цвет
// к структуре можно присвоить uint32_t число
Утилиты
char* splitList(char* str); // разделить строку на подстроки. Цыганские фокусы
int8_t inList(char* name, char* list); // получить номер, под которым name входит в list (вида "val1,val2,val3")
int8_t inList(String& name, char* list);
String encodeDate(GPdate& d); // склеить дату в строку String
void encodeDate(char* str, GPdate& d); // склеить дату в строку str[11]
String encodeDate(year, month, day); // склеить дату в строку String
GPdate decodeDate(char* str); // разобрать строковую дату[11] в структуру
String encodeTime(GPtime& t); // склеить время в строку String
void encodeTime(char* str, GPtime& t); // склеить время в строку str[9]
String encodeTime(hour, minute, second);// склеить время в строку String
GPtime decodeTime(char* str); // разобрать строковое время[9] в структуру
String encodeColor(GPcolor color); // собрать GPcolor цвет в String #rrggbb
String encodeColor(uint32_t color); // собрать цвет в String #rrggbb
uint32_t decodeColor(char* hex); // разобрать цвет #rrggbb в число
// добавить новое значение в массив с перемоткой (для графиков)
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); // добавить секунды, массив, размер массива
Компонент/Вызов | form() | click() | update() |
---|---|---|---|
TITLE | ✔ | ||
LABEL | ✔ | ||
BUTTON | ✔ | ||
BUTTON_MINI | ✔ | ||
NUMBER | ✔ | ✔ | ✔ |
TEXT | ✔ | ✔ | ✔ |
PASS | ✔ | ✔ | ✔ |
AREA | ✔ | ✔ | |
CHECK | ✔ | ✔ | ✔ |
SWITCH | ✔ | ✔ | ✔ |
DATE | ✔ | ✔ | ✔ |
TIME | ✔ | ✔ | ✔ |
SLIDER | ✔ | ✔ | ✔ |
COLOR | ✔ | ✔ | ✔ |
SELECT | ✔ | ✔ | |
LED_RED | ✔ | ✔ | |
LED_GREEN | ✔ | ✔ |
Библиотека может работать как в локальной сети (esp подключается к роутеру), так и в режиме точки доступа (смартфон подключается к esp).
WiFi.mode(WIFI_STA);
WiFi.begin("login", "pass");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(WiFi.localIP());
// ...
portal.start(); // запускаем портал
Для подключения к порталу нужно зайти в браузере на IP адрес платы, который выдал ей роутер. В примере выше этот адрес выводится в монитор порта.
В этом режиме при запуске портала нужно передать WIFI_AP для запуска DNS сервера
WiFi.mode(WIFI_AP);
WiFi.softAP("My Portal");
portal.start(WIFI_AP); // запускаем портал с настройкой на режим AP
На стандартных настройках IP адрес для подключения в этом режиме будет 192.168.4.1
В GyverPortal используется стандартная библиотека ESP8266WebServer, поэтому
для обеспечения работы сервера нужно вызывать portal.tick()
в цикле программы. Возвращает true
, если сервер запущен в данный момент.
GyverPortal portal;
void build() {}
void setup() {
// подключаемся к сети
portal.attachBuild(build);
portal.start();
}
void loop() {
portal.tick();
// опрос действий
}
void build() {}
void f() {
GyverPortal portal;
portal.attachBuild(build);
portal.start();
while (portal.tick()) {
// опрос действий
}
}
Для выхода из цикла можно вызвать portal.stop()
по таймауту или сигналу с браузера.
- Создаём функцию вида:
void f()
. Далее в ней: - Создём пустую строку:
String s;
. - Запускаем конструктор:
BUILD_BEGIN(s)
. Передаём созданную строку. Здесь добавляется начальный HTML код. - (Опционально) применяем тему:
add.THEME(тема)
, GP_LIGHT/GP_DARK - Строим страницу, используя конструктор или прибавляя свои данные к строке.
- Завершаем работу конструктора:
BUILD_END()
. Здесь добавляется завершающий HTML код и страница отправляется на сервер.
Шаблон функции конструктора:
void build() {
String s;
BUILD_BEGIN(s);
add.THEME(GP_LIGHT);
// собираем страницу
// ...
BUILD_END();
}
Передаём в библиотеку нашу функцию-конструктор страницы:
portal.attachBuild(build);
Библиотека сама будет вызывать её, когда потребуется отобразить страницу. Функций-конструкторов (а следовательно и страниц) может быть несколько и их можно переключать.
Основная суть использования форм:
- Форма имеет своё уникальное имя, должно начинаться с /
- Внутри формы может быть сколько угодно элементов, но только одна кнопка типа SUBMIT
- При нажатии на SUBMIT esp получает имя формы и данные из всех элементов внутри этой формы
- При нажатии на SUBMIT страница перезагружается, поэтому значения компонентов страницы нужно хранить в переменных и передавать при следующей сборке страницы
Пример с двумя формами, первая может передать текст из окна ввода, вторая - только факт нажатия кнопки:
форма_1
ввод текста
кнопка submit
форма_1
форма_2
кнопка submit
форма_2
В конструкторе GyverPortal это будет выглядеть так:
void build() {
String s; // создать строку
BUILD_BEGIN(s); // запустить конструктор
add.THEME(GP_LIGHT); // применить тему
add.FORM_BEGIN("/login"); // начать форму, передать имя
add.TEXT("txt", "Login", ""); // ввод текста, подсказка Login, текста нет
add.BREAK(); // перенос строки
add.SUBMIT("Submit"); // кнопка Submit
add.FORM_END(); // завершить форму
add.FORM_BEGIN("/exit"); // начать форму, передать имя
add.SUBMIT("Exit"); // кнопка Exit
add.FORM_END(); // завершить форму
BUILD_END(); // завершить построение
}
Результат работы конструктора:
Все инструменты конструктора описаны в документации выше.
- При нажатии любой кнопки типа SUBMIT в браузере функция
form()
вернётtrue
- Функция должна опрашиваться после
tick()
- Для поиска формы, с которой пришёл сигнал, используем
form(имя)
- вернётtrue
, если имя совпало- Лучше обернуть поиск в
if (form())
, чтобы не тратить процессорное время на сравнение строк
- Лучше обернуть поиск в
portal.tick();
if (portal.form()) {
Serial.print("Submit form: ");
if (portal.form("/login")) Serial.println("Login");
if (portal.form("/exit")) Serial.println("Exit");
}
Вместо ручного опроса form()
можно подключить свою функцию вида void f(GyverPortal*)
, она будет вызвана при нажатии на любой SUBMIT.
Эту функцию нужно будет передать в attachForm()
.
void myAction(GyverPortal* p) {
// имеем доступ к объекту портала, который отправил вызов
if (p -> form("/exit")) Serial.println("exit");
}
void setup() {
portal.attachForm(myAction);
}
В библиотеке реализованы готовые инструменты для полученя данных из компонентов формы (см. документацию выше). Например выведем в порт содержимое поля ввода текста:
portal.tick();
if (portal.form("/login")) Serial.println(portal.getString("txt"));
// где "txt" - имя компонента
В библиотеке реализован механизм, позволяющий обрабатывать действия на странице без её перезагрузки (как при использовании форм):
- Форма позволяет по нажатию одной кнопки получить значения с нескольких компонентов. Страница перезагрузится.
- Клик позволяет получить текущее (изменённое) значение только с кликнутого компонента. Страница не перезагрузится.
- При клике по некоторым компонентам или изменении их значения (см. таблицу в документации) функция
click()
вернётtrue
- Функция должна опрашиваться после
tick()
- Для поиска компонента, с которого пришёл сигнал, используем
click(имя)
- вернётtrue
, если имя совпало- Лучше обернуть поиск в
if (click())
, чтобы не тратить процессорное время на сравнение строк
- Лучше обернуть поиск в
portal.tick();
if (portal.click()) {
if (portal.click("mybutton")) Serial.println("Click!");
}
Вместо ручного опроса click()
можно подключить свою функцию вида void f(GyverPortal*)
, она будет вызвана при нажатиях на компоненты.
Эту функцию нужно будет передать в attachClick()
.
void myClick(GyverPortal* p) {
// имеем доступ к объекту портала, который отправил вызов
if (p -> click("mybutton")) Serial.println("Click!");
}
void setup() {
portal.attachClick(myClick);
}
Парсинг данных от кликов можно производить при помощи тех же функций, что и для форм.
Кнопку (BUTTON, BUTTON_MINI) можно "подключить" к другому компоненту: при клике по кнопке будет вызван сигнал click
с именем кнопки и данными с указанного компонента. Для подключения нужно указать имя компонента третьим аргументом при добавлении кнопки:
add.BUTTON(имя кнопки, текст кнопки, имя компонента);
add.BUTTON_MINI(имя кнопки, текст кнопки, имя компонента);
Пример, клик по кнопке отправляет текст из поля txt:
add.TEXT("txt", "");
add.BUTTON_MINI("btn", "Send", "txt");
В библиотеке реализован механизм скриптовых запросов со страницы по таймеру. Это позволяет обновлять значения некоторых компонентов и надписей (см. таблицу в документации) без обновления страницы в браузере.
Для включения режима обновлений нужно добавить в начало страницы блок AJAX_UPDATE:
void build() {
String s;
BUILD_BEGIN(s);
add.AJAX_UPDATE("name1,name2,name3");
// ...
add.LABEL("NAN", "val"); // будем обновлять текст
BUILD_END();
}
- Функция AJAX_UPDATE принимает список имён компонентов, разделённых запятой.
- ПРОБЕЛ ПОСЛЕ ЗАПЯТОЙ НЕ СТАВИМ.
- Также можно указать период запросов на обновления в миллисекундах
add.AJAX_UPDATE("name1,name2", 5000);
, по умолчанию - 1000 (1 секунда). - Не все компоненты поддерживают режим обновлений (см. таблицу в документации).
- При наступлении обновления функция
update()
вернётtrue
- Функция должна опрашиваться после
tick()
- Для поиска компонента, с которого пришёл сигнал, используем
update(имя)
- вернётtrue
, если имя совпало- Лучше обернуть поиск в
if (update())
, чтобы не тратить процессорное время на сравнение строк
- Лучше обернуть поиск в
- Нужно ответить на запрос обновления при помощи функции
answer()
. В неё передаётся актуальное значение для компонента - Если не ответить на обновление до следующего вызова
tick()
- библиотека ответит пустым ответом, чтобы страница не зависла
portal.tick();
if (portal.update()) {
if (portal.update("val")) portal.answer(random(1000));
}
Вместо ручного опроса update()
можно подключить свою функцию вида void f(GyverPortal*)
, она будет вызвана при обновлении любого компонента.
Эту функцию нужно будет передать в attachUpdate()
.
void myUpdate(GyverPortal* p) {
if (p -> update("val")) p -> answer(random(1000));
}
void setup() {
portal.attachUpdate(myUpdate);
}
[См. примеры demoSubmitAuto и demoClickAuto] Вместо ручного парсинга можно указать библиотеке переменные, которые будут автоматически получать новые значения с указанных компонентов страницы. Это работает как для форм, так и для кликов.
- Инициализируем список, вызвав
.list.init(количество)
, передаём размер списка в количестве переменных. - Добавляем переменную по её адресу:
.list.add(&переменная, имя, тип)
- с указанием имени компонента и его типа.list.add(&переменная, форма, имя, тип)
- с указанием имени формы, имени компонента и типа
Указанные переменные обновят свои значения при действии с формы с указанным именем или при клике. Если имя формы не указано - компонент будет парситься при действии с любой формы. Для работы с кликами не нужно указывать имя формы.
Тип данных | Тип/Компонент | 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 | ✔ |
Графики 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);
В конструктор передаём как
add.PLOT<2, 20>("table", names, arr);
Динамический график вызывает update
, отвечаем ему новыми значениями и он строит график в реальном времени. Для передачи значений по нескольким осям используем
answer(массив, размер)
или answer(массив, размер, dec)
, где dec имеет смысл делителя (см. выше).
Лёгкий статический график без масштаба
[См. пример staticPlot]
add.PLOT<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0)
add.PLOT_DARK<к-во осей, к-во данных>(имя, подписи, данные int16_t, int dec = 0)
Статический график с масштабом и привязкой ко времени
[См. пример stockPlot]
add.PLOT_STOCK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0)
add.PLOT_STOCK_DARK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0)
Данный график требует для отображения массив даты и времени типа uint32_t
, содержащий время в формате unix.
Динамический график, вызывает update по своему имени, требует ответа
[См. пример ajaxPlot]
add.AJAX_PLOT(имя, к-во осей, к-во точек по Х, период update);
add.AJAX_PLOT_DARK(имя, к-во осей, к-во точек по Х, период update);
В библиотеке реализована возможность делать print()
в специальное окно лога на странице:
- Окно лога можно создать только одно
- Обновление происходит автоматически, раз в секунду
- Страница не обновляется
- Можно отправлять любые данные, как Serial
Добавляем add.AREA_LOG(к-во строк)
в нужное место страницы
Вызываем log.start(размер буфера)
. Размер буфера по умолчанию 64 символа
- Примечание: это размер буфера на стороне библиотеки, то есть ограничение на количество символов на одну отправку на страницу (раз в секунду). У страницы браузера свой буфер для отображения текста!
Просто вызываем log.print()
или log.println()
как у обычного Serial. См. пример demoLog.
Конструктор GyverPortal ничем не ограничивает построение страницы: достаточно прибавить к строке любой HTML код между запуском GP_BUILD(s)
и завершением конструктора GP_SHOW()
:
void build() {
String s;
GP_BUILD(s);
// собираем страницу
// ...
GP_SHOW();
}
Для справки:
Стандартный BUILD_BEGIN(String)
внутри состоит из:
GP_BUILD(s);
add.PAGE_BEGIN();
add.AJAX_CLICK();
add.PAGE_BLOCK_BEGIN();
Стандартный BUILD_END()
внутри состоит из:
add.PAGE_BLOCK_END();
add.PAGE_END();
GP_SHOW();
Достаточно прибавить любой HTML код к строке, например:
s += F("<input type=\"email\" class=\"myClass\">");
Можно обернуть в F macro, чтобы не занимать оперативку.
Для обеспечения работоспособности механизмов библиотеки в кастомных компонентах нужно соблюдать следующие моменты:
- Если нужна поддержка кликов - добавить в страницу
add.AJAX_CLICK()
- У компонентов формы должен быть указан атрибут name для передачи данных через submit.
- У кликабельных компонентов должен быть указан атрибут onclick с параметром-функцией:
onclick="GP_click(this)"
. Библиотека сама перехватит вызов и направит вclick()
. - У компонентов, с которых нужен сигнал
click()
по изменению данных, должен быть указан атрибут onchange с параметром-функцией:onchange="GP_click(this)"
. Библиотека сама перехватит вызов и направит вclick()
. - У компонентов, для которых нужны обновления
update()
, должен быть указан атрибутid
. Его значение также нужно передать вadd.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
При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru Библиотека открыта для доработки и ваших Pull Request'ов!