76 Commits
Author SHA1 Message Date
oskarvitaliiandClaude Opus 5 fec4c9be1a Merge dev into master: документация приведена в соответствие с кодом
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:10:49 +07:00
oskarvitaliiandClaude Opus 5 f25f24b6c5 docs: привести README в соответствие с реализацией
Сверка документации с кодом выявила четыре расхождения, все появились
после вчерашней правки протокола:

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

Все 16 констант из таблицы настроек сверены с config.h, поведенческие
утверждения - с кодом, расход прошивки подтверждён сборкой: 26% и 4%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:08:49 +07:00
oskarvitaliiandClaude Opus 5 2fd0a44555 Merge dev into master: экран памяти, кнопка BOOT и планы по видеокарте
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:58:01 +07:00
oskarvitaliiandClaude Opus 5 f18090b5b7 feat(ui): экран оперативной памяти и переключение кнопкой BOOT
Второй экран показывает загрузку ОЗУ крупной цифрой и её график со
шкалой 0-100 - у памяти она естественная, подбирать нечего. В углу свой
значок, планка памяти вместо микросхемы: он же и говорит, какой экран
открыт.

Тревога в приоритете: при перегреве плата принудительно возвращается на
экран процессора и кнопка не работает, пока температура не упадёт. Смысл
тревоги в том, что её нельзя прощёлкать.

Экран памяти выпадает из листания, если агент не шлёт ключ M - со старым
агентом кнопка просто ничего не делает. То же решение подойдёт машинам
без дискретной видеокарты.

Выбранный экран не запоминается при перезагрузке: хранить негде.
Тревоги по памяти нет намеренно - загрузка ОЗУ под 100% здесь обычное
рабочее состояние.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:56:33 +07:00
oskarvitaliiandClaude Opus 5 4344fa8873 feat(agent): слать загрузку оперативной памяти ключом M
Прошивка её пока не показывает - на 72x40 нет места под третий
показатель. Это намеренно: неизвестный ключ прошивка игнорирует, поэтому
агента можно обновлять независимо от плат.

Память берётся у железа с именем Total Memory - это физическая память.
У соседнего Virtual Memory датчик называется точно так же, но считает
вместе с файлом подкачки и показывает заниженную цифру: 87% против
фактических 97%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:04:11 +07:00
oskarvitaliiandClaude Opus 5 b15421305c docs: записать в бэклог план по видеокарте
Выбран приоритетный экран, а не листалка: устройство смотрится боковым
зрением и должно само показывать то, что ближе к своему порогу.
Совмещённый экран отвергнут по измерениям - вторая температура влезает
только шрифтом 5x7, и смысл крупной цифры теряется.

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

Записана и главная ловушка подхода: при близких к порогам показателях
экран начнёт скакать между ними, нужна задержка переключения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 01:08:11 +07:00
oskarvitaliiandClaude Opus 5 ed726f7fb6 docs: внести в бэклог перечень доступных датчиков
Проверено перебором через LibreHardwareMonitorLib после установки PawnIO.
Ключевой вывод: температура видеокарты NVIDIA читается без прав
администратора, потому что идёт через её драйвер, а не через MSR. Админ
нужен только процессору.

Дисков и датчиков материнской платы с вентиляторами на этой машине нет.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:50:15 +07:00
oskarvitaliiandClaude Opus 5 96df0465d5 Merge dev into master: сглаживание температуры и бэклог
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:42:59 +07:00
oskarvitaliiandClaude Opus 5 fcc96f5090 feat(ui): сглаживать температуру скользящим средним за 5 замеров
Мгновенное значение плавает на +-1 градус, и цифра мельтешила каждую
секунду при полном покое. На экран теперь идёт среднее за
TEMP_AVG_SAMPLES последних замеров; агент шлёт раз в секунду, так что
это ровно 5 секунд.

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

Тревога переведена на усреднённое значение: иначе экран показывал бы 84,
когда мигание уже идёт. Загрузка и график намеренно остались сырыми.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:41:20 +07:00
oskarvitaliiandClaude Opus 5 1047ae1452 docs: завести бэклог проекта
Первый пункт - другие показатели (GPU, память, диски) и листалка экранов
кнопкой BOOT. Протокол к этому уже готов: незнакомые ключи прошивка
игнорирует, так что агент может начать слать новые поля, не ломая уже
прошитые платы.

Там же записаны открытые хвосты: обрезание строки при 100 градусах и
100% загрузки, непроверенный перезагрузкой автозапуск, ручной цикл
заливки и вес обзорного изображения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:33:02 +07:00
oskarvitaliiandClaude Opus 5 a006c45d98 Merge dev into master: значок процессора и обзорное изображение
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:29:48 +07:00
oskarvitaliiandClaude Opus 5 135278ba18 docs: добавить обзорное изображение проекта
К изображению дана сноска с двумя поправками: экран на плате 0.42 дюйма,
а не 0.96, и график строится по температуре, а не по загрузке.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:29:20 +07:00
oskarvitaliiandClaude Opus 5 c76c2ef7e5 feat(ui): значок процессора в правом верхнем углу
Полоса y=0..10 справа выбрана по измерениям на самой плате, а не на глаз:
строка загрузки лежит ниже (y=11..24), а цифры температуры не дотягиваются
туда даже трёхзначными (49 px + градус = 56 из 72). Значок 11x11 не
перекрывает ничего ни при каких значениях и не исчезает под нагрузкой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:29:19 +07:00
oskarvitaliiandClaude Opus 5 6c4cdb4605 Merge dev into master: индикатор температуры CPU на ESP32-C3
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:04:34 +07:00
oskarvitaliiandClaude Opus 5 e366f844a0 docs: add CPU_temp to the projects index
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:47:26 +07:00
oskarvitaliiandClaude Opus 5 03db58473c docs: переписать README как пошаговую инструкцию по повторению
Прежний README описывал устройство проекта, но по нему нельзя было
собрать всё с нуля. Теперь это инструкция: что установить, какие пункты
меню выставить, как определить пины дисплея, как поставить агента и
автозапуск, как проверить без агента.

Отдельно вынесены грабли, на которые мы наступили: USB CDC On Boot по
умолчанию выключен, LibreHardwareMonitor требует PawnIO, разделитель
дробной части должен быть точкой, .ps1 нужен UTF-8 с BOM, порт надо
освобождать перед заливкой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:40:25 +07:00
oskarvitaliiandClaude Opus 5 bd0a87ea49 feat(ui): тревога по перегреву и config.h со всеми настройками
Настройки собраны в config.h: пины, ориентация, пороги тревоги, шкала
графика, тайминги и скорость Serial. Раньше они были раскиданы по
модулям, и подстройка требовала лазить по коду.

Тревога включается на ALARM_ON_C и отпускает только на ALARM_OFF_C
(85 и 82 градуса). Один порог заставлял бы её дёргаться каждую секунду,
пока температура топчется на границе.

В тревоге экран мигает негативом (инверсия буфера, а не команда
контроллера - не зависит от микросхемы дисплея), светодиод GPIO8 мигает
с ним в общей фазе. Без данных светодиод горит ровно, в норме погашен.
Зажигается низким уровнем: проверено опытом, заодно в простое пин стоит
высоким, что безопасно для GPIO8 как пина начальной загрузки.

Переменная alarm переименована в alarmActive: имя alarm занято функцией
из unistd.h, которая попадает в сборку ESP32.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:32:31 +07:00
oskarvitaliiandClaude Opus 5 db86597433 feat(ui): повернуть экран на 180 градусов и укрупнить загрузку
Ориентация вынесена в константу DISPLAY_ROTATION: менять её теперь
правкой одной строки, а не параметра в конструкторе.

Знак градуса поднят с базовой линии 10 на 8 - встал надстрочным знаком
вровень с верхом цифр. Загрузка CPU выводится шрифтом 7x14 вместо 5x7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:08:34 +07:00
oskarvitaliiandClaude Opus 5 59848698ff feat(cpu_temp): индикатор температуры CPU на ESP32-C3 с OLED
Плата принимает по USB-CDC строки "T=67.0;L=34" от агента на ПК и рисует
крупную цифру, загрузку и график за последнюю минуту на экране 72x40.

Пины дисплея (SDA=5, SCL=6) определены сканером, а не по шелкографии:
подписи GPIO8/GPIO9 на плате - это дефолтные Wire-пины чипа.

Агент читает сенсоры через LibreHardwareMonitorLib. С версии 0.9.5 она
требует отдельно установленный драйвер PawnIO: без него загрузка CPU
читается, а температуры пусты даже под администратором.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 21:42:24 +07:00
oskarvitalii ac8a72b05d Merge dev into master: снимок веб-панели в документации 2026-08-08 09:32:58 +07:00
oskarvitaliiandClaude Opus 5 ed4dad0a52 docs: добавить снимок веб-панели
Снимок с работающего прибора: плашка «Подача есть», лампы датчиков и
выходов, раскрытый блок настроек. Вставлен в README (раздел 7) и в
MANUAL (раздел 5).

На снимке нет чувствительных данных: адрес уведомлений показан только
именем хоста и это заглушка, IP приватный.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 09:30:25 +07:00
oskarvitalii 7a7075f6de Merge dev into master: исправлено описание поведения реле при загрузке
Документация утверждала, что реле при загрузке молчат. На собранном
приборе оба реле кратковременно моргают при перезагрузке; поведение
признано некритичным, прошивка не менялась, исправлен текст.
2026-08-08 01:55:23 +07:00
oskarvitaliiandClaude Opus 5 fde9ee799a docs: исправить утверждение о поведении реле при загрузке
На собранном приборе оба реле кратковременно моргают при перезагрузке.
README и MANUAL утверждали обратное - что реле при загрузке молчат.
Это было предсказание из рассуждения об уровнях на пинах, а не
наблюдение, и оно не подтвердилось. Формулировки исправлены.

Вероятный механизм описан честно, как вероятный: оба выхода сидят на
служебных пинах (GPIO2 это UART1 TX, GPIO0 - пин выбора режима загрузки),
и пока setup() не выполнился, уровнем управляет не прошивка. Осциллографом
не проверяли, о чём в тексте сказано.

Само решение остаётся верным: альтернативный D8 подтянут к LOW и держал бы
реле уверенно включённым всю загрузку вместо короткого моргания.

Пользователь признал поведение некритичным, прошивка не менялась. В
документации указано, чем лечится, если однажды станет мешать.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 01:51:30 +07:00
oskarvitalii f77ff376d4 Merge dev into master: переработанный интерфейс и текст события в webhook
Веб-панель переделана в световое табло: главный ответ читается с
расстояния, цвета по конвенции сигнальных ламп, настройки свёрнуты.
Исправлено: лампа выхода «Авария» горела зелёным при срабатывании,
обновление стирало ввод в форме, шрифты тянулись с Google Fonts и на
объекте не загружались.

Страница переехала во флеш (PROGMEM) и в отдельный файл web_page.h -
последнее вынужденно: генератор прототипов Arduino не понимает сырые
строковые литералы и ломался на JavaScript внутри.

OLED: верхняя строка отдана статусу кеглем 2, добавлено состояние реле.

В теле webhook поле status теперь содержит человеческую фразу
(«Насосы стоят, подачу держит резерв»), машиночитаемое значение
переехало в новое поле state.

Прошито и проверено на приборе: COM7, LOLIN D1 ESP-WROOM-02, 2MB FS:64KB.
2026-08-08 01:40:10 +07:00
oskarvitaliiandClaude Opus 5 0c06ba1032 feat(webhook): слать в status человеческую фразу вместо кода
В теле уведомления status теперь содержит ту же формулировку, что видит
оператор в панели: «Насосы стоят, подачу держит резерв». Прежние
машиночитаемые значения переехали в новое поле state (ALARM / OK /
STARTED / STOPPED), чтобы сценарий на сервере ветвился по нему, а не
разбирал русский текст.

ЛОМАЮЩЕЕ ИЗМЕНЕНИЕ: если сценарий в n8n сравнивал status с "ALARM",
условие надо переключить на state.

Формулировки собраны в одном месте прошивки (statusKind / statusWord /
statusSub / statusPhrase) и оттуда попадают и в webhook, и в панель через
новые поля k, w, s в /status. Раньше та же логика была продублирована в
JS панели; два набора фраз разошлись бы при первой правке. Побочно JS
сократился почти вдвое.

Прошито и проверено на приборе: /status отдаёт
{"k":"ok","w":"Подача есть","s":"работает насос 1"}, /config по-прежнему
отдаёт только имя хоста без токена.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 01:33:48 +07:00
oskarvitaliiandClaude Opus 5 6240862c27 fix(build): вынести HTML в web_page.h - сборка через arduino-cli падала
Генератор прототипов Arduino не понимает сырые строковые литералы: внутри
JS он нашёл "function draw(d){" и "function poll(){", принял их за
определения функций C++ и вставил для них прототипы в начало файла.
Сборка падала с "error: 'function' does not name a type", а директивы
#line показывали ошибку на строках внутри HTML.

Заголовочные файлы препроцессор не сканирует, поэтому страница переехала
в web_page.h. Тот же приём используется в соседнем проекте.

Ошибка не всплывала раньше потому, что я проверял скетч через
g++ -fsyntax-only по .ino: так файл компилируется как обычный .cpp и
препроцессор Arduino не участвует. Проверять сборку нужно arduino-cli,
это записано в README 11.1.

Прошито и проверено на железе (COM7, LOLIN D1 ESP-WROOM-02, 2MB FS:64KB):
размеры флеша совпадают, LittleFS монтируется, ранее заданные через панель
настройки пережили перепрошивку, Wi-Fi поднимается, датчики читаются.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 01:08:17 +07:00
oskarvitaliiandClaude Opus 5 cb151d2e30 feat(ui): переработать веб-панель и макет OLED
Панель как световое табло, а не дашборд: главный ответ занимает верх
страницы и читается с расстояния. Прежде самым крупным элементом были
часы браузера - единственное, что не несло информации о приборе, - а
ответ "есть подача или нет" был набран 11 кеглем в панели размером с
кнопку. Норма и авария выглядели почти одинаково.

Исправлены три содержательные ошибки:

- лампа выхода "Авария" горела ЗЕЛЁНЫМ при срабатывании: стиль
  "включено" был общим для всех ламп, и сработавшая сигнализация
  подсвечивалась цветом "всё хорошо";
- страница делала location.reload() при любом изменении состояния,
  стирая недописанный адрес в форме вместе с прокруткой и фокусом;
- CSS подключал шрифты с Google Fonts, тогда как сеть на объекте не
  разрешает внешние имена - нарисованный макет там не появлялся никогда.

Страница стала статической и уехала во флеш (PAGE_HTML PROGMEM, ~8 КБ,
отдаётся через send_P). Раньше HTML собирался строкой в куче на каждый
запрос: ~14 КБ при ~40 КБ свободной кучи. Состояние подставляет JS,
забирая /status и новый /config. В /status добавлено поле rdy.

/config отдаёт только имя хоста webhook: полный URL содержит секретный
токен, и выводить его на экран, видимый всем в помещении, незачем.

Цвета по конвенции сигнальных ламп: резерв теперь ЖЁЛТЫЙ, а не красный -
работающий резерв это предупреждение, система ещё справляется. Красный
оставлен для остановки основных. Убраны декоративные коды SYS_OK / 0xFF,
не кодировавшие ничего, и тройное дублирование состояния резерва.
Настройки свёрнуты в details.

OLED: верхняя строка отдана статусу кеглем 2 - прежде её постоянно
занимал заголовок "PUMP CONTROLLER v8", который никогда не менялся.
Три строки датчиков сжаты в одну, освободившееся место отдано состоянию
обоих реле. Текст остаётся латиницей: встроенный шрифт Adafruit GFX
кириллицы не содержит.

Проверено: сборка -Wall -Wextra без предупреждений; все три состояния
панели просмотрены в браузере на 375 и 800 px. На железе не проверялось.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:43:34 +07:00
oskarvitalii d53035a5ef Merge dev into master: pump_controller_8_2_OLED_DONE
Добавлен проект контроллера насосной станции на Wemos D1 mini вместе с
его историей: мониторинг двух основных и резервного насоса, два реле
аварий на D3/D4 (модули active-LOW), OLED, веб-панель с настройками и
состоянием реле, уведомления на HTTPS-webhook, настройки в LittleFS.

Также: .gitignore переведён на белый список, чтобы рабочий каталог
D:\Soft\Arduino мог служить рабочим деревом репозитория, не втягивая
в него старые версии скетчей, архивы и драйверы.
2026-08-07 23:20:29 +07:00
oskarvitaliiandClaude Opus 5 42cbb82492 chore: allowlist .gitignore, рабочий каталог D:\Soft\Arduino как корень репо
Локально все скетчи лежат вперемешку в одной папке: рабочие проекты,
старые версии, эксперименты, архивы и драйверы. Чтобы этот каталог мог
служить рабочим деревом репозитория, .gitignore инвертирован — по
умолчанию игнорируется всё, проекты добавляются точечно строкой
"!/ИмяПапки/".

Прежние правила (артефакты сборки, мусор ОС, secrets.h) сохранены и
перенесены ниже белого списка: более поздний шаблон побеждает, поэтому
они продолжают действовать внутри отслеживаемых папок.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:13:58 +07:00
oskarvitaliiandClaude Opus 5 2aa7e20fd4 docs: add pump_controller_8_2_OLED_DONE to the projects index
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:56:50 +07:00
oskarvitalii e91d13b8eb Добавить проект pump_controller_8_2_OLED_DONE
Контроллер насосной станции на Wemos D1 mini: мониторинг двух основных
и резервного насоса, два реле аварий, OLED, веб-панель с настройками,
уведомления на HTTPS-webhook.

История проекта влита целиком, в сообщениях коммитов разобраны причины
принятых решений: выбор пинов D3/D4 под реле active-LOW, ограничение
блокировки loop() при отправке webhook, перенос настроек в LittleFS и
найденная причина несохранения настроек (чип 2 МБ при настройке IDE 4 МБ).
2026-08-07 22:56:09 +07:00
oskarvitaliiandClaude Opus 5 3f21b44a65 Уточнить в коде причину отказа от EEPROM
Комментарий утверждал, что запись в EEPROM на этой плате проваливается,
что читается как «EEPROM сломана». На деле причина была в неверном
Flash Size, и с верной настройкой работали бы оба механизма. Формулировка
исправлена, чтобы следующий читатель не пошёл менять хранилище вместо
проверки размера чипа.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 20:05:42 +07:00
oskarvitaliiandClaude Opus 5 703c4d46c6 Зафиксировать проверенную на железе раскладку 2MB (FS:64KB)
Прошивка запущена на целевой плате: LittleFS монтируется и отдаёт
45056 Б, файл настроек читается при старте. В документации указан
фактически использованный вариант Flash Size вместо предполагавшегося
FS:128KB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 20:04:42 +07:00
oskarvitaliiandClaude Opus 5 963209dcc9 Найдена причина: чип 2 МБ при настройке IDE 4 МБ
Плата имеет физически 2 МБ флеша, в IDE было выбрано 4MB (FS:2MB).
При этой раскладке сектор EEPROM лежит по смещению 0x3FB000, а начало
LittleFS — по 0x400000, тогда как чип заканчивается на 0x200000. Обе
области вне микросхемы, поэтому spi_flash_erase_sector() проваливался
и EEPROM.commit() возвращал false.

Лечится настройкой Flash Size = 2MB (FS:128KB OTA:~960KB), а не кодом:
любая раскладка 2MB кладёт файловую систему ниже 0x1FB000, а EEPROM в
сектор 511. С верной настройкой работали бы оба механизма хранения.

Ранее записанная в README гипотеза про «щель между FS и областью SDK»
была неверной и заменена подтверждённым разбором. Переход на LittleFS
остаётся оправданным по своим качествам, но проблему решает настройка
IDE — в документации это разделено явно, чтобы не ввести в заблуждение
при похожем симптоме на другой плате.

Документация исправлена: плата 2 МБ, а не 4 МБ; требование совпадения
выбранного размера с физическим; предупреждение о потере учётных данных
Wi-Fi при смене раскладки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:51:38 +07:00
oskarvitaliiandClaude Opus 5 4c0f9672f3 Перенести настройки из EEPROM в LittleFS
EEPROM.commit() на целевой плате возвращает false — падает сам вызов SDK.
Проверкой исходников исключены ошибка в библиотеке, перекрытие адресов и
стирание сектора со стороны WiFiManager. Физический размер флеша тоже ни
при чём: LittleFS на этом же чипе работает, что подтверждено соседним
проектом на такой же плате.

Наиболее вероятный механизм: при раскладке 4MB/FS:2MB сектор EEPROM (1019,
0x3FB000) попадает в щель между концом ФС (0x3FA000) и служебной областью
SDK (0x3FC000) и оказывается защищён от стирания. Доказать не удалось,
поэтому в документации гипотеза помечена как гипотеза. Переход на LittleFS
выбран не как обход симптома, а как переезд на подтверждённо работающий
на этом железе механизм.

Настройки теперь в /settings.json (ArduinoJson v7). Загрузка
отказоустойчива: отсутствующий или битый ключ оставляет значение по
умолчанию, поэтому файл от прошлой версии прошивки не ломается при
добавлении полей. Имена ключей вынесены в константы KEY_*, используемые и
при чтении, и при записи.

Молчаливой потери настроек больше нет: при недоступной ФС взводится
settingsFsReady = false, и веб-панель показывает предупреждение, что
настройки не переживут перезагрузку. В лог при старте выводится геометрия
ФС и предупреждение при выборе FS:none.

Проверено: xtensa-lx106-elf-g++ -fsyntax-only -Wall -Wextra против ядра
3.1.2 — предупреждений нет. На железе не проверялось.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:41:05 +07:00
oskarvitaliiandClaude Opus 5 ca02a48684 Диагностика: печатать сектор EEPROM и границы FS против физического флеша
commit() возвращает false, то есть падает сам вызов SDK
(spi_flash_erase_sector либо spi_flash_write). Самая вероятная причина —
сектор EEPROM за пределами физической микросхемы.

При раскладке 4MB/FS:2MB сектор EEPROM это 1019 (смещение 0x3FB000), что
требует чипа не меньше 4 МБ. Теперь лог сам печатает это смещение и
сравнивает с ESP.getFlashChipRealSize(), а также границы файловой системы —
последнее важно, потому что при том же дефекте LittleFS по адресу
0x400000 окажется недоступен ровно так же.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:21:25 +07:00
oskarvitaliiandClaude Opus 5 fa54e5790f Диагностика сохранения EEPROM: проверять commit() и перечитывание
Настройки не сохраняются после перезагрузки. Причина пока не установлена,
это шаг сбора улик, а не исправление.

Найден дефект, мешавший диагностике: результат EEPROM.commit()
игнорировался, поэтому лог печатал «Конфигурация сохранена» безусловно —
даже при провалившейся записи. Теперь запись подтверждается повторным
чтением сектора из флеша и сравнением всех полей; при расхождении в лог
идут записанные и прочитанные значения.

Добавлена проверка геометрии флеша при старте: несовпадение размера по
настройке IDE с физическим уводит сектор EEPROM за пределы микросхемы,
и запись молча теряется — симптом ровно тот же.

Проверкой чтения исключены: корректность библиотеки EEPROM, раскладка
адресов (258 из 512, без перекрытий), стирание сектора со стороны
WiFiManager::resetSettings() — он вызывает только WiFi.disconnect().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:29:47 +07:00
oskarvitaliiandClaude Opus 5 cf61617a46 Настройки через веб, состояние реле в UI, распиновка под реле active-LOW
Распиновка. Пина D0, который использовался под выход «Резерв», в доступном
наборе нет. Кнопка сброса Wi-Fi удалена как избыточная: при пропавшей сети
wm.autoConnect() сам поднимает портал. Освободившийся D3 закрыл дефицит.

Выходы переехали на D3 и D4 и стали инверсными. Причина: для реле active-LOW
безопасное состояние — пин в HIGH, а D3/D4 подтянуты к HIGH внешними
резисторами платы и держат этот уровень всю загрузку. D8, наоборот, подтянут
к LOW и щёлкал бы реле при каждом включении питания. В setup() digitalWrite()
идёт до pinMode(), иначе защёлка выхода даёт короткий LOW.

Инверсия живёт только в setRelay(); логическое состояние дублируется в
relayMainOn/relayEmergOn, чтобы панель показывала смысл, а не уровень пина.

Настройки. device, выдержка аварии и выдержка резерва вынесены в /set_config
и EEPROM. Раскладка EEPROM версионирована: magic 0xA55B, старый 0xA55A
распознаётся и переносится, поэтому прошитые приборы не теряют webhook.
Значения по умолчанию воспроизводят прежнее поведение (5 с и 0 с).

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

device_name фильтруется до [A-Za-z0-9_-] вместо экранирования: такой набор
безопасен и в JSON, и в HTML. Пустое или неверное поле формы означает
«не менять», поэтому частичное заполнение не сбрасывает остальное.

Состояние реле выведено в /status и в панель.

Проверено: xtensa-lx106-elf-g++ -fsyntax-only -Wall -Wextra против ядра
3.1.2 — предупреждений в скетче нет (попутно убран неиспользуемый
isEmergency); валидация настроек прогнана на 22 граничных случаях;
раскладка EEPROM проверена на перекрытия. На железе не проверялось.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:02:21 +07:00
oskarvitaliiandClaude Opus 5 4c760e943f Задокументировать два принятых ограничения
Отсутствие ретраев webhook — сознательное решение: автономная работа
без отправки событий это штатный режим.

Пауза мониторинга до 2 минут при старте без сохранённой сети
(блокирующий портал WiFiManager в setup()) — принята как есть,
с указанием способа лечения, если понадобится.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:59:57 +07:00
oskarvitaliiandClaude Opus 5 355a9912ab Ограничить блокировку loop() при отправке webhook бюджетом 3 с
BearSSL делает TLS-хендшейк синхронно, поэтому вместо асинхронности
блокировка ограничивается по времени.

Главный источник залипания — не таймауты HTTPClient, а DNS:
WiFiClientSecureCtx::connect(name, port) резолвит имя через
WiFi.hostByName() без таймаута и подвешивает loop() на ~10 с.
Теперь имя разрешается заранее с лимитом 800 мс, результат попадает
в кэш lwIP, и внутренний резолв возвращается мгновенно. Соединение
по-прежнему идёт по имени, поэтому SNI и заголовок Host сохраняются.

Остаток бюджета уходит в http.setTimeout(): он ограничивает и
connect+TLS (через _client->setTimeout() до connect), и чтение
ответа (собственный цикл HTTPClient по _tcpTimeout).

Худший случай: ~3.5 с вместо прежних 10-15 с. Выходы аварии не
затронуты — digitalWrite() и раньше выполнялся до отправки.

Проверено: xtensa-lx106-elf-g++ -fsyntax-only -Wall -Wextra против
ядра 3.1.2 — в скетче замечаний нет; разбор URL прогнан на 15
граничных случаях.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:40:42 +07:00
oskarvitaliiandClaude Opus 5 75430a3732 Initial commit: контроллер насосов v8.2 + документация
Прошивка Wemos D1 Mini для мониторинга трёх насосов: OLED,
веб-панель, webhook-уведомления, сброс Wi-Fi кнопкой.

Адрес webhook задаётся через веб-панель и хранится в EEPROM;
в исходнике не хранится, так как содержит секретный токен.

Добавлены README.md (техдокументация) и MANUAL.md (руководство).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 13:51:22 +07:00
oskarvitaliiandClaude Opus 4.8 bda2ecb7a3 Merge dev into master: carousel dot fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 13:56:38 +07:00
oskarvitaliiandClaude Opus 4.8 d1f96007de fix(display): erase full icon height in boot carousel
The sun's lowest ray reached y+H, one row below the H-tall erase box,
leaving a stray dot/trail. Erase H+4 so nothing is left behind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 13:55:53 +07:00
oskarvitaliiandClaude Opus 4.8 208669d19f Merge dev into master: boot icon carousel
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 12:51:03 +07:00
oskarvitaliiandClaude Opus 4.8 a0f96fc52d feat(display): boot icon carousel
On power-up, after the title splash the screen clears and each weather
icon slides in from the right to centre with its colour-coded label, then
slides out to the left — Fine, Fair, Changeable, Rain, Storm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 12:48:28 +07:00
oskarvitaliiandClaude Opus 4.8 68bdfdc879 Merge dev into master: RU/EN web interface + scrollable forecast log
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 11:27:09 +07:00
oskarvitaliiandClaude Opus 4.8 98e8170b8b feat(web): RU/EN language toggle (Russian default)
Client-side i18n: static labels via data-i18n, dial zone words, and the
26 Zambretti phrases + categories + trend words translated in the browser.
Choice persists in localStorage. Device firmware unchanged (API stays
English; TFT stays Latin).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 11:15:26 +07:00
oskarvitaliiandClaude Opus 4.8 e9ec610f1c feat(web): scrollable Forecast log with fixed height
Cap the log panel at ~216px with a thin themed scrollbar so a long
forecast history does not stretch the page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 10:43:02 +07:00
oskarvitaliiandClaude Opus 4.8 2bf49c14ac revert: restore pre-4b59b89 web server (drop chunked API + low-memory auto-reboot)
The chunked API handlers and the maxblock<6000 auto-restart from 4b59b89
broke the web UI on-device; restore the working String-based handlers and
plain loop while keeping the scrollable forecast log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 08:48:25 +07:00
oskarvitaliiandClaude Opus 4.8 f8294d4aed feat(web): scrollable Forecast log with fixed height
Cap the log panel at ~216px with a thin themed scrollbar so a long
forecast history does not stretch the page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 07:08:21 +07:00