Формирование пакета
Браузер (далее клиент) формирует интерфейс, на основании JSON пакетов, формируемых лампой (далее сервер).
Для начала формирования интерфейса, нужно создать экземпляр класса Interface, передав в него указатель на транспорт и
память выделяемую на формирование фрейма.
Транспортом могут быть:
AsyncWebSocket - отправка всем подключенным клиентам
AsyncWebSocketClient - отправка конкретному клиенту
AsyncWebServerRequest - http. не рекомендуется, так как будет передан набор фреймов а не валидный JSON
Если при формировании пакета, память выделенная на фрейм будет исчерпана, произойдет сериализация текущего фрейма и его отправка.
После чего, фрейм будет очищен и формирование пакета продолжится. Чем меньше памяти выделяется на фрейм, тем за большее количество фреймов
будет отправлен пакет. Это влияет на скорость загрузки. Таким образом мы балансируем между потребляемой памятью и скоростью загрузки.
Пакеты могут быть двух видов:
json_frame_interface - формирование интерфейса
json_frame_value - формирование данных
Пример.
extern EmbUI embui;
AsyncWebSocket ws("/ws");
Interface *interf = new Interface(&embui, client);
interf->json_frame_interface("Огненная лампа");
...
interf->json_frame_flush();
delete interf;
Формирование интерфейса
Интерфейс состоит из секций. Корневая секция создается json_frame_interface, а закрывается и отправляется json_frame_flush.
Так же существуют вспомогательные секции. Создаются json_section_begin, закрывается json_section_end.
Виды вспомогательных секций:
json_section_menu - боковое меню
json_section_content - содержимое не встраивается в страницу, а замещает элементы с существующими ID
json_section_main - страница (приводит к замене текущей страницы)
json_section_hidden - встроенный скрытый блок
json_section_line - горизонтальная ориентация элементов
Все секции (кроме json_section_menu, json_section_content) являются хелперами к json_section_begin и могут быть
заменены ее вызовом с нужной комбинацией параметров.
Параметры json_section_begin:
name - id секции, используется для отправки данных.
label - если указан бедет выведен заголовок
main - секция является страницей
hidden - секция является скрываемым блоком
line - горизонтальная ориентация элементов
Мы можем создать скрытую секцию, аналог <form action="/mysect">:
interf->json_frame_interface("Огненная лампа");
interf->json_section_begin("mysect");
interf->button_submit("mysect", "Отправить");
interf->json_section_end();
interf->json_frame_flush();
ВНИМАНИЕ! Так же секцию создает контрол select. Он должен обязательно заканчиваться json_section_end.
interf->select(F("evList"), String(event.event), F("Тип события"), false);
interf->option(String(EVENT_TYPE::ON), F("Включить лампу"));
...
interf->option(String(EVENT_TYPE:
IN_STATE), F("Состояние пина"));
interf->json_section_end();
В секции помещаются контролы, все контролы могут брать данные как из глобального конфига, так и получать их на прямую.
void hidden(const String &id);
void hidden(const String &id, const String &value);
Контролы могут отправлять данные при изменении, или при сабмите секции. За это отвечает флаг directly.
void checkbox(const String &id, const String &label, bool directly = false);
void checkbox(const String &id, const String &value, const String &label, bool directly = false);
Описание некоторых специфических контролов.
hidden - скрытый контрол, не отображаемый в интерфейсе. Нужен для проброса состояния в некоторых сложных формах.
Пример использования - управление конфигурациями лампы.
constant - тоже, что и hidden но выводиться лейбл, как не активный но видимый пользователю элемент.
button - отправка параметра - значения кнопки. используется для обработки действия без доп. параметров (например загрузка страницы)
button_submit - отправка данных из секции.
button_submit_value - тоже, но дополнительно передается значение. если действий несколько (удалить, загрузить)
spacer - разделитель, может быть как линией, так и заголовком.
Формирование данных.
Сейчас данные клиентам приходят в двух случаях:
1. при приеме пакета post от одного клиента, данные рассылаются остальным клиентам.
2. периодически вызывается ф-я send_pub, которая транслирует клиентам параметры
interf->json_frame_value();
interf->value(F("pTime"), myLamp.timeProcessor.getFormattedShortTime(), true);
interf->value(F("pMem"), String(ESP.getFreeHeap()), true);
interf->json_frame_flush();
Обработка данных от клиента.
Клиент отправляет данные в случаях:
1. Нажатие на раздел меню
2. Нажатие на кнопку
3. Сабмит секции
4. Изменение параметра с флагом directly
Никакие данные автоматически не сохраняются!!!
ф-я post сканирует данные и определяет вероятный обработчик. После чего передает в нее JSON объект со всеми данными.
Регистрация обработчика параметра:
Обработчик представляет из себя ф-ю, которая принимает JSON объект с данными, обрабатывает их и формирует результат в виде
изменения интерфейса. Обработчик можно повесить на имя любого параметра. Но при обработке секции, разумнее повесить его на имя секции.
Так же обработчик можно установить на группу параметров "paramname*"
Обработка directly параметров одной ф-ей
embui.section_handle_add(F("bright"), set_effects_param);
embui.section_handle_add(F("speed"), set_effects_param);
embui.section_handle_add(F("scale"), set_effects_param);
Обрабока отправки данных из секции
embui.section_handle_add(F("set_wifi"), set_settings_wifi);
Обработка кнопки - загрузка секции
embui.section_handle_add(F("show_wifi"), show_settings_wifi);
Обработка флага переключателя
embui.section_handle_add(F("Mic"), set_micflag);
Обработка группы
embui.section_handle_add(F("evconf*"), show_event_conf);
Внимание!!! обработчик может быть вызван без передачи указателя на интерфейс или данные.
Обрабатываем данные, сохраняем в конфиг и НЕ формирует интерфейс
void set_eventflag(Interface *interf, JsonObject *data){
if (!data) return;
SETPARAM(F("Events"), myLamp.setIsEventsHandled((*data)[F("Events")] == F("true")));
}
Формируем страницу интерфейса и НЕ обрабатываем данные
void show_settings_event(Interface *interf, JsonObject *data){
if (!interf) return;
interf->json_frame_interface();
block_settings_event(interf, data);
interf->json_frame_flush();
}
И обрабатываем данные и формируем интерфейс
void set_effects_config_list(Interface *interf, JsonObject *data){
if (!interf || !data) return;
EFF_ENUM num = (EFF_ENUM)(*data)[F("effListConf")];
confEff = myLamp.effects.getEffect(num);
show_effects_config_param(interf, data);
}
Вызов обработчиков, как реакцию на внешние изменения. Нажатие кнопки, события, http, итп.
Так как существует множество мест, где состояние лампы меняется, я принял решение объединить обработчики.
Реализация в remote_action. Для каждого действия производиться симуляция передачи параметра в обработчик.
Обмен данными с глобальным конфигом.
Раньше ВСЕ параметры обязательно отображались в глобальный конфиг. Сейчас это не так.
В глобальный конфиг имеет смысл транслировать только те состояния, которые должны обязательно сохраниться после
перезагрузки лампы.
Для предотвращения случайной записи в конфиг, те параметры что небыли зарегистрированы в create_parameters будут проигнорированы.
Для синхронизации состояния лампы с параметрами после перезагрузки, используется sync_parameters.
Который так же симулирует вызов обработчика параметров. Таким образом мы избегаем множественного дублирования обработчиков в разных реализациях.