-
Notifications
You must be signed in to change notification settings - Fork 42
/
Interface.txt
172 lines (127 loc) · 11.2 KB
/
Interface.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
Формирование пакета
Браузер (далее клиент) формирует интерфейс, на основании 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::PIN_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.
Который так же симулирует вызов обработчика параметров. Таким образом мы избегаем множественного дублирования обработчиков в разных реализациях.