Перейти к содержанию

Отчёт по изменениям — 2026-05-25 … 2026-05-26

Продолжение changes-2026-05-22.md. Сюда вошли 12 коммитов в dev-0.13 после релиза v0.13.1: реализация PWA, крупная WiFi-стабилизация и автоматизация деплоя max-webapp. Последний коммит — 8e35274.


1. PWA — DOSEmax установлен на телефоне

Коммиты: 83f8a75, 9dbc21c, 8e35274

Что было: PWA только в планах (ADR-011), ярлык не ставился — манифеста на сервере не было, в прошивке маршрутов тоже.

Что стало: две устанавливаемые PWA с разной цветовой кодировкой, оператор различает по иконке на главном экране телефона.

Иконка URL Имя Назначение
🟡 жёлтая (amber #FBBF24) https://awdc.nikolaev.world/ Oper DOSEmax управление парком через MAX-бота
🟢 зелёная (#16A34A) http://awdc.local/ Admin DOSEmax админ одного контроллера в зоне Wi-Fi

Состав реализации:

  • tools/gen_pwa_icons.py — генератор maskable-иконок 192/512 из web-ui/public/logo.png. Каждый таргет получает свой фон-цвет с 10%-safe-zone (Android cropping под форму устройства).
  • {web-ui,max-webapp}/public/manifest.json — name/short_name, display=standalone, theme_color, иконки maskable.
  • {web-ui,max-webapp}/public/sw.js — минимальный passthrough service worker. Chrome требует SW для appearance кнопки «Установить» — без него только bookmark.
  • src/static_handlers.cpp — новые маршруты /manifest.json, /icons/icon-{192,512}.png, /sw.js (gzipped в PROGMEM через tools/build_ui.py).
  • {web-ui,max-webapp}/src/main.tsnavigator.serviceWorker.register('/sw.js') на window.load с silent-catch (на http:// secure context нет, registration fail-silent — это OK для local-сценария).
  • {web-ui,max-webapp}/index.html<link rel="manifest"> + <meta name="theme-color"> синхронен с фоном иконки.

Замечание про DOSEmax Local: Chrome требует HTTPS или localhost для полноценной PWA-install. http://awdc.local/ не secure → Chrome показывает только «Добавить на главный экран» (ярлык-bookmark, не PWA). Отдельная задача tasks/local-https-ca.md описывает локальный CA-подход для HTTPS на ESP32. Сейчас в backlog без приоритета.

Прошивка: Flash 70.8% → 74.0% (+~60 КБ — manifest + 2 PNG + sw.js gzipped в PROGMEM).

Howto: setup-pwa.md.


2. WiFi-стабилизация: AP не отваливается при потере BS

Семь коммитов: 7cad315, db6e715, 3f1423f, dc0955e, 6bb8faf, плюс 9dbc21c/83f8a75 (PWA — параллельно).

Симптом: контроллер сконфигурирован с persistent STA. Базовая станция (роутер) выключается. Через секунды-минуты SoftAP контроллера тоже пропадает из эфира — клиенты теряют соединение, контроллер становится недоступным. После восстановления BS — STA подключается, но DNS не работает (hostByName: DNS Failed).

Корни проблемы (накопители):

  1. Повторный WiFi.mode(WIFI_AP_STA) в staConnect() на КАЖДЫЙ retry. ESP32 в WIFI_AP_STA заставляет SoftAP следовать за STA-каналом. При scan'е STA каналы прыгают → SoftAP теряет трансляцию. Фикс: WiFi.mode() ставится один раз в wifiInit(), в staConnect() не повторяется.
  2. WiFi.disconnect(true, false) при retryCount=5 выключал ВЕСЬ WiFi-стек (wifioff=true), включая AP. Заменено на disconnect(false, false).
  3. net_manager.onDisconnected дёргал staConnect() НЕМЕДЛЕННО при потере STA. Это запускало плотный retry-цикл, AP постоянно следовал за STA-сканом. Фикс: то же расписание backoff что у onConnectFailed.
  4. Экспоненциальный backoff в persistent retry: 30с → 60с → 2мин → 5мин → 10мин (cap). Сбрасывается при успешном GOT_IP. Без этого AP не получал «тишины» между STA-attempts.
  5. netRequestSTA в STA_FAILED игнорировал backoff — bot/imap/smtp polling'и каждый цикл сбрасывали таймер. Фикс: в STA_FAILED проверяем s_persistRetryAtMs; если backoff не истёк, отвечаем consumer'у FAILED сразу без staConnect().
  6. CONNECT_TIMEOUT 30с → 10с — окно scan'а в 3 раза короче при недоступной BS.
  7. Явный WiFi.disconnect(false, false) после CONNECT_FAILED — останавливает internal scan ESP32, освобождает радио для AP.
  8. DNS fallback через lwIP dns_setserver() если DHCP не пушит DNS на повторно поднятый интерфейс (известная ESP32-баг). Лог post-GOT_IP: gw=... dns0=... dns1=... для диагностики.

Результат: на стенде проверено end-to-end: - BS off → AP остаётся доступным всё время downtime. - BS on → STA подключается на следующем backoff-тике, DNS работает. - Можно ускорить через UI «Подключить» в STA-вкладке (вызывает staConnect() напрямую через API).

Ревёрты по дороге: одна промежуточная правка (db6e715) — пытался WiFi.disconnect(false, false) ПЕРЕД WiFi.begin() для очистки lwIP state, но на cold-boot это ломало первое подключение (STA «connected» получал IP но L2 не работал, router отвечал «destination unreachable»). Откатил.


3. Auto-deploy max-webapp через pio run

Коммиты: 90e2a4c, 31a67ad, 88f57cd

Что было: firmware деплоится автоматически после pio run через post-build hook (tools/deploy_firmware_hook.py). Для max-webapp нужно было руками cd max-webapp && bash scripts/deploy.sh.

Что стало:

  • tools/deploy_max_webapp_hook.py — миррор deploy_firmware_hook.py. Opt-in через max-webapp/deploy.env (без файла — silent no-op). Найдёт git-bash на Windows тем же путём (стандартные пути + git --exec-path).
  • max-webapp/scripts/deploy.sh — поддержка SSH_KEY env (parity с deploy-firmware.sh): IdentitiesOnly=yes, BatchMode=yes, StrictHostKeyChecking=accept-new.
  • platformio.ini extra_scripts: добавлен post:tools/deploy_max_webapp_hook.py после deploy_firmware_hook.

Теперь pio run = build firmware → export artifacts → deploy firmware → deploy max-webapp → done. Оба деплоя независимо опт-ин, каждый со своим SSH_KEY.

Также — фикс кодировки логов: ранее на Windows PIO-thread падал с UnicodeEncodeError на любых non-ASCII символах (, , кириллица) в выводе наших скриптов — billed как «вис на 30 минут». Все stdout deploy-скриптов переведены на ASCII English, hooks принудительно кодят subprocess-output через errors='replace' (даже если sys.stdout.encoding показывает UTF-8). Комменты в коде остались на русском (не печатаются).

Плюс реальный фикс exposed логом: deploy-firmware.sh теперь делает mkdir -p $DEPLOY_PATH через ssh ДО scp. Без этого первый деплой на новый сервер падал с «dest open ... No such file or directory».


4. Документация — отложенные задачи

Коммит: 31cfe58

  • project-context/tasks/local-https-ca.md — отложенная задача с status: deferred. Локальный CA для парка → HTTPS на ESP32 → полноценный PWA для DOSEmax Local (вместо bookmark). Документирован архитектурный подход, поэтапный план (~10 часов работы + ~60 КБ Flash), альтернативы (self-signed / Let's Encrypt — отвергнуты с обоснованием) и триггеры для возврата к задаче (≥5 жалоб, self-host без cloud).
  • Со-ссылки в ADR-011 и doc/user/install-pwa.md.

Текущее состояние проекта (snapshot 2026-05-26)

Версии

  • FW_VERSION = "0.13.1"include/config.h)
  • MIN_UPGRADABLE_FROM = "0.12.0"
  • web-ui/package.json = 0.13.1
  • max-webapp/package.json = 0.1.0 (отдельная dev-версия)
  • Тег v0.13.1 на коммите 0d84fe9
  • HEAD: 8e35274 на dev-0.13

Ресурсы

  • Flash: 74.0% (1 358 КБ из 1 835)
  • RAM: 19.9% (65.4 КБ из 327.7)
  • Запас Flash: ~480 КБ — комфортно для дальнейших фич.

Что работает (stable / verified)

  • AP/STA WiFi + AP-stability при потере BS
  • OTA upload (file system) + OTA pull-mode (server) + auto-deploy
  • net_manager, BootValidation, rollback
  • Watchdog, NTP, alarms, metrics, NVS backup
  • EEPROM counters (runtime + pump), LEV detection с AC-edge ISR
  • Dosator (proportional + pulsed), HMI TM1650
  • Web UI (Vite + Alpine + Tabler)
  • Core dump
  • MaxBot board chat + MAX Mini App (L2-lite)
  • PWA Oper DOSEmax (cloud, install through Chrome)
  • PWA Admin DOSEmax (local, через ярлык-bookmark — HTTPS отложен)

Known limitations

  • TLS использует setInsecure() — cert verification не реализован.
  • IMAP polling, не IDLE.
  • DOSEmax Local — только bookmark, не полноценный PWA (HTTPS на ESP32 — отдельная отложенная задача).
  • Pre-production флаги (AWDC_RUN_SIGNAL_TEST_GENERATOR=1) пока в default env esp32dev. Для боевой сборки — esp32dev-prod.

Backlog / следующие задачи (по приоритету)

  1. EEPROM error propagationeepromRead/Write возвращают void, silent fail. Поднять alarmRaise(EEPROM_FAIL) при I²C-ошибках (из ревью).
  2. Pump fractional seconds — хранить periods вместо seconds (pumpFrac[ch] = 0 в eeprom_counters.cpp:309 теряет до 1 сек на сессию).
  3. /api/system/capabilities endpoint — UI прячет недоступные интеграции по compile-time флагам.
  4. OTA strict semver parser + downgrade guard в ManifestValidator.
  5. Service mode для дозаторов — ручное управление, тест входов/выходов.
  6. Local HTTPS CA (отложено) — когда соберутся пользователи на полноценный PWA для local-сценария.
  7. Web UI refactormain.ts 2900 строк → разнести по pages/components.
  8. Unified JsonBodyHandler в API.

Список коммитов

8e35274  feat(pwa): two-tone icons — Oper (yellow) cloud + Admin (green) local
31cfe58  docs(pwa): отложенная задача — HTTPS на ESP32 через локальный CA
88f57cd  chore(deploy): all stdout switched to ASCII English
31a67ad  feat(deploy): pio run теперь деплоит и max-webapp параллельно firmware
90e2a4c  feat(max-webapp): deploy.sh поддерживает SSH_KEY env
6bb8faf  fix(wifi): короче CONNECT_TIMEOUT + явный disconnect после fail
dc0955e  fix(wifi): netRequestSTA в STA_FAILED уважает backoff timer
3f1423f  fix(wifi): backoff в onDisconnected — AP не отваливается при потере STA
db6e715  fix(wifi): revert disconnect-before-begin + setAutoReconnect(false)
7cad315  fix(wifi): AP пропадает при STA-fail + DNS не работает после reconnect
9dbc21c  feat(pwa): минимальный service worker для Chrome installability
83f8a75  feat(pwa): DOSEmax — cloud + local PWA (manifest + icons)