Отчёт по изменениям — 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.ts—navigator.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).
Корни проблемы (накопители):
- Повторный
WiFi.mode(WIFI_AP_STA)вstaConnect()на КАЖДЫЙ retry. ESP32 вWIFI_AP_STAзаставляет SoftAP следовать за STA-каналом. При scan'е STA каналы прыгают → SoftAP теряет трансляцию. Фикс:WiFi.mode()ставится один раз вwifiInit(), вstaConnect()не повторяется. WiFi.disconnect(true, false)при retryCount=5 выключал ВЕСЬ WiFi-стек (wifioff=true), включая AP. Заменено наdisconnect(false, false).net_manager.onDisconnectedдёргалstaConnect()НЕМЕДЛЕННО при потере STA. Это запускало плотный retry-цикл, AP постоянно следовал за STA-сканом. Фикс: то же расписание backoff что уonConnectFailed.- Экспоненциальный backoff в persistent retry: 30с → 60с → 2мин
→ 5мин → 10мин (cap). Сбрасывается при успешном
GOT_IP. Без этого AP не получал «тишины» между STA-attempts. netRequestSTAв STA_FAILED игнорировал backoff — bot/imap/smtp polling'и каждый цикл сбрасывали таймер. Фикс: в STA_FAILED проверяемs_persistRetryAtMs; если backoff не истёк, отвечаем consumer'уFAILEDсразу безstaConnect().CONNECT_TIMEOUT30с → 10с — окно scan'а в 3 раза короче при недоступной BS.- Явный
WiFi.disconnect(false, false)после CONNECT_FAILED — останавливает internal scan ESP32, освобождает радио для AP. - 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_KEYenv (parity сdeploy-firmware.sh):IdentitiesOnly=yes,BatchMode=yes,StrictHostKeyChecking=accept-new.platformio.iniextra_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.1max-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 envesp32dev. Для боевой сборки —esp32dev-prod.
Backlog / следующие задачи (по приоритету)¶
- EEPROM error propagation —
eepromRead/Writeвозвращают void, silent fail. ПоднятьalarmRaise(EEPROM_FAIL)при I²C-ошибках (из ревью). - Pump fractional seconds — хранить periods вместо seconds
(
pumpFrac[ch] = 0вeeprom_counters.cpp:309теряет до 1 сек на сессию). /api/system/capabilitiesendpoint — UI прячет недоступные интеграции по compile-time флагам.- OTA strict semver parser + downgrade guard в ManifestValidator.
- Service mode для дозаторов — ручное управление, тест входов/выходов.
- Local HTTPS CA (отложено) — когда соберутся пользователи на полноценный PWA для local-сценария.
- Web UI refactor —
main.ts2900 строк → разнести по pages/components. - 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)