Skip to content

Простой конструктор вебморды для настроек esp8266/esp32

License

Notifications You must be signed in to change notification settings

GyverLibs/Settings

Repository files navigation

latest PIO Foo Foo Foo

Foo

Settings

Библиотека для создания простого веб-интерфейса настроек на esp8266/esp32

  • Веб-приложение весит около 15кб и вшивается в программу в бинарном gzip виде без возни с файлами
  • Удобный билдер панели управления из скетча
  • Десяток типовых виджетов с возможностью объединения в группы и вложенные меню
  • Система авторизации с разными правами для авторизованных юзеров и гостей
  • Файловый менеджер и OTA (обновление по воздуху)
  • Интеграция с библиотекой GyverDB для полностью автоматического хранения данных
  • Компактный бинарный протокол связи
  • Легко адаптируется под любую библиотеку HTTP сервера, из коробки реализовано три версии: GyverHTTP, стандартный esp-WebServer, ESPAsyncWebserver
  • Это GyverHub на минималках
  • Исходник веб-приложения здесь

Есть Android-приложение для поиска устройств с библиотекой

promo

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

ESP8266, ESP32

Зависимости

  • GTL v1.1.14+
  • GyverDB v1.1.8+
  • StringUtils v1.4.28+
  • GyverHTTP v1.0.23+
  • BSON v2.0.0+

При установке из реестра PIO или Arduino IDE все зависимости установятся автоматически

platformio.ini
[env]
framework = arduino
lib_deps =
    GyverLibs/Settings
    ;esphome/ESPAsyncWebServer-esphome   ; для версии SettingsAsync
    ;esphome/ESPAsyncTCP-esphome         ; для версии SettingsAsync

[env:d1_mini]
platform = espressif8266
board = d1_mini
upload_speed = 921600
monitor_speed = 115200
monitor_filters = esp8266_exception_decoder, default
build_type = debug
board_build.filesystem = littlefs

[env:esp32dev]
monitor_speed = 115200
platform = espressif32
board = esp32dev
upload_speed = 921600
board_build.filesystem = littlefs

[env:esp32-c3]
monitor_speed = 115200
platform = espressif32
board = esp32dev
board_build.mcu = esp32c3
upload_speed = 2000000
board_build.f_cpu = 80000000L
board_build.filesystem = littlefs

Содержание

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

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

Вебморда является самостоятельным веб-приложением, написанным на html/css/js. Её файлы минифицированы, сжаты в gz и вшиты в код библиотеки как PROGMEM массив. В библиотеке настроен вебсервер, который отправляет файлы вебморды при заходе на IP платы в браузере. Лёгкий html файл подгружается каждый раз, а скрипты и стили кешируются браузером для ускорения загрузки. Вебморда общается с платой по http: например при загрузке запрашивает пакет с виджетами и прочей информацией, пакеты имеют формат бинарного json.

Captive portal

Во всех трёх реализациях сервера из коробки настроен DNS для работы как Captive portal - если ESP работает в режиме точки доступа (AP или AP_STA), то при подключении к точке автоматически откроется окно браузера со страницей настроек.

Приложение для поиска

Позволяет находить устройства с библиотекой в локальной сети и заменяет браузер, вебморда открывается сразу в приложении, кнопка назад возвращает к списку устройств. Чтобы удалить устройство - долгое удержание на нём на смартфоне или правой кнопкой мыши на ПК. Для поиска смартфон/ПК должны быть в одной локальной сети с устройством. В приложении должна быть указана корректная маска подсети (настраивается в роутере). Если в роутере она не менялась - то она там стандартная 255.255.255.0, как и в приложении по умолчанию.

Требуется версия библиотеки v1.0.13+

Билдер и виджеты

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

// минимальный код
SettingsGyver sett;

void build(sets::Builder& b) {
    // b.Input(...);
    // b.Button(...);
}

void setup() {
    // подключение к WiFi...

    sett.begin();
    sett.onBuild(build);
}

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

ID виджета

У всех виджетов есть вариант функции с ID и без ID. ID виджета нужен для:

  • Работа с подключенной базой данных на чтение и запись значений
  • Отправка обновлений на виджет
  • Разбор действий отдельно от вывода виджетов, чтобы разделить UI и обработку

ID в данной библиотеке задаётся числом, тип size_t (на ESP это 32-бит беззнаковое целое). Можно задавать ID:

  • Просто числом вручную
  • enum
  • Хэш-строки из библиотеки StringUtils: SH("my_input") или "my_input"_h
  • Хэш-ключи DB_KEYS из библиотеки GyverDB

Если ID не задан - он будет присваиваться библиотекой автоматически (только для активных виджетов). Автоматический ID - это число от UINT32_MAX, уменьшается на 1 с каждым вызовом. Если автоматический ID совпадёт с каким то из вручную заданных - в вебморде высветится ошибка Duplicated ID

Не рекомендуется задавать ID числом 0, т.к. b.build.id == 0 при запросе виджетов

enum keys : size_t {
    my_inp = 1,
    button,
};

DB_KEYS(
    kk,
    my_inp,
    button
);

void build(sets::Builder& b) {
    b.Input("My input");                // без ID
    b.Input("my_inp"_h, "My input");    // хэш-строка
    b.Input(SH("my_inp"), "My input");  // хэш-строка
    b.Input(12, "My input");            // вручную числом
    b.Input(keys::my_inp, "My input");  // enum
    b.Input(kk::my_inp, "My input");    // GyverDB-хэш
}

Взаимодействие с виджетами

Есть несколько способов взаимодействия с виджетами, т.е. отправки и получения значений:

  • У виджета можно задать ID, по которому библиотека будет автоматически читать и писать данные в базу данных GyverDB
  • К виджету можно подключить переменную, библиотека будет читать из неё значение и писать при изменении
  • У виджета без ID и переменной будет значение по умолчанию (0 или пустая строка), но получить новое значение с вебморды можно из инфо о билде
  • Активный виджет (значение можно менять из вебморды) возвращает true при изменении значения пользователем, также при клике по кнопке
String str;
char cstr[20];

void build(sets::Builder& b) {
    // виджет без id и начального значения
    // При установке с вебморды получаем значение напрямую
    if (b.Input("My input")) {
        Serial.println(b.build.value);
    }

    // виджет без id с привязанной String-строкой
    // при установке с вебморды значение запишется в строку
    b.Input("My input", &str);
    b.Input("My input", AnyPtr(cstr, 20));  // для char-массивов

    // виджет с id без привязанной переменной
    // будет работать с базой данных по указанному ключу
    b.Input("my_inp"_h, "My input");

    // действие возвращается независимо от наличия id
    if (b.Button()) Serial.println("btn 1");
    if (b.Button("my_btn2"_h)) Serial.println("btn 2");
}

Здесь AnyPtr - тип данных, принимающий указатель на любой встроенный тип (числа, строки). Для передачи ему char-буфера нужно явно вызвать конструктор с указанием размера буфера

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);
    }
}
// разделение UI и действий
void build(sets::Builder& b) {
    // вывод UI
    b.Input("my_inp"_h, "My input");
    b.Button("my_btn"_h, "My button");

    // обработка действий
    switch (b.build.id) {
        case "my_inp"_h:
            Serial.print("input: ");
            Serial.println(b.build.value);
            break;

        case "my_btn"_h:
            Serial.println("btn click");
            break;
    }
}

Если ID виджета задан и переменная не привязана - будет использоваться БД. Если привязана переменная - будет использоваться она

Тип данных Text

Text - это обёртка для текстовых данных из библиотеки StringUtils, см. документацию там. Text может конвертироваться в любые другие типы и сравниваться с ними, а также имеет множество инструментов для парсинга и поиска.

Serial.println(b.build.value);  // печататься
b.build.value == 123;           // сравниваться
b.build.value == "123";         // сравниваться
b.build.value.toFloat();        // конвертироваться
byte b = b.build.value;         // авто-конвертироваться

База данных

Библиотека интегрирована с GyverDB - относительно быстрой базой данных для хранения данных любого типа. Settings автоматически читает и обновляет данные в БД, поэтому рекомендуется изучить как работать с БД на странице описания GyverDB. При использовании GyverDBFile база данных будет автоматически писаться в файл при изменениях, а файловая система позаботится об оптимальном износе flash памяти. При запуске рекомендуется инициализировать БД, указав ключи и соответствующие им начальные значения и типы. Эти значения будут записаны только в том случае, если запись в БД ещё не существует. В то же время автоматическое обновление БД работает только для существующих записей, т.е. Settings будет работать только с сущестующими ячейками и не создаст новых. Минимальный пример:

// Подключить библиотеки и создать БД и Settings
#include <GyverDBFile.h>
#include <LittleFS.h>
GyverDBFile db(&LittleFS, "/data.db");

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

// Объявить хэш-ключи БД через макрос. Это удобнее, чем "строки" и enum, 
// а также не боится изменения порядка или удаления ключей из середины списка
DB_KEYS(
    keys,
    input,
    slider
);

// билдер
void build(sets::Builder& b) {
    b.Input(kk::input);
    b.Slider(kk::slider);
}

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

    // WIFI
    WiFi.mode(WIFI_STA);
    WiFi.begin("WIFI_SSID", "WIFI_PASS");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println();
    Serial.println(WiFi.localIP());

    // settings
    sett.begin();
    sett.onBuild(build);

    // запуск файловой системы
#ifdef ESP32
    LittleFS.begin(true); // format on fail
#else
    LittleFS.begin();
#endif

    // запуск БД и чтение из файла
    db.begin();
    
    // инициализация БД начальными значениями
    db.init(keys::input, "text");
    db.init(keys::slider, 30);
}

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

Несколько БД

Можно использовать несколько баз данных, например одна для сохраняемых в память настроек, вторая для "временных" настроек, которые не нужно сохранять при перезагрузке: GyverDBFile сохраняет в файл, а обычная GyverDB - нет, живёт чисто в оперативной памяти. Переключаться между БД нужно в билдере таким образом, чтобы после смены БД шли только виджеты с ключами из этой БД. Например

GyverDBFile db_flash(&LittleFS, "/data.db");
GyverDB db_ram;

void build(sets::Builder& b) {
    settings.attachDB(&db_ram);
    b.Input("input2"_h, "...");

    settings.attachDB(&db_flash);
    b.Input("input1"_h, "...");
}

После выхода из билдера нужно оставлять подключенной ту БД, для которой нужны автоматические обновления, система не сможет обновляться одновременно с нескольких БД. Также нужно оставлять последней подключенной БД, которая пишет на флешку, чтобы система автоматически вызывала её тикер.

Динамические виджеты

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

int numbers[5];

void build(sets::Builder& b) {
    // вывод в цикле
    for (int i = 0; i < 5; i++) {
        b.Input();
    }
    
    // обработка действий также будет работать
    for (int i = 0; i < 5; i++) {
        if (b.Input()) Serial.println(b.build.value);
    }
    
    // массив number с привязанными переменными
    for (int i = 0; i < 5; i++) {
        b.Number(String("number #") + i, &numbers[i]);
    }

    // обработка и действий
    for (int i = 0; i < 5; i++) {
        if (b.Number(String("number #") + i, &numbers[i])) {
            Serial.print(String("number #") + i + ": ");
            Serial.println(numbers[i]);
        }
    }

    // можно и так
    for (int i = 0; i < 5; i++) {
        b.Number(String("number #") + i, &numbers[i]);

        if (b.wasSet()) {
            Serial.print(String("number #") + i + ": ");
            Serial.println(numbers[i]);
            b.clearSet();
        }
    }
}

Также можно динамически скрывать виджеты, например

void build(sets::Builder& b) {
    if (flag) {
        b.Input();
        b.Slider();
        // ...
    }
}

Частым сценарием является открытие группы настроек с активацией режима, это можно сделать так:

void build(sets::Builder& b) {
    if (b.Switch()) {
        b.reload(); // перезагрузить вебморду по клику на свитч
    }

    // здесь flag должен быть прочитан из БД или переменной
    if (flag) {
        b.Input();
        b.Slider();
        // ...
    }
}

Например с БД

DB_KEYS(
    kk,
    mode_sw
);

void build(sets::Builder& b) {
    // запись в БД и перезагрузка
    if (b.Switch(kk::mode_sw)) b.reload();

    // чтение из БД
    if (db[kk::mode_sw]) {
        b.Input();
        b.Slider();
        // ...
    }
}

Обновления

В системе реализован механизм обновлений - вебморда периодически запрашивает у устройства обновления. Если база данных подключена - то при изменениях значений в базе где то в программе библиотека автоматически отправит новые значения в вебморду (например если какое то значение изменилось при помощи кнопки). Примечание: если вебморда открыта одновременно с нескольких браузеров - обновления базы данных получит только тот из них, который запросил их первым. Также можно отправить свои значения, если база данных не подключена или не используется для каких-то виджетов. Для этого нужно подключить обработчик обновлений и вручную отправить данные по id виджета.

void build(sets::Builder& b) {
    b.Label("lbl1"_h, "Random");
    b.Label("lbl2"_h, "millis()", "", sets::Colors::Red);
}
void update(sets::Updater& upd) {
    upd.update("lbl1"_h, random(100));
    upd.update("lbl2"_h, millis());
}

void setup() {
    sett.begin();
    sett.onBuild(build);
    sett.onUpdate(update);
}

Warning

В функции update нельзя складывать строки, либо результат нужно преобразовать к String: upd.update(id, (String)(str + 123 + "abc"));

В обновлении также можно вызвать upd.alert(текст) и upd.notice(текст) - всплывающее окно с текстом

Статус

  • Вебморда отслеживает статус устройства, при потере связи появится текст offline в заголовке страницы. После потери связи вебморда будет запрашивать информацию о виджетах, это очень удобно при разработке - например добавляем виджет, загружаем прошивку. За это время вебморда уже понимает что устройство оффлайн и при первом успешном подключении выводит актуальные виджеты.
  • При изменении значений виджетов вебморда следит за доставкой пакета, при ошибке связи появится надпись error* у соответствующего виджета

Настройки вебморды

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

sett.config.sliderTout = 100;
sett.config.requestTout = 2000;
sett.config.updateTout = 2500;

Контейнеры

Виджеты можно объединять в контейнеры. Контейнер нужно начать и закончить, так как пакет данных собирается линейно в целях оптимизации скорости и памяти. Метод beginКонтейнер всегда вернёт true для красоты организации кода в блоке условия:

void build(sets::Builder& b) {
    if (b.beginGroup("Group 1")) {
        b.Input("input1"_h, "Text");

        b.endGroup();  // закрыть группу
    }
}

Второй вариант - у всех контейнеров есть парный класс, который сам откроет и закроет контейнер. Нужно создать объект с любым именем и передать ему билдер:

void build(sets::Builder& b) {
    {
        sets::Group g(b, "Group 2");  // должен быть первым в блоке

        b.Input("input1"_h, "Text");
    }
}

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

void build(sets::Builder& b) {
    b.Input("input1"_h, "Text 1");

    {
        sets::Menu g(b, "Submenu");

        b.Input("input2"_h, "Text 2");
    }
}

Можно располагать виджеты горизонтально в строку, у них может быть общее название. Если у виджета задано название - он будет пытаться растянуться на всю ширину, если нет - то не будет. Частый вариант использования - первый виджет с названием, остальные мелкие без:

void build(sets::Builder& b) {
    {
        sets::Row g(b);
        // sets::Row g(b, "Row");

        b.Slider("Slider");
        b.LED();
        b.Switch();
    }
}

Отдельный тип контейнера - кнопки, внутри него можно добавлять только кнопки:

void build(sets::Builder& b) {
    {
        sets::Buttons btns(b);

        // кнопка вернёт true при клике
        if (b.Button("btn1"_h, "Button 1")) {
            Serial.println("Button 1");
        }

        if (b.Button("btn2"_h, "Button 2", sets::Colors::Blue)) {
            Serial.println("Button 2");
        }
    }
}

Авторизация

В системе предусмотрена авторизация: если в прошивке указать отличный от пустой строки пароль - вебморда будет работать в "гостевом" режиме: отображаются только разрешённые гостям виджеты, файловый менеджер и OTA скрыты и заблокированы. Для ввода пароля нужно зайти в меню (правая верхняя кнопка) и нажать на ключик. Серый ключик означает что авторизация отключена, зелёный - клиент авторизован, красный - неверный пароль. Пароль может содержать любые символы и иметь любую длину - в явном виде он не хранится и не передаётся. Пароль сохраняется в браузере и авторизация работает автоматически при перезагрузке страницы.

Для разделения админского и гостевого доступа предусмотрен виртуальный контейнер Guest. Если пароль установлен и клиент не авторизован - он будет видеть только виджеты из гостевых контейнеров. Для корректной работы гостевой контейнер не должен прерываться обычными контейнерами. Пример:

void setup() {
    // ...
    // sett.setPass("pass1234");
    sett.setPass(F("pass1234"));  // любая строка
}

void build(sets::Builder& b) {
    if (b.beginGroup("Group 1")) {
        // гости не видят
        b.Pass(kk::pass, "Password");

        // виджеты, которые видят гости и админы
        {
            sets::GuestAccess g(b);
            b.Input(kk::uintw, "uint");
            b.Input(kk::intw, "int");
            b.Input(kk::int64w, "int 64");
        }

        // гости не видят
        {
            sets::Menu m(b, "sub sub");
            b.Label(kk::lbl2, "millis()", "", sets::Colors::Red);
        }

        b.endGroup();
    }
}

В гостевой контейнер можно поместить несколько обычных контейнеров, например групп.

Примечание: если вложенное меню закрыто от гостей, но содержит ещё одно вложенное меню - кнопка открытия меню будет отображаться, но само меню будет пустым

Иконки лейблов

Можно использовать emoji, они неплохо смотрятся в меню. Например с удобного сайта

b.Input(kk::intw, "🔈Громкость");

Виджеты

Особенности некоторых виджетов

  • Time - принимает и отправляет время в секундах с начала суток независимо от часового пояса браузера
  • Date/DateTime - принимает и отправляет unix-секунды, в браузере выводится с учётом часового пояса браузера
  • Slider - строка результата кликабельная, можно задать значение вручную, оно подчиняется настройкам min/max/step
  • Color - принимает и отправляет цвет в 24-битном формате RRGGBB
  • Select - принимает и отправляет индекс подстроки в списке вариантов, начиная с 0
  • Pass - если передать третьим аргументом строку - она будет отображаться как "заглушка" для окна пароля, например b.Pass(pass, "Pass", "***");
  • Confirm - при любом выборе юзера виджет возвращает true. Для определения что именно выбрал юзер можно опросить b.build.value.toBool() или подключить bool переменную
  • Button - если выводится последней в группе - меняет свой стиль на "ярлык", который прикреплен к окну группы снизу

Описание классов

  • SettingsGyver (SettingsGyver.h) - на вебсервере GyverHTTP
  • SettingsESP (SettingsESP.h) - на стандартном вебсервере ESP
  • SettingsAsync (SettingsAsync.h) - на асинхронном ESPAsyncWebserver

Настройки компиляции

#define SETT_NO_DB  // полностью отключить поддержку GyverDB

SettingsBase/SettingsGyver/SettingsESP/SettingsAsync

Settings(const String& title = "", GyverDB* db = nullptr);

// установить пароль на вебморду. Пустая строка "" чтобы отключить
void setPass(Text pass);

// перезагрузить страницу. Можно вызывать где угодно + в обработчике update
void reload();

// установить заголовок страницы
void setTitle(const String& title);

// подключить базу данных
void attachDB(GyverDB* db);

// использовать автоматические обновления из БД (при изменении записи новое значение отправится в браузер)
void useAutoUpdates(bool use);

// обработчик билда типа f(sets::Builder& b)
void onBuild(BuildCallback cb);

// обработчик обновлений типа f(sets::Updater& upd)
void onUpdate(UpdateCallback cb);

// обработчик скачивания файлов с устройства типа f(Text path)
void onFetch(FileCallback cb);

// обработчик загрузки файлов на устройство типа f(Text path)
void onUpload(FileCallback cb);

// тикер, вызывать в родительском классе
void tick();

// установить размер пакета (умолч. 1024 Б). 0 - отключить разбивку на пакеты. Не работает для Async-версии
void setPacketSize(size_t size);

// установить кастом js код из PROGMEM
void setCustom(const char* js, size_t len, bool gz = false);

// установить кастом js код из файла
void setCustomFile(const char* path, bool gz = false);

// установить инфо о проекте (отображается на вкладке настроек и файлов)
void setProjectInfo(const char* name, const char* link = nullptr);

// настройки вебморды
Config config;
{
    // таймаут отправки слайдера, мс. 0 чтобы отключить
    uint16_t sliderTout = 100;

    // таймаут ожидания ответа сервера, мс
    uint16_t requestTout = 2000;

    // период обновлений, мс. 0 чтобы отключить
    uint16_t updateTout = 2500;
}

Builder

// инфо о билде
Build build;

// авто-ID следующего виджета
size_t nextID();

// указатель на текущий SettingsXxx
void* thisSettings();

// перезагрузить страницу (вызывать в действии, например if (...click() b.reload()))
void reload();

// было действие с каким-то из виджетов выше
bool wasSet();

// сбросить флаг чтения wasSet
void clearSet();

// КОНТЕЙНЕРЫ
// разрешить неавторизованным клиентам следующий код
bool beginGuest();

// запретить неавторизованным клиентам
void endGuest();

// группа
bool beginGroup(Text title = Text());
void endGroup();

// вложенное меню
bool beginMenu(Text title);
void endMenu();

// горизонтальная группа виджетов
bool beginRow(Text title = Text());
void endRow();

// ряд кнопок
bool beginButtons();
void endButtons();

// ВИДЖЕТЫ
// ПАССИВНЫЕ

// ================= LOG =================
void Log(size_t id, Logger& log, Text label = "");
void Log(Logger& log, Text label = "");

// ================= LABEL =================
// текстовое значение, может обновляться по id
void Label(size_t id, Text label = "", Text text = Text(), uint32_t color = SETS_DEFAULT_COLOR);
void Label(size_t id, Text label, Text text, sets::Colors color);
void Label(Text label = "", Text text = Text(), uint32_t color = SETS_DEFAULT_COLOR);
void Label(Text label, Text text, sets::Colors color);

// лейбл с численным значением (выполняется быстрее, весит меньше)
void LabelNum(size_t id, Text label, T text, uint32_t color = SETS_DEFAULT_COLOR);
void LabelNum(size_t id, Text label, T text, sets::Colors color);
void LabelNum(Text label, T text, uint32_t color = SETS_DEFAULT_COLOR);
void LabelNum(Text label, T text, sets::Colors color);

void LabelFloat(size_t id, Text label, float text, uint8_t dec = 2, uint32_t color = SETS_DEFAULT_COLOR);
void LabelFloat(size_t id, Text label, float text, uint8_t dec, sets::Colors color);
void LabelFloat(Text label, float text, uint8_t dec = 2, uint32_t color = SETS_DEFAULT_COLOR);
void LabelFloat(Text label, float text, uint8_t dec, sets::Colors color);

// ================= LED =================
// светодиод (value 1 включен - зелёный, value 0 выключен - красный)
void LED(size_t id, Text label, bool value);
void LED(size_t id, Text label = "");
void LED(Text label, bool value);
void LED(Text label = "");

// светодиод с цветом на выбор
void LED(size_t id, Text label, bool value, uint32_t colorOff, uint32_t colorOn);
void LED(size_t id, Text label, bool value, Colors colorOff, Colors colorOn);
void LED(Text label, bool value, uint32_t colorOff, uint32_t colorOn);
void LED(Text label, bool value, Colors colorOff, Colors colorOn);

// ================= TEXT =================
// текстовый абзац
void Paragraph(size_t id, Text label = "", Text text = Text());
void Paragraph(Text label = "", Text text = Text());

// active

// ================= INPUT =================
// ввод текста и цифр [результат - строка], подключаемая переменная - любой тип
bool Input(size_t id, Text label = "", AnyPtr value = nullptr);
bool Input(Text label = "", AnyPtr value = nullptr);

// ================= NUMBER =================
// ввод цифр [результат - строка], подключаемая переменная - любой тип
bool Number(size_t id, Text label = "", AnyPtr value = nullptr);
bool Number(Text label = "", AnyPtr value = nullptr);

// ================= PASS =================
// ввод пароля [результат - строка], подключаемая переменная - любой тип
bool Pass(size_t id, Text label = "", AnyPtr value = nullptr);
bool Pass(Text label = "", AnyPtr value = nullptr);

// ================= COLOR =================
// ввод цвета [результат - 24-бит DEC число], подключаемая переменная - uint32_t
bool Color(size_t id, Text label = "", uint32_t* value = nullptr);
bool Color(Text label = "", uint32_t* value = nullptr);

// ================= SWITCH =================
// переключатель [результат 1/0], подключаемая переменная - bool
bool Switch(size_t id, Text label = "", bool* value = nullptr, uint32_t color = SETS_DEFAULT_COLOR);
bool Switch(size_t id, Text label, bool* value, Colors color);
bool Switch(Text label = "", bool* value = nullptr, uint32_t color = SETS_DEFAULT_COLOR);
bool Switch(Text label, bool* value, Colors color);

// ================= DATE =================
// дата [результат - unix секунды], подключаемая переменная - uint32_t
bool Date(size_t id, Text label = "", uint32_t* value = nullptr);
bool Date(Text label = "", uint32_t* value = nullptr);

// ================= TIME =================
// время [результат - секунды с начала суток], подключаемая переменная - uint32_t
bool Time(size_t id, Text label = "", uint32_t* value = nullptr);
bool Time(Text label = "", uint32_t* value = nullptr);

// ================= DATETIME =================
// дата и время [результат - unix секунды], подключаемая переменная - uint32_t
bool DateTime(size_t id, Text label = "", uint32_t* value = nullptr);
bool DateTime(Text label = "", uint32_t* value = nullptr);

// ================= SLIDER =================
// слайдер [результат - число], подключаемая переменная - любой тип
bool Slider(size_t id, Text label = "", float min = 0, float max = 100, float step = 1, Text unit = Text(), AnyPtr value = nullptr, uint32_t color = SETS_DEFAULT_COLOR);
bool Slider(size_t id, Text label, float min, float max, float step, Text unit, AnyPtr value, Colors color);
bool Slider(Text label = "", float min = 0, float max = 100, float step = 1, Text unit = Text(), AnyPtr value = nullptr, uint32_t color = SETS_DEFAULT_COLOR);
bool Slider(Text label, float min, float max, float step, Text unit, AnyPtr value, Colors color);

// двойной слайдер [результат - число], подключаемая переменная - любой тип
bool Slider2(size_t id_min, size_t id_max, Text label = "", float min = 0, float max = 100, float step = 1, Text unit = Text(), AnyPtr value_min = nullptr, AnyPtr value_max = nullptr, uint32_t color = SETS_DEFAULT_COLOR);
bool Slider2(size_t id_min, size_t id_max, Text label, float min, float max, float step, Text unit, AnyPtr value_min, AnyPtr value_max, Colors color);

// ================= SELECT =================
// опции разделяются ; [результат - индекс (число)], подключаемая переменная - uint8_t
bool Select(size_t id, Text label, Text options, uint8_t* value = nullptr);
bool Select(Text label, Text options, uint8_t* value = nullptr);

// ================= BUTTON =================
// кнопку можно добавлять как внутри контейнера кнопок, так и как одиночный виджет
bool Button(size_t id, Text label = "", uint32_t color = SETS_DEFAULT_COLOR);
bool Button(Text label = "", uint32_t color = SETS_DEFAULT_COLOR);

bool Button(size_t id, Text label, sets::Colors color);
bool Button(Text label, sets::Colors color);

// misc
// окно подтверждения, для активации отправь пустой update на его id или update с текстом подтверждения
bool Confirm(size_t id, Text label = "", bool* ptr = nullptr);

// кастомный виджет, type соответствует имени класса. params - ключи и значения
bool Custom(Text type, size_t id, const BSON& params = BSON(), AnyPtr value = nullptr);
  • Text - универсальный текстовый формат, принимает строки в любом виде. При указании value отличным от стандартного будет отправлено его значение. Иначе будет отправлено значение из БД, если она подключена. Если в качестве значения нужно число - используйте конструктор Value, например b.Color("col", "Color", Value(my_color));, где my_color это uint32_t.
  • AnyPtr - указатель на переменную любого типа из списка: float, double, любой целочисленный, String, AnyPtr(char[], size_t len)

Build

Инфо о билде

// тип билда
const Type type;

// клиент авторизован
const bool granted;

// id виджета (действие)
const size_t id;

// значение виджета (действие)
const Text value;

// тип - сборка виджетов
bool isBuild();

// тип - действие (обработка клика или значения)
bool isAction();

Контейнеры

// контейнер гостевого доступа
class GuestAccess(Builder& b);

// контейнер группы виджетов
class Group(Builder& b, Text title = Text());

// контейнер вложенного меню
class Menu(Builder& b, Text title);

// горизонтальный контейнер
class Row(Builder& b, Text title);

// контейнер кнопок
class Buttons(Builder& b);

Updater

// всплывающее уведомление красное
void alert(Text text);

// всплывающее уведомление зелёное
void notice(Text text);

// пустой апдейт (например для вызова Confirm)
void update(size_t id);

// апдейт с float
void update(size_t id, float value, uint8_t dec = 2);

// апдейт с числом
void update(size_t id, <любой численный тип> value);

// апдейт с текстом
void update(size_t id, <любой текстовый тип> value);

// апдейт логгера
void update(size_t id, Logger& logger);

// апдейт для двойного слайдера
void update2(size_t id_min, <любой численный тип> value_min, <любой численный тип> value_max);
void update2(size_t id_min, float value_min, float value_max, uint8_t dec = 2);

// кастом апдейт для кастом виджета, params - ключи и значения
void update(size_t id, BSON& params);

Logger

Logger(size_t size);

// наследует Print
void print(любые_данные);
void println(любые_данные);

// вывод в String
String toString();

Версии

  • v1.0

  • v1.0.2

    • Добавлен виджет Confirm (всплывающее окно подтверждения)
    • Кастомные всплывающие окна для Input (Input теперь работает на просмотре AP WiFi точки на Xiaomi)
  • v1.0.5

    • Добавлен виджет LED
    • Добавлен файловый менеджер
    • Добавлено ОТА обновление
    • Добавлена авторизация и гостевой фильтр виджетов
    • Новый стиль для Select
  • v1.1.0

    • Добавлен виджет Number
    • Создание виджета без ID (автоматический ID)
    • Создание виджета без лейбла (будет равен типу виджета)
    • Привязка внешней переменной к виджету на чтение и запись
    • Редактор текстовых файлов в менеджере файлов
    • Убран лаг с прошлым состоянием вебморды при обновлении страницы
    • Возможность полностью убрать поддержку GyverDB дефайном
    • Мелкие фиксы стилей
    • Методы build(), value(), id() теперь являются членами (build, value, id) для краткости
  • v1.1.4

    • У виджета Slider теперь кликабельный результат, можно ввести число
    • Виджеты DateTime и Date теперь выводят и отправляют unix время с учётом часового пояса браузера
  • v1.1.5

    • Виджету LED добавлена настройка цвета включенный/выключенный
    • Виджету Confirm добавлена возможность подключить bool переменную для результата
    • Добавлена возможность отправить всплывающее окно в Update
  • v1.1.6

    • Добавлен виджет LabelNum для чисел
    • Добавлены настройки вебморды (таймаут соединения, слайдеров, апдейтов)
  • v1.1.8

    • Разбивка на пакеты для меньшего использования памяти
    • Добавлен LabelFloat
  • v1.1.12

    • Добавлена поддержка цвета виджетам Slider и Switch
  • v1.2.0

    • Добавлен двойной слайдер
    • Добавлен логгер (Web Serial)
    • Добавлена поддержка кастомных виджетов на JS
    • Добавлены обработчики скачивания и загрузки файлов

Установка

  • Библиотеку можно найти по названию Settings и установить через менеджер библиотек в:
    • 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: найти библиотеку как при установке и нажать "Обновить"
  • Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!

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

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

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

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