GyverLibs/GyverPortal

Идеи/проблемы на обновление v3.5

GyverLibs opened this issue · 173 comments

v3.5

  • Добавлено
    • Отображение границ таблицы TABLE_BORDER()
    • Опасные copy- и click-парсеры (для опроса в условии)
    • Ширина для AREA
    • Всплывающее окно с ошибкой, если "клик" не дошёл до сервера
    • Можно получить логин и пароль, которые вводятся при авторизации, login() и pass()
    • Настройка размера/толщины текста и переноса для текстовых подписей TITLE, LABEL, LABEL_BLOCK. Подписи SPAN/BOLD/PLAIN больше не нужны
    • encodeDMY для GPdate (день.месяц.год)
    • Цвет GP_YELLOW_B
    • Настройка цвета для LED
    • RELOAD_CLICK работает с popup (ALERT, PROMPT, CONFIRM)
    • Проверка подключен ли клиент, функции online() и onlineTimeout()
    • У веб-лога добавлены кнопки для очистки и остановки прокрутки
    • Компонент RADIO для списков выбора
  • Пофикшено
    • Глаз в PASSWORD() поставлен на место
    • Баг с макросом M_TR10
    • SPINNER() повинуется выравниванию
    • Таблица SYSTEM_INFO вернулась в компактный вид
    • HINT (сломался в v3.4)
    • Невероятный баг с вечной загрузкой CHECK() и проблемой с опросом его значения с формы
  • Улучшено
    • Оптимизированы скрипты
    • Автоматическое удаление пробелов в списке UPDATE()
    • Ширину окна CAM_STREAM() и cam_stream_window можно задать стрингой
    • В ONLINE_CHECK() добавлена возможность отправлять свой js код при пропадании связи с платой
    • Дизайн таблички FILE_MANAGER + добавлена ширина вторым аргументом
    • Переделан механизм перезагрузки страницы, теперь работает гораздо быстрее и стабильнее
    • UPDATE запрашивает обновления только когда окно браузера активно
    • UPDATE при пропадании связи сигнализирует всплывающим баннером
    • Скрыты пустые ячейки у таблицы с заданными ширинами
  • Изменено
    • В SPINNER() вернулась ширина предпоследним аргументом, её установка отключает автоширину
    • ONLINE_CHECK() теперь выдаёт всплывающий баннер вместо иконки в названии страницы
    • Убран setReloadTimeout(), механизм улучшен, задаётся общий таймаут в setTimeout()
    • Дизайн LABEL_BLOCK, чтобы отличался от кнопок
    • Поле пароля с "глазом" теперь вызывается компонентом PASS_EYE

TODO v3.5

  • ОТА через cmd/shell curl -vF firmware=@firmware.bin http://x.x.x.х/GP_OTAupload

ROADMAP

Вы забыли релиз 3.4 сделать)

Или это специально?

я спать ушёл да

GP.UPDATE("lbl1, lbl2, lbl3, lbl4");

GP.LABEL("NAN", "lbl1");
GP.LABEL("NAN", "lbl2");
GP.LABEL("NAN", "lbl3");
GP.LABEL("NAN", "lbl4");

void action() {
if (portal.update()) {
String i = "Kek";
portal.updateString("lbl1", i); // Обновляется
portal.updateString("lbl2", i); // обновляется
portal.updateString("lbl3", i); // Нет обновления
portal.updateString("lbl4", i); // Нет обновления итд.
}
}

Обновляются значения только у первых двух компонентов, дальше обновления не происходят. На v3.3 и 3,4 проверял.

список id должен быть без пробелов. Но это действительно баг, т.к. у меня сделано удаление пробелов через js, но оно удаляет только один)) спасибо за репорт

В примерах из раздела projects стоит BUILD_BEGIN(); а с 3,4 он перестал компилироваться, надо заменить на GP.BUILD_BEGIN(); и GP.BUILD_END(); соответственно.

всё таки пропустил)

в wiki не прописаны

// подключить функцию которая вызывается при ошибке
    void attachError(void (*handler)(const String& UpdateError)) {
        _OTAerror = *handler;
    }
    
    // отключить функцию которая вызывается при ошибке
    void detachError() {
        _OTAerror = nullptr;
    }

нужно ли их вызывать, перед?
String error(); // вывести описание ошибки (например в label)

@DenysChuhlib поясни

@GyverLibs в новой версии этот код что-то не работает)

    GP.FILE_MANAGER(&LittleFS);  // передать ссылку на свою ф. систему (&LittleFS, &SPIFFS..) выводит список файлов из Flash памяти с кнопками для удаления (нужно настроить delete)
    GP.SYSTEM_INFO();            // выводит таблицу системной информации
    GP_VERSION;

на дисплее пусто

p.s. убрал GP.NAV_TABS_LINKS и все прогрузилось)

и как я вижу, для UI дизайн надо переделывать, иначе все разъезжается в ПК версии)
в мобильной все ок

@Serega88kos
GP_VERSION; это строка char*, см. документацию. Её нужно куда-то вывести чтобы увидеть

для UI дизайн надо переделывать

Да вроде не надо, там такая же центральная колонка. Я тестировал на своих скетчах, сразу выглядело норм

в wiki не прописаны

// подключить функцию которая вызывается при ошибке
    void attachError(void (*handler)(const String& UpdateError)) {
        _OTAerror = *handler;
    }
    
    // отключить функцию которая вызывается при ошибке
    void detachError() {
        _OTAerror = nullptr;
    }

нужно ли их вызывать, перед?
String error(); // вывести описание ошибки (например в label)

Можно когда угодно

@GyverLibs поправил предыдущее сообщение, не знаю, прочитал ли исправление...
В UI в ПК получается у меня так, надпись слева, поле ввода справа, между ними огромное пустое пространство)

M_BLOCK(GP_THIN, "",
            "Настройки WIFI",
            M_BOX(GP.LABEL("SSID"); GP.TEXT("ssid", "", w.ssid, "200px"); );
            M_BOX(GP.LABEL("PASS"); GP.PASS("pass", "", w.pass, "200px"); ););

переходить на таблицы?)

Можно когда угодно

как его вызывать тогда? что-то у меня не получилось...

надпись слева, поле ввода справа, между ними огромное пустое пространство

всё верно. У UI по умолчанию ширина 1000 пикс, у BOX ширина 100% и выравнивание по краям. Либо уменьшать ширину UI, либо уменьшать ширину BOX, либо сложить всё в таблицу и задать ширину её или UI

Можно когда угодно

как его вызывать тогда? что-то у меня не получилось...

Придумай, там просто подключил функцию, и через сериал выводишь, например

по задумке error можно вывести в LABEL прямо под кнопкой OTA обновления. Если будет ошибка - он появится. Если плата перезагрузится - и лейбл чист - ошибки нет

@GyverLibs файл темы с глазом смотрю подправил, но у меня что-то не вернулся на нужное место...

GP.BUILD_BEGIN(GP_LIGHT);
GP.UI_BEGIN("Меню", "", "");

заголовок меню в мобильной версии на черном фоне, так и задумано?

у меня что-то не вернулся на нужное место...

обнови браузер и почисть кеш. Я специально скачал всяких говнобраузеров и проверил, глаз везде работает корректкно

заголовок меню в мобильной версии на черном фоне

в мобильной светлой теме верхний бар чёрный

Скачал новый, Brave, глаз за пределами.
g34

ну тут мои полномочия всё)

по задумке error можно вывести в LABEL прямо под кнопкой OTA обновления.

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

ну тут мои полномочия всё)

margin-left:260px;margin-top:-5px;
мой старый вариант
g341
тут глаз плавает, в зависимости от ширины)

по задумке error можно вывести в LABEL прямо под кнопкой OTA обновления.

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

Что не принимает? Покажи тогда

тут глаз плавает, в зависимости от ширины

что это за браузер?

тут глаз плавает, в зависимости от ширины

что это за браузер?

brave.com
да и в любом другом...

SPINNER(): УБРАНА ШИРИНА, сделана автоширина, значение по центру

как теперь обходить этот момент. если спиннеров несколько и все разные значения, то уже не выходят все под одну ширину)

@MalfurionST у тебя тоже глазик барахлил, как сейчас дела?)

проблема явно где то у тебя. Вот хром/яндекс/brave/edge
image

не выходят все под одну ширину)

что нибудь придумаю
upd. Сделал

сделал проще макрос и решил с таблицей)

    M_TABLE(
            M_TR(
              GP.LABEL("SSID");
              GP.TEXT("ssid", "", w.ssid););
            M_TR(
              GP.LABEL("PASS");
              GP.PASS("pass", "", w.pass);););

вот в таком виде глаз идеально встает)
а было так

M_BLOCK(GP_THIN, "", "Настройки WIFI",
            M_BOX(GP.LABEL("SSID"); GP.TEXT("ssid", "", w.ssid, "150px"););
            M_BOX(GP.LABEL("PASS"); GP.PASS("pass", "", w.pass, "150px");););

g342

понятно, глаз убегает при использовании внутри THIN BLOCK. Но почему - непонятно, починим)

Просматривая файлы в глаз кинулся нюансик.

#define M_TR10(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) M_TR8(a1,a2,a3,a4,a5,a6,a7,a8,a9); GP.TD(); a10;

Может там должен быть M_TR9 а не 8?

Жесть это теперь мощный инструмент, у меня глаза розбегаються в bilder.h

Может там должен быть M_TR9

фига ты глазастый) сейчас пойду фиксить

@DenysChuhlib ты в canvas загляни ещё)

@DenysChuhlib ты в canvas загляни ещё)

Я видел, надо будет разобраться

@GyverLibs предложение, вывод системной информации сделать компактнее, не через строчку. Места больно много занимает)

а оно не через строчку... что то поломалось, строки таблицы раздулись. Перед загрузкой релиза всё было окей)))

залил текущие фиксы, можно забирать

В UI меню, если сделать длинное название ссылки (различные настройки), до получаются две строки и выравнивание посередине.
Глаз заработал.
Информация компактная.
Спиннер, ширина работает.

пофиксил

все ок.
такой вариант GP.LABEL(String(day + "/" + month + "/" + year)); через GP.SEND(); делать?)

GP.LABEL уже выводит текст на страницу...

ругается на оператор +
по отдельности выводит, но интервал между ними большой

ругается на оператор +

Уже в который раз отправляю читать про стринги https://alexgyver.ru/lessons/strings/

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

Баг на спиннере, когда меняешь значения, сбивается ширина.

У спиннера автоширина

с пк такого прикола нет, а с мобильного есть)

Блин не понял, покажи подробнее

Спин с фиксированной шириной на мобиле не меняет ширину при нажатии у меня

сейчас не могу повторить такое, возможно задал ширину после этого.
посмотри, случайно бросил взгляд на неактивный чекбокс.
если аккуратно или быстро или через уголок подводить курсор, то он не серый бывает, а слабо серый и мерцает.
это в css или так браузеры отрабатывают?
п.с. таблицы мощная штука, главное в запятых в макросах не запутаться)
п.с. с ссылкой кажется что-то определил, этот как-то кэшируется в браузере. сейчас в хроме словил, проверил в эдж нет такого, возвращаюсь в хром и принудительно перезагрузил страницу, ссылка исправилась.

После обновления всё стало кешироваться, после изменения стилей надо делать ctrl+f5

GP.HINT вроде перестал работать

ох блин точно, в одном месте ; лишняя...

а что случилось с GP.CHECK();?
включить можно, но выключить уже никак)
другие элементы при этом сохраняются после отправки формы.

а что случилось с GP.CHECK();?

добавил вариант от @MalfurionST. У меня работает корректно

вот это жесть! Оказывается существует баг - если делать svg через xml, как у нас сделано - нельзя накидывать анимацию transition! Браузер начинает вечно что то загружать и начинаются лаги. Обновил в репе версию пофикшенную....

не работает copyBool
работает getBool

странно, всё работает

bool v1 = 0, v2 = 0;
v1 = ui.getBool("ch");
ui.copyBool("ch", v2);

Serial.print(v1);
Serial.print(',');
Serial.println(v2);

// выводит одинаково 1,1 - 0,0 и так далее

copyBool(name, x) внутри это просто x = getBool(name)

BOOL.ino.txt
проблема повторилась)

GyverPortal.1.1_.Microsoft.Edge.2022-12-17.20-27-19.mp4

запись делал в чистом браузере, где не было сохранений)

опаааа, а вот эту проблему мы уже решали)) После изменения check она снова появилась, потому что я не глядя удалил кусок кода в старом CHECK, который это дело фиксил. Пасиба!

Добрый день. Библиотека 3.4.0. На андроиде не отображаются графики из примеров plotStock plotStatic plotAjax . На других платформах не пробовал. Подключаюсь к ESP-01 в режиме точки доступа. В конструкторе страниц добавлял компоненты выше и ниже графика - на экране смартфона между компонентами видно пустое пространство. Цвета темы и графика пробовал в разных комбинациях - графика не видно. Может графики не должны работать в смартфоне?

Второстепенный вопрос - есть ли простое решение организовать компонент типа RadioButton?

Если есп работает как точка доступа - нужно загрузить файлы скриптов графиков в память и поставить аргумент local у функции графика в единичку

Radio - а что именно интересует? Отображение нескольких лейблов (в столбик-строчку) и около каждого радио, которую можно выбрать только одну?

GyverLibs> загрузить файлы скриптов графиков в память
GyverLibs> и поставить аргумент local у функции графика в единичку
Я понимаю что Вы предлагаете, но я не знаю как это сделать. Пожалуйста покажите примером.

GyverLibs> а что именно интересует?
1
Нужен функциональный аналог SELECT, выбор одного из многих. Сейчас сделал из нескольких SWITCH, работает. Клацаем выключенный - любой включенный сам отключается. Если клацаем включенный, он выключается и затем сам включается. Старт процессов "сам отключается" или "сам включается" происходят не всегда моментально, а с очередным тиком ui.tick(). И это не напрягает. Напрягает то, что не работает два условия подряд - отслеживание клика и сразу же апдейта.

Это работает
void action() {
// был клик по компоненту
if (ui.click()) {
if (ui.click("sw1")) {valSw1=1; valSw2=0; valSw3=0;};
if (ui.click("sw2")) {valSw1=0; valSw2=1; valSw3=0;};
if (ui.click("sw3")) {valSw1=0; valSw2=0; valSw3=1;};
}

if (ui.update()) {
ui.updateBool("sw1",valSw1);
ui.updateBool("sw2",valSw2);
ui.updateBool("sw3",valSw3);
}
}

Это не работает
if (ui.click("sw1")) {ui.updateBool("sw1",1); ui.updateBool("sw2",0); ui.updateBool("sw3",0);}
if (ui.click("sw2")) {ui.updateBool("sw1",0); ui.updateBool("sw2",1); ui.updateBool("sw3",0);}
if (ui.click("sw3")) {ui.updateBool("sw1",0); ui.updateBool("sw2",0); ui.updateBool("sw3",1);}

Второстепенный вопрос - см скрин выше верхняя границы таблицы; граница двойная; это таблица с указанием размеров ячеек; все другие таблицы получаются с одинарной границей. Это не напрягает, но не перфекционизм.

Я понимаю что Вы предлагаете

В итоге ситуация какая? Есп не подключена к роутеру?

Старт процессов "сам отключается" или "сам включается" происходят не всегда моментально

Апдейт завязан на таймер или на клик?

отслеживание клика и сразу же апдейта

Да, это не работает, потому что это разные запросы от браузера

граница двойная

По умолчанию граница отключена, не нужно её включать 😁 при указании ширин компактным способом создаётся пустая строка в таблице. Можно указать ширины столбцов первой строки вручную, тогда этой пустой строки не будет

Я добавлю обычный классический radio в следующем обновлении

GyverLibs> В итоге ситуация какая?
Я не знаю как "загрузить файлы скриптов графиков в память и поставить аргумент local у функции графика в единичку".
Прошу пример или образец.

GyverLibs> Есп не подключена к роутеру?
Есп не подключена к роутеру и не планируется. Сейчас у меня связь Есп+смартфон и в этой связке мне нужен график.

GyverLibs> Апдейт завязан на таймер или на клик?
Сейчас апдейт работает когда завязан на таймер; это не очень хорошо, т.к. апдейт многократно вызывается, хотя событие обновления уже при первом (или втором вызове) сделано.

steelmak> отслеживание клика и сразу же апдейта
GyverLibs> Да, это не работает, потому что это разные запросы от браузера
Понял.

GyverLibs> тогда этой пустой строки не будет
Попробую.

GyverLibs> Я добавлю обычный классический radio в следующем обновлении
Прошу добавить компонент (числовой / текстовый), в котором можно менять РАЗМЕР ШРИФТА (и типовые цвета). Нужно для выделения на форме размером/цветом каких-то результирующих значений.

загрузить файлы скриптов графиков в память

Всё описано в документации

local у функции графика в единичку

Посмотреть в документации как выглядит полная функция нужного графика, последним аргументом там везде есть bool local. И указать её единичкой, например
GP.PLOT_STOCK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0, int height = 400, bool local = 0);
У обычного plot нет оффлайн режима, у остальных двух есть

Сейчас апдейт работает когда завязан на таймер

В такой ситуации лучше переделать на UPDATE_CLICK

РАЗМЕР ШРИФТА

Окей

Я благодарю Вас, что отвечаете.

GyverLibs> Всё описано в документации
Вашу документацию из Вики многократно читал и перечитываю. И спасибо что пишите!
Но тему загрузки файлов (скриптов) в память не нашёл.
Тема Загрузки из Вики

GP.PLOT_STOCK<к-во осей, к-во данных>(имя, подписи, массив времён, массив данных, int dec = 0, int height = 400, bool local = 0);
Попробую.

UPDATE_CLICK
Попробую.

Но тему загрузки файлов (скриптов) в память не нашёл

https://github.com/GyverLibs/GyverPortal/wiki/%D0%A4%D0%B0%D0%B9%D0%BB%D1%8B

А можно ли получить логин и пароль, которые вводятся при авторизации? При использовании enableAuth(x,y), что бы с ними дальше работать. Например если вести дополнительный секретный пароль, то он отключит авторизацию на страницу.

получить логин и пароль, которые вводятся при авторизации

добавлю в следующем обновлении

Добавте пожалуйста в параметры GP.PASS, возможность отключить глаз

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

Всё верно, в "ночном" билде случайно удалена одна буква и скрипты не работают. А так как они теперь кешируются - кеш надо чистить

@GyverLibs идея по HINT, т.к. с мобильного его не видно, можно сделать как на сайтах, знак вопроса справа, в виде иконки, при нажатии на который всплывает окно с текстом подсказки)
IMG_20221224_100931
Только думаю не надо привязывать к мобильному интерфейсу, думаю и для ПК выйдет красиво им понятно, что тут есть справка)

Предлагаю добавить в BUTTON и BUTTON_MINI режим update.
Чтобы можно было динамически управлять кнопками.
Пример приводил на форуме
https://community.alexgyver.ru/threads/gyverportal.6632/post-134673

Управлять кнопками это как?

Например управлять доступностью кнопок, текстом на кнопке.
GP.BUTTON(имя, текст, id, цвет, ширина, откл, перезагр);
выше давал ссылку на пример, который я пытался реализовать

В моей текущей реализации апдейт передаёт только значение компонента. Всё остальное можно сделать через reload

но reload перезагрузит страницу, а хотелось именно динамическое обновление

В текущей реализации не предусмотрено) перезагрузка страницы - абсолютно нормальное дело, в текущей версии когда все закешировано - происходит мгновенно

Проблема с копированием фрагментов строки на кириллице (на английском всё норм).
// переменные
String sSystem, // общая строка
sModel, sZavNum, sDataIzg, sPoverka; // фрагменты

// где-то в коде приняли строку на 32 символа и разбираем
sSystem = "qwertyui12.45.78iuytrewq87.54.21"; // на англ нормально
//sSystem = "модель11завном2233.44.5566.77.88"; // на русс трабля
sModel = sSystem.substring(0,8);
sZavNum = sSystem.substring(8,16);
sDataIzg = sSystem.substring(16,24);
sPoverka = sSystem.substring(24,32);

Режим точки доступа. Скрины со смартфона.

1
2

строки на кириллице

Господи ну ещё бы, символ кириллицы занимает 2-3 байта вместо одного. Вот так брать и делать substring по фиксированной позиции - работать не будет

Да, про кодировку кириллицы почитал.
Есть мысль принимать строку в массив char[] или byte[].
Далее перебираем массив и если байт равен 208 или 209 (признак кириллицы в UTF-8)
то "склеивать" два байта в символ (string) кириллицы
if (byte[i]==208 || byte[i]==208 ) s = byte[i]+byte[i+1];
Правда не знаю как это написать, чтобы скомпилировалось и работало.
Пожалуйста, подскажите как из массива char[] или byte[] конвертировать в string ?
Этот подход мог быть как идея для хитрожопой функции String Rus_UTF8(String);

Сейчас обнаружил другую проблему.
И она более важная чем кириллица.
Обновление (update) лейбла перестаёт работать,
после 4...8 приёма одиночного (и неодиночного символа).
Пробовал цифры и латиницу. Пробовал кеш чистить. Пробовал другой модуль ESP.
Обновление страницы тоже не помогает.
Помогает только аппаратный reset - но это шляпа, как и ручное обновление страницы.
Монитор порта показывает что символ ушёл и принят, т.к. эхо работает.
Александр - вся надежда на Вас.

String slb;
//
GP.UPDATE("lb");
GP.LABEL("Label: ");
GP.LABEL("NAN","lb");
//
slb = Serial.readString();
Serial.println(slb);
//
if (ui.update("lb")) ui.answer(slb);

Думал может slb "чистится" или "портится" после Serial.println(slb);
Пробовал через дополнительную переменную - не помогает.

Говоря короче - мне нужен компонент, который можно апдейтить, но нельзя кликнуть и менять руками.
Завтра попробую принять в порт и проапдейтить другие компоненты, которые можно и руками менять.

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

компонент, который можно апдейтить, но нельзя кликнуть и менять руками

Любой компонент можно "отключить", передав false последним аргументом

Обновление (update) лейбла перестаёт работать, после 4...8 приёма одиночного (и неодиночного символа).

Значит скетч написан неправильно. Вот пример с выводом на лейбл данных из сериал, ничего не ломается

#define AP_SSID ""
#define AP_PASS ""

#include <GyverPortal.h>
GyverPortal ui;

String lbl;

void build() {
  GP.BUILD_BEGIN(GP_DARK);

  GP.UPDATE("lbl");
  GP.LABEL("Label: ");
  GP.LABEL("NAN", "lbl");
  
  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();
  Serial.setTimeout(10);
}

void action() {
  if (ui.update("lbl")) ui.answer(lbl);
}

void loop() {
  ui.tick();
  if (Serial.available()) {
    lbl = Serial.readString();
  }
}

Беру из примеров апдейт if (ui.update("txt")) ui.answer(random(60));
Работает.
Как только из монитора в ESP отправляю что-то - процесс апдейта останавливается.

И к слову. Из компонентов принимающих string только три - TITLE, LABEL и TEXT.
TITLE использовать для оформления не красиво, он как заголовок.
Неактивный TEXT мелкий и блёклый.
Остаётся приличный LABEL.

Спасибо.
Оказывается надо Serial.setTimeout(10);

Оказывается надо Serial.setTimeout(10);

это не то чтобы надо, просто ускоряет парсинг. Я так понимаю readString вызывался прямо в билдере или апдейте? Это критически неправильно

Из компонентов принимающих string только три - TITLE, LABEL и TEXT.

Документация с вами не согласна
image

Александр - ещё раз спасибо, Вы маг и чародей, работает.
Подскажите - есть ли событие или функция для отслеживания активности нашей веб формы?
Что я вижу сейчас в режиме точки доступа без пароля (далее ТД)?
Подали питание. Появилась ТД.
В этот момент можем проверять соединение с ТД в цикле while (WiFi.status() != WL_CONNECTED) {}
Подключаемся к ТД и входим в цикл void loop() {}.
Как только пользователь в браузере загрузит страницу (по адресу 192.168.4.1)
начинает вызываться void action() {}.

Что мне нужно?
Я бы хотел каким-то образом понимать, что у пользователя в браузере
открыта (активна) вкладка с нашей формой (по адресу 192.168.4.1).
Когда пользователь переключается между вкладками,
сворачивает браузер или гасит экран смартфона
вызов void action() {} продолжает работать. Это неплохо, но...
хотелось бы отслеживать эти события (или событие условной неактивности формы).
Для чего это надо? Чтобы в автомате прекращать запросы-ответы с портом Serial.

Примечание. Предполагаю, что в какой-то момент при удалении от esp
произойдёт разрыв WiFi и можно использовать (WiFi.status() != WL_CONNECTED).
Но в целом это неподходящий сценарий работы, т.к. при приближении связь
без нашего участия может восстановиться; даже с погашенным экраном смартфона.
Делать в интерфейсе кнопку "вкл-откл запросы-ответы Serial" - это несовременно.
Отслеживать клики на форме и использовать таймаут - тоже не торт.

В этот момент можем проверять соединение с ТД в цикле while (WiFi.status() != WL_CONNECTED) {}

Ох мамочки, всё перепутано :) конструкция (WiFi.status() != WL_CONNECTED) {} используется для проверки подключения к роутеру, а к режиму точки доступа отношения не имеет.

Как только пользователь в браузере загрузит страницу (по адресу 192.168.4.1)
начинает вызываться void action() {}.

Нет, action вызывается при действиях пользователя на странице

у пользователя в браузере
открыта (активна) вкладка с нашей формой

Я погуглю, но скорее всего это невозможно определить средствами JavaScript. Также если если открыть портал с двух устройств - уже непонятно за кем следить.

Чтобы в автомате прекращать запросы-ответы с портом Serial.

Зачем?

при удалении от esp
произойдёт разрыв WiFi и можно использовать (WiFi.status() != WL_CONNECTED)

Опять же, это относится к режиму STA. В режиме AP можно использовать WiFi.softAPgetStationNum(), которая вернёт количество подключенных к ТД устройств

> Ох мамочки, всё перепутано :)
Признаюсь, я сразу удалил и не пробовал работу конструкции (WiFi.status() != WL_CONNECTED) {}
в режиме точки доступа.

> Нет, action вызывается при действиях пользователя на странице
У меня в порт начинают уходить данные до действий на странице, т.е. с момента как загрузилась страница (открыли страницу) в браузере 192.168.4.1 . Для теста отправку в порт я сделал из void action() {}.

начинают уходить данные до действий на странице

Настроенный update также является действием, браузер отправляет запросы на есп.

Конструкция выглядит странно. Я не знаю деталей, но возможно тут хватило бы есп32 вместо МК + есп

Александр, если не надоел своими вопросами, предлагаю на повестке оставить вопрос

как определить, открыта (активна) вкладка с нашей формой
Я погуглю, но скорее всего это невозможно определить средствами JavaScript.

В js есть флаг document.hidden, я могу просто добавить его в механизм update и запросы не будут приходить когда окно не активно

проверил - работает как нужно, залил в репозиторий. Если в браузере открыта другая вкладка или браузер сёрнут - обновления не приходят