GyverPortal

J777

✩✩✩✩✩✩✩
30 Сен 2024
2
0
Респект автору за портал!
Подскажите, как программно обновить страницу (из моего кода)?
 

ASM

★★★★★✩✩
26 Окт 2018
1,797
371
@J777,
C++:
GP.RELOAD(имя);                     // скрытый блок перезагрузки страницы. Добавь его "имя" в UPDATE и ответь 1 на update, чтобы обновить страницу
GP.RELOAD_CLICK(список);            // клик по указанным в списке компонентам перезагружает страницу [пример: "id1,id2,id5"]
 
  • Лойс +1
Реакции: J777 и SoftFelix

J777

✩✩✩✩✩✩✩
30 Сен 2024
2
0
Подскажите, как увеличить ширину поля ввода GP.TEXT?
Можно ли программно перейти на другую страницу?
 

nostradamus

✩✩✩✩✩✩✩
11 Окт 2024
1
0
Подскажите пожалуйста, столкнулся с двумя проблемами сегодня:
1. Плата esp32 cam, после прошивки примера из библиотеки actionClick плата шлет в монитор порта ошибку
E (242) quad_psram: PSRAM ID read error: 0xffffffff, PSRAM chip not found or not supported.
плата не поддерживается библиотекой?
2.Плата esp32 wroom, после прошивки этого же примера в нее, wifi в режиме AP,
все работает, кроме последней кнопки в примере, выдает сообщение "offline"+ плашка сбоку что я оффлайн. При этом в консоль ничего не шлет при нажатии на кнопку.

версия библиотеки 3.6.6
 

MaxWhite

✩✩✩✩✩✩✩
12 Мар 2020
30
5
Помогите, пожалуйста, объединить ГП и его же форму ввода логина и пароли от вифи! всю голову сломал, десятки раз загружал разный код в вемос, все равно не пойму - можно ли сделать 2 функции build и build2 и в одной форма ввода WIFI будет (как в оригинале), в другой - мой код, вроде делал, но все равно после отключения портала ничего по нужному ип адресу. Не понимаю с ui.start, ui.attachBuild и прочими, доки не оч информативны... В идеале по нажатой кнопке и включении устройства - портал, если wifi данные сохранены, то сразу подключаться и запускать мой код.
 

Pussymonsta

✩✩✩✩✩✩✩
29 Апр 2022
2
0
Всех приветствую. Есть необходимость поднять на esp32 точку доступа и при подключении по логину/паролю получать веб морду, отрисованную при помощи GyverPortal. Подружить скетч для создания точки доступа и пример из портала не вышло. Есть ли у кого наработки по подобному направлению или кто то может подсказать, как по определенному IP в созданной точке доступа разместить страницу, отриcованную в портале?
 

kmarakov

✩✩✩✩✩✩✩
29 Июн 2024
3
0
Здравствуйте. Плата Weemos Mini на esp8266. Делаю форму сохранения настроек SSID и пароля в гайвер портале. В ходе разбирательств выяснил, что просто не могу получить текст из формы, ui.getString возвращает пустую строку, и все.

В примере все работает, а в моей программе нет. Прикладываю рабочий текст, и мой. Никак понять не могу, что не так.
Мой код, не работает, выводит пустую строку в консоль:
#define AP_SSID "???"
#define AP_PASS "???"

#include <GyverPortal.h>
GyverPortal ui;

void build() {
  GP.BUILD_BEGIN(600);
  GP.THEME(GP_LIGHT);
  GP.GRID_RESPONSIVE(700);

  M_GRID(
    M_BLOCK_TAB(
      "WiFi Setup",

      M_BOX(GP.LABEL("SSID:     "); GP.TEXT("ssid", "SSID", ""););
      M_BOX(GP.LABEL("Password: "); GP.TEXT("password", "Password", ""););
      M_BOX(GP.BUTTON_MINI("btn", "Submit"));
    );
  );

  GP.FORM_END();
  GP.BUILD_END();
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PASS);

  ui.attachBuild(build);
  ui.attach(action);
  ui.start();
}

void action() {
  if (ui.click("btn")) {
    Serial.println(ui.getString("ssid"));
  }
}

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

Работающий пример с GitHub:
#define AP_SSID ""
#define AP_PASS ""

#include <GyverPortal.h>
GyverPortal ui;

// конструктор страницы
void build() {
  GP.BUILD_BEGIN();
  GP.THEME(GP_DARK);

  // ui.uri() возвращает текущий адрес страницы
  // используем его в конструкции с if для постройки страницы
  // переход на страницы может осуществляться в адресной строке браузера
  // или по кнопке-ссылке BUTTON_LINK

  // страница с формой
  // ВАЖНО: url страницы должен совпадать с именем формы!
  // чтобы форма отображалась в браузере после Submit
  if (ui.uri("/save")) {
    GP.FORM_BEGIN("/save");
    GP.TEXT("txt", "text", ""); GP.BREAK();
    GP.SUBMIT("Submit");
    GP.FORM_END();
    GP.BUTTON_LINK("/", "Back");

    // страница с кнопкой, на которую можно кликнуть
  } else if (ui.uri("/clicks")) {
    GP.BUTTON("btn", "Button");
    GP.BUTTON_LINK("/", "Back");

    // страница с лампочкой, которая сама переключается
  } else if (ui.uri("/updates")) {
    GP.UPDATE("led");
    GP.LABEL("LED: ");
    GP.LED_RED("led", 0);   GP.BREAK();
    GP.BUTTON_LINK("/", "Back");

    // главная страница, корень, "/"
  } else {
    GP.BUTTON_LINK("/save", "Form");
    GP.BUTTON_LINK("/clicks", "Clicks");
    GP.BUTTON_LINK("/updates", "Updates");
  }

  GP.BUILD_END();
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(WiFi.localIP());

  // подключаем конструктор и запускаем
  ui.attachBuild(build);
  ui.attach(action);
  ui.start();
}

bool led;
void action() {
  // имитация активности
  if (ui.form("/save")) Serial.println(ui.getString("txt"));
  if (ui.click("btn")) Serial.println("Button");
  if (ui.update("led")) ui.answer(led = !led);
}

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

kmarakov

✩✩✩✩✩✩✩
29 Июн 2024
3
0
Я тут переспал со своей проблемой, и вроде решил. Как я понимаю, последнее время многим нужно было примерно тоже самое. Текст прилагаю, проверьте, пожалуйста. Может позже более подробно комментарии расставлю.
По работе: в течении 10 секунд пытается подключиться к последней записанной точке доступа. Если не может, запускает свою. Подключаетесь к ней, там форма ввода SSID и пароля. Сохраняет в EEPROM, автоматически перезагружает. Потом снова пытается подключиться к уже новой точке доступа, которую вы ввели. Если не получится в течении 10 секунд, снова запустит портал настройки.

C++:
#include <LittleFS.h>
#include <GyverPortal.h>
#include <EEPROM.h>
GyverPortal ui(&LittleFS);

struct settings {
  char ssid[30];
  char password[30];
} user_wifi = {};

void build() {
  GP.BUILD_BEGIN(600);
  GP.THEME(GP_LIGHT);
  GP.GRID_RESPONSIVE(700);

  GP.FORM_BEGIN("/main");
  M_GRID(
    M_BLOCK_TAB(
      "Статус",

      M_BOX(GP.LABEL("WiFi connected"););
      M_BOX(GP.LABEL("SSID: "); GP.LABEL(user_wifi.ssid););
    );
  );

  GP.FORM_END();
  GP.BUILD_END();
}

void action() {
  if (ui.form("/setup")) {
    eepromput();
  }
}

void eepromput(){
  ui.copyStr("ssid", user_wifi.ssid);
  ui.copyStr("password", user_wifi.password);

  EEPROM.begin(sizeof(struct settings));
  EEPROM.put(0, user_wifi);
  EEPROM.commit();
  Serial.println("Reset...");
  delay(1000);
  WiFi.softAPdisconnect();
  ESP.restart();
}

void setup() {
  Serial.begin(115200);

  EEPROM.begin(sizeof(struct settings));
  EEPROM.get(0, user_wifi);

  WiFi.mode(WIFI_STA);
  WiFi.begin(user_wifi.ssid, user_wifi.password);

  byte tries = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
    if (tries++ > 10) {
      Serial.println("");
      Serial.println("Can't connect.");
      setupPortal();
      break;
    }
  }

  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  ui.attachBuild(build);
  ui.attach(action);
  ui.start();
  ui.enableOTA("admin", "pass");

  if (!LittleFS.begin()) Serial.println("FS Error");
  ui.downloadAuto(true);
}

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

void setupPortal() {
  Serial.println("Portal start");

  // запускаем точку доступа
  WiFi.mode(WIFI_AP);
  WiFi.softAP("WiFi Setup");

  ui.attachBuild(buildSetup);
  ui.attach(action);
  ui.start();

  // работа портала
  while (ui.tick());
}

void buildSetup() {
  GP.BUILD_BEGIN(600);
  GP.THEME(GP_LIGHT);
  GP.GRID_RESPONSIVE(700);

  GP.FORM_BEGIN("/setup");
  M_GRID(
    M_BLOCK_TAB(
      "WiFi Setup",

      M_BOX(GP.LABEL("SSID:     "); GP.TEXT("ssid", "SSID", ""););
      M_BOX(GP.LABEL("Password: "); GP.TEXT("password", "Password", ""););
      M_BOX(GP.SUBMIT("Submit"));
    );
  );

  GP.FORM_END();
  GP.BUILD_END();
}
 

Xao_Asakura

✩✩✩✩✩✩✩
27 Окт 2024
1
0
Здраствуйте, сижу над проектом солнечного трекера, столкнулся с проблемой не реагируют кнопки нажатия "◄", "►", решил поискать в документации вдруг в новой версии кнопки создаються по новым ключевым словами, но ничего не смог найти, сможет кто подсказать что делать?

GP_MAKE_BLOCK_TAB(
"MANUAL",
GP_MAKE_BOX(GP.LABEL("Duty"); GP.SLIDER("spd", duty, 0, 1023);
GP_MAKE_BOX(GP.BUTTON("bkw", "◄"); GP.BUTTON("frw", "►");
);
);
 
Изменено:

anr69

✩✩✩✩✩✩✩
25 Ноя 2024
3
0
подскажите...
на esp8266 все норм, долго использую
пробую несколько esp32 ,поставил для плат сборку последнюю Arduino core for the ESP32 Release v3.0.7
примеры при нажатие на любую кнопку на форме .. всплывает попап окно - плата офлайн
сейчас попробую понизить сборку под плату
может кому тоже поможет ... в портале написано
  • esp32 (SDK v2+)
ну а то что 3+ это уже много чего поменяли и оно много чего не работает
на последней 2.0.18 вроде заработало
 
Изменено:

NaLex

✩✩✩✩✩✩✩
10 Май 2022
5
0
Приветствую! В каком виде файлы отправлять, чтобы обновить файловую систему (по ссылке /ota_update)? Отправляю какой-нибудь BIN и вся файловая система сбрасывается в ноль. Использую SPIFFS на ESP8266. Прошивка обновляется как положено.
 
Изменено:

anr69

✩✩✩✩✩✩✩
25 Ноя 2024
3
0
@NaLex,я могу ошибаться , но прошивка это отдельно а SPIFFS или чем вы отформатируете часть флешки это другое...в настройках контроллера вы указываете сколько под прошивку а сколько под фалы отдать...и работать с этими файлами надо уже не через /ota_update ... в портале есть примеры....
 

NaLex

✩✩✩✩✩✩✩
10 Май 2022
5
0
@anr69,
Портал позволяет загружать и файловую систему отдельно.

Разобрался.

Вариант номер 1.
Скачиваю область SPIFFS (или LittleFS) с помощью esptool и получается BIN для загрузки файловой системы через портал. Стартовый адрес узнаю с помощью инструмента (плагина) для загрузки файлов.
C++:
E:\esptool-win64\esptool -b 921600 -p COM3 read_flash 0x00200000 0x1FA000 c:\spiffs.bin
Вариант номер 2.
Создаю образ из папки с файлами с помощью mkspiffs или mklittlefs, в зависимости от используемой системы.
C++:
C:\...\mklittlefs -b 0x2000 -p 0x100 -s 0x1FA000 -c c:\data c:\littlefs.bin
Размер блока, страницы и общий размер узнаю с помощью этого скетча:
C++:
//#include <FS.h>
#include <LittleFS.h>

void setup() {
  Serial.begin(9600);

  if (!LittleFS.begin()) {
    Serial.println("Failed to mount file system");
  }
  else Serial.println("Mounted");

  FSInfo fs_info;
  LittleFS.info(fs_info);

  Serial.println();
  Serial.println("totalBytes");
  Serial.println(fs_info.totalBytes, HEX);
  Serial.println();
  Serial.println("usedBytes");
  Serial.println(fs_info.usedBytes, HEX);
  Serial.println();
  Serial.println("blockSize");
  Serial.println(fs_info.blockSize, HEX);
  Serial.println();
  Serial.println("pageSize");
  Serial.println(fs_info.pageSize, HEX);
  Serial.println();
  Serial.println("maxOpenFiles");
  Serial.println(fs_info.maxOpenFiles, HEX);
  Serial.println();
  Serial.println("maxPathLength");
  Serial.println(fs_info.maxPathLength, HEX);
}

void loop() {

}
 
Изменено:

Владлэн

✩✩✩✩✩✩✩
28 Авг 2023
24
7
Зачем всё это?
При компиляции в Arduino IDE (2.3) выбираю разбивку сколько под программу, сколько под файловую систему.
В папке (например, C:\Users\Username\AppData\Local\arduino\sketches\6A0EE9D80D67621DE2852B3DD8B18B3F\) получаю бинарник прошивки, который нормально через портал обновляется. Файловая система не слетает. А чтоб создать образ файловой системы нужны сторонние инструменты. Но я ими не пользуюсь, чаще просто через фалменеджер в портале файлы закидываю.
 
  • Лойс +1
Реакции: NaLex

NaLex

✩✩✩✩✩✩✩
10 Май 2022
5
0
@Владлэн, чтобы пользователь мог обновить файловую систему. Планируется периодическая смена файлов.
 

Владлэн

✩✩✩✩✩✩✩
28 Авг 2023
24
7
@NaLex, не, я говорю зачем так сложно.
Вот сейчас перепроверил на IDE 1.8.19.
Компилирую скетч, получаю bin файл прошивки. В инструментах установлен ESP8266 LittleFS Data Uload, нажимаю на него. Рядом с бинарником прошивки появляется бинарник файловой системы.
 
  • Лойс +1
Реакции: NaLex

NaLex

✩✩✩✩✩✩✩
10 Май 2022
5
0
@Владлэн, да, точно. Спасибо! Только в моём случае BIN файловой системы сохраняется в C:\Users\User\AppData\Local\Temp\arduino_build_709460\
 

anr69

✩✩✩✩✩✩✩
25 Ноя 2024
3
0
Оформи код соответствующим тэгом, см. Правила
C++:
void AREA(const String& name, int rows = 1, const String& value = "", const String& width = "", bool dis = false) {
    *_GPP += F("<textarea onchange='GP_click(this)' style='height:auto");
    if (width.length()) {
        *_GPP += F("; width:");
        *_GPP += width;
        *_GPP += F(";");
    }
    *_GPP += F("' name='");
    *_GPP += name;
    *_GPP += F("' id='");
    *_GPP += name;
    *_GPP += F("' rows='");
    *_GPP += rows;
    *_GPP += "'";
    if (dis) *_GPP += F(" disabled");
    *_GPP += ">";
    if (value.length()) *_GPP += value;
    *_GPP += F("</textarea>\n");
    send();
}
чуть подправил чтобы width работал....
 
Изменено:

wizard suleiman

✩✩✩✩✩✩✩
13 Окт 2023
49
2
Здравствуйте. Только вникаю в то что это такое, как работает, и что с его помощью можно делать...
кнопки как подключать в графическом интерфейсе разобрался вроде, а вот как сделать 2-3 slider не могу понять, и это возможно вообще ?
я плохо все еще понимаю что это... это создание любого графического интерфейса на веб сервере, или это только загрузка кода, и убирание из него всего не нужного...

можно ли просто 3 слайдера создать ? и все))) и как сделать ?

C++:
struct Data {
float slider1 = 33;
float slider2 = 33;
float slider3 = 33;
};
sets::Group g(b, "variables");
b.Slider1("", 0, 100, 0.5, "", &data.slider1);
b.Slider2("", 0, 100, 0.5, "", &data.slider2);
b.Slider3("", 0, 100, 0.5, "", &data.slider3);
 
Изменено:

Владлэн

✩✩✩✩✩✩✩
28 Авг 2023
24
7
У меня сделано так.
Для хранения настроек в еепром библиотека https://github.com/GyverLibs/FileData

C++:
struct Data {
    uint8_t rgbBright = 255;
    uint16_t co2Max = 2000;
    };
Data _data;
В функции void build() так:
C++:
M_BLOCK_THIN_TAB(
    "RGB светодиод",
    M_BOX(
        GP.SPAN("Максимум CO2");
        GP.SLIDER("sld/co2max", _data.co2Max, 0, 5000);
        );   
        
    M_BOX(
        GP.SPAN("Яркость");
        GP.SLIDER("sld/rgbbright", _data.rgbBright, 0, 255);
        );       
    );
В функции void action() так:
C++:
//макс. уровень co2 для светодиода   
if (ui.clickSub("sld/co2max")) { 
    _data.co2Max = valInt = ui.getInt("sld/co2max");
    }               
    
//яркость светодиода
if (ui.clickSub("sld/rgbbright")) { 
    _data.rgbBright = valInt = ui.getInt("sld/rgbbright");
    }
 
  • Лойс +1
Реакции: wizard suleiman

wizard suleiman

✩✩✩✩✩✩✩
13 Окт 2023
49
2
@Владлэн, получилось)))) огромное спс за пример
C++:
#include <Arduino.h>
// вывод всех виджетов в две группы, одна с БД, вторая с переменными
// переменные для удобства запакованы в структуру
#define WIFI_SSID "xxx"
#define WIFI_PASS "xxx"

#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif

#include <GyverDBFile.h>
#include <LittleFS.h>
GyverDBFile db(&LittleFS, "/data2.db");

#include <SettingsGyver.h>
SettingsGyver sett("My Settings", &db);

DB_KEYS(
kk,
conf,
btn);

struct Data {
float slider = 255;
uint8_t rgb1 = 255;
uint8_t rgb2 = 255;
};

Data data;
bool cfm_f, notice_f, alert_f;

void build(sets::Builder& b) {

{ sets::Group g(b, "database");

}

{ sets::Group g(b, "variables");

b.Slider("", 0, 255, 0.5, "", &data.slider);
b.Slider("rgb1", 0, 255, 0.5, "rgb1", &data.slider);
b.Slider("rgb2", 0, 255, 0.5, "rgb2", &data.slider);
}

if (b.beginButtons()) {
if (b.Button("Notice")) notice_f = true;
if (b.Button("Error")) alert_f = true;
if (b.Button("Confirm")) cfm_f = true;
b.endButtons();
}
bool res;
if (b.Confirm(kk::conf, "Confirm", &res)) {
Serial.println(res);
// Serial.println(b.build.value.toBool());
}
}

void update(sets::Updater& u) {
if (cfm_f) {
cfm_f = false;
u.update(kk::conf);
}
if (notice_f) {
notice_f = false;
u.notice("Уведомление");
}
if (alert_f) {
alert_f = false;
u.alert("Ошибка");
}
}

void setup() {
Serial.begin(115200);
Serial.println();
// ======== WIFI ========
// STA
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint8_t tries = 20;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (!--tries) break;
}
Serial.println();
Serial.print("Connected: ");
Serial.println(WiFi.localIP());
// ======== SETTINGS ========
sett.begin();
sett.onBuild(build);
sett.onUpdate(update);
// ======== DATABASE ========
#ifdef ESP32
LittleFS.begin(true);
#else
LittleFS.begin();
#endif
db.begin();

}

void loop() {
sett.tick();
}
офигенно удобная штука, без знания java и php можно многое создавать, но все же надо найти время и вникнуть...
 
Изменено:

wizard suleiman

✩✩✩✩✩✩✩
13 Окт 2023
49
2
@NaLex,
возможно... а это хорошо или плохо ?)))
взял это установил не самую последнюю версию + скачал Settings что то, как то, да работает))) в чем разница еще не вник...
я тут теперь эти ползунки еще не могу к адресной ленте подключить))) сижу вникаю...

решил еще костыль поставить, что бы текстом управлять... написал вот такой код

код для платы 1:
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.println("R1G2B3");
Serial.println(' ');
delay(1000);
Serial.println("R4G5B6");
Serial.println(' ');
delay(1000);
Serial.println("R7G8B9");
Serial.println(' ');
delay(1000);
Serial.println("R10G11B12");
Serial.println(' ');
delay(1000);
Serial.println("R13G14B15");
Serial.println(' ');
delay(1000);
}
код для платы 2:
int a = 0;
int b = 0;
int c = 0;

void setup() {
Serial.begin(115200); // Инициализация последовательного порта на скорости 115200 бод
}

void loop() {
if (Serial.available() > 0) { // Проверяем, есть ли доступные данные
String input = Serial.readStringUntil('\n'); // Читаем строку до символа новой строки
// Проверяем, начинается ли строка с 'R' и содержит 'G' и 'B'
if (input.startsWith("R") && input.indexOf('G') > 1 && input.indexOf('B') > 1) {
// Извлекаем значения
int rIndex = input.indexOf('R') + 1;
int gIndex = input.indexOf('G');
int bIndex = input.indexOf('B');
// Извлекаем числа из строки
a = input.substring(rIndex, gIndex).toInt();
b = input.substring(gIndex + 1, bIndex).toInt();
c = input.substring(bIndex + 1).toInt();

Serial.print(a);
Serial.print(' ');
Serial.print(b);
Serial.print(' ');
Serial.print(c);
Serial.print(' ');
}
}
}//конец
и если 1 плату соединить со 2, по TX - RX все работает... а вот ели код через сайт отправлять нет...
просто ужас))) если сможете по этому вопросу интеграции кода 2 в этот пример например

пример называется test:
#include <Arduino.h>

#define WIFI_SSID ""
#define WIFI_PASS ""

#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif

#include <GyverDBFile.h>
#include <LittleFS.h>
// база данных для хранения настроек
// будет автоматически записываться в файл при изменениях
GyverDBFile db(&LittleFS, "/data.db");

#include <SettingsGyver.h>
// указывается заголовок меню, подключается база данных
SettingsGyver sett("My Settings", &db);

// ключи для хранения в базе данных
enum kk : size_t {
    txt,
    pass,
    uintw,
    intw,
    int64w,

    color,
    toggle,
    slider,
    selectw,
    sldmin,
    sldmax,

    lbl1,
    lbl2,

    date,
    timew,
    datime,

    btn1,
    btn2,
};

sets::Logger logger(150);

// билдер! Тут строится наше окно настроек
void build(sets::Builder& b) {
    // можно узнать, было ли действие по виджету
    if (b.build.isAction()) {

Serial.print("Set: 0x");
Serial.print(b.build.id, HEX);
Serial.print(" = ");
Serial.println(b.build.value);   
logger.print("Set: 0x");
logger.println(b.build.id, HEX);

/* заменил на вот так вроде, сейчас не могу проверить... но все равно не работает
Serial.print(b.build.id);
Serial.println(b.build.value);
 logger.println(b.build.id);
*/




    }

    // группа. beginGroup всегда вернёт true для удобства организации кода
    if (b.beginGroup("Group 1")) {
        b.Input(kk::txt, "Text");
        b.Pass(kk::pass, "Password");
        b.Input(kk::uintw, "uint");
        b.Input(kk::intw, "int");
        b.Input(kk::int64w, "int 64");

        b.endGroup();  // НЕ ЗАБЫВАЕМ ЗАВЕРШИТЬ ГРУППУ
    }

    // пара лейблов вне группы. Так тоже можно
    b.Label(kk::lbl1, "Random");
    b.Label(kk::lbl2, "millis()", "", sets::Colors::Red);

    // ещё группа
    // можно использовать такой синтаксис: sets::Group(Builder&, title) заключается в блок кода в самом начале,
    // вызывать endGroup в этом случае не нужно
    // так же работают остальные контейнеры (Menu, Buttons)
    {
        sets::Group g(b, "Group 2");
        b.Color(kk::color, "Color");
        b.Switch(kk::toggle, "Switch");
        b.Select(kk::selectw, "Select", "var1;var2;hello");
        b.Slider(kk::slider, "Slider", -10, 10, 0.5, "deg");
        b.Slider2(kk::sldmin, kk::sldmax, "Slider", -10, 10, 0.5, "deg");

        // логгер, в него печатаем выше
        b.Log(logger);
    }

    // и ещё
    if (b.beginGroup("Group3")) {
        b.Date(kk::date, "Date");
        b.Time(kk::timew, "Time");
        b.DateTime(kk::datime, "Datime");

        // а это кнопка на вложенное меню. Далее нужно описать его содержимое
        if (b.beginMenu("Submenu")) {
            // тут тоже могут быть группы
            if (b.beginGroup("Group 3")) {
                b.Switch("sw1"_h, "switch 1");
                b.Switch("sw2"_h, "switch 2");
                b.Switch("sw3"_h, "switch 3");
                b.endGroup();
            }

            // и просто виджеты
            b.Label("lbl3"_h, "Another label", "Val", sets::Colors::Green);
            b.Label("lbl4"_h, "Привет", "Val", sets::Colors::Blue);

            b.endMenu();  // не забываем завершить меню
        }

        b.endGroup();
    }

    // кнопки являются "групповым" виджетом, можно сделать несколько кнопок в одной строке
    if (b.beginButtons()) {
        // кнопка вернёт true при клике
        if (b.Button(kk::btn1, "reload")) {
            Serial.println("reload");
            b.reload();
        }

        if (b.Button(kk::btn2, "clear db", sets::Colors::Blue)) {
            Serial.println("clear db");
            db.clear();
            db.update();
        }

        b.endButtons();  // завершить кнопки
    }
}

// это апдейтер. Функция вызывается, когда вебморда запрашивает обновления
void update(sets::Updater& upd) {
    // можно отправить значение по имени (хэшу) виджета
    upd.update(kk::lbl1, random(100));
    upd.update(kk::lbl2, millis());

    // примечание: при ручных изменениях в базе данных отправлять новые значения не нужно!
    // библиотека сделает это сама =)
}

void setup() {
    Serial.begin(115200);
    Serial.println();

    // ======== WIFI ========

    // STA
    WiFi.mode(WIFI_AP_STA);
    // if (strlen(WIFI_SSID)) {
    WiFi.begin(WIFI_SSID, WIFI_PASS);
    uint8_t tries = 20;
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
        if (!--tries) break;
    }
    Serial.println();
    Serial.print("Connected: ");
    Serial.println(WiFi.localIP());
    // }

    // AP
    WiFi.softAP("AP ESP");
    Serial.print("AP: ");
    Serial.println(WiFi.softAPIP());

    // ======== SETTINGS ========
    sett.begin();
    sett.onBuild(build);
    sett.onUpdate(update);

    // настройки вебморды
    // sett.config.requestTout = 3000;
    // sett.config.sliderTout = 500;
    // sett.config.updateTout = 1000;

    // ======== DATABASE ========
#ifdef ESP32
    LittleFS.begin(true);
#else
    LittleFS.begin();
#endif
    db.begin();
    db.init(kk::txt, "text");
    db.init(kk::pass, "some pass");
    db.init(kk::uintw, 64u);
    db.init(kk::intw, -10);
    db.init(kk::int64w, 1234567ll);
    db.init(kk::color, 0xff0000);
    db.init(kk::toggle, (bool)1);
    db.init(kk::slider, -3.5);
    db.init(kk::selectw, (uint8_t)1);
    db.init(kk::date, 1719941932);
    db.init(kk::timew, 60);
    db.init(kk::datime, 1719941932);
    db.init(kk::sldmin, -5);
    db.init(kk::sldmax, 5);

    db.dump(Serial);

    // инициализация базы данных начальными значениями
}

void loop() {
    // тикер, вызывать в лупе
    sett.tick();
}

было бы сверхчудестно))) а так же если найдете этому парсеру дом в ваших проектах тоже будет хорошо)))