Формат основан на Keep a Changelog, проект использует Semantic Versioning.
В v5.0.69 весы начали отдавать раскрашенную таблицу, но на одном листе, тогда как скрипт tools/csv_to_xlsx.py делал четыре. HTML-формат иначе и не умеет: Excel хранит в нём каждый лист отдельным файлом, в одном документе несколько листов не собрать.
Формат сменён на SpreadsheetML 2003 (XML) — единственный, который держит несколько листов в одном файле и при этом собирается на ESP32 обычной печатью в поток. Эндпоинт и кнопка те же.
Три листа:
- Сводка — период, число замеров, вес начало → конец и мин/макс, температура мин/макс, батарея начало → конец в вольтах и процентах, расход заряда в сутки, остаток дней работы и длина полного цикла.
- Замеры — как раньше: вес жёлтой заливкой, изменение к предыдущему замеру зелёным/красным, температура пятью ступенями, батарея с красной отсечкой на 3.6 В. Добавлено закрепление шапки.
- По дням — замеров за сутки, вес на конец дня, привес за сутки (зелёным/красным), мин/макс веса и температуры, батарея на конец дня.
Файл собирается за три прохода по логу: статистика для «Сводки» нужна до того, как пишется первый лист; «По дням» группируется на лету — строки в логе идут по возрастанию даты, достаточно отследить смену первых 10 символов.
Значения на «Сводке» — строки, локаль Excel к ним не применяется, поэтому точка заменяется на запятую вручную (_ru_dec()): иначе рядом с настоящими числами из «Замеров» выглядело чужеродно.
Графиков — SpreadsheetML их не поддерживает. Нужны графики — tools/csv_to_xlsx.py, он делает настоящий XLSX с четырьмя листами и тремя диаграммами.
Предупреждение Excel про формат файла осталось: оно появляется из-за расширения .xls при не-бинарном содержимом. Нажать «Да».
Формат проверен до прошивки: сгенерирован файл по реальной выгрузке + искусственные крайние случаи (мороз −5.4 °C, жара +41 °C, разряд 3.38 В), открыт настоящим Excel через COM. Три листа на месте, заливки применяются: шапка #1F3A2E, вес #FFF3CD, жара #E06666, разряд #FFC7CE.
Нажатие «📥 Весь лог CSV» отдавало 500 Cannot open log. Выгрузка за период при этом работала — разница и указала на причину.
Веб-слой открывал файл сам:
f = LOG_FS.open(LOG_FILE, "r"); // LOG_FS на ESP32 = SPIFFSА лог пишется в LittleFS (Logger.cpp: _fs_open_read()). Макрос LOG_FS остался с тех времён, когда на ESP32 использовалась SPIFFS, и после перехода на LittleFS указывал в пустоту: файла там нет, открытие падает всегда. Выгрузки за период и за дату шли через Logger и потому работали.
Веб-слой больше не лезет в файловую систему. Полная выгрузка идёт тем же путём, что и остальные — log_stream_csv_range(cs, "", "") с пустым диапазоном. Макрос LOG_FS удалён, чтобы не выстрелил ещё раз.
Единственным экспортом был голый CSV: колонка цифр, в которой ничего не разглядеть — где улей набрал, где просел, когда села батарея. Раскрасить можно было скриптом (tools/csv_to_xlsx.py), но он живёт на одном конкретном компьютере: подключишься с другого ноутбука — снова получишь безликий CSV.
Раскраску делает сама прошивка. Новый эндпоинт GET /api/log/xls?from=&to= отдаёт HTML-таблицу с инлайн-стилями под именем beehive_<период>.xls — Excel и LibreOffice открывают её как книгу и сохраняют заливку. Кнопка «📊 Excel с подсветкой (за период)» добавлена в раздел «⬇ Скачать» на вкладке Архив, период берётся тот же, что выбран выше.
Что раскрашено (всё считается на устройстве, Logger.cpp: log_stream_xls_range()):
- вес — жёлтая заливка, жирный;
- изменение к предыдущему замеру — новая колонка, рост зелёным, падение красным (порог ±0.05 кг);
- температура — 5 ступеней от синего (<10 °C) до красного (>35 °C);
- батарея — зелёная при заряде >80%, бледно-зелёная 50-80%, жёлтая <50%, красная ниже 3.6 В (порог алерта разряда);
- заряд в процентах — новая колонка, считается той же кривой LiPo, что в
Battery.cpp:53-59, поэтому сходится с показаниями на экране и в Telegram.
Настоящий XLSX (zip + несколько XML) на ESP32 не собрать без заметного heap, поэтому выбран HTML-вариант. Плата: Excel при открытии показывает предупреждение «формат файла не соответствует расширению» — надо нажать «Да». Об этом написано прямо под кнопкой.
CSV-выгрузка осталась на месте — она удобнее, когда данные идут в расчёты, а не на просмотр.
16.08 весы прислали отчёты в 14:00:19, 14:02:39, 14:04:43, 14:06:02 — интервал совпадает с auto-sleep = 120 сек. Значит плата циклически перезагружалась, и каждый cold boot попадал в окно ±10 мин от слота 14:00 → отправлялся ещё один «отчёт при включении» (логика v5.0.62, BeehiveScale.ino:577-583).
Защиты не было: tgSentSinceBoot — статик в RAM, обнуляется на каждом boot; persist.* лежит в RTC-памяти, которую power-cut стирает полностью. Единственное, что переживает снятие питания, — EEPROM. Функции save_last_tg_report_unix() / load_last_tg_report_unix() написаны ещё в v5.0.44, но не вызывались ниоткуда.
Исправлено: отметка времени успешной отправки пишется в EEPROM (addr 356-360) и проверяется перед каждым отчётом. Порог — половина минимального интервала между слотами расписания, максимум 15 минут. При расписании 08:00/11:00/14:00/… это 15 мин, при слотах через 10 мин — 5 мин, так что настоящие слоты не глушатся.
ESP32 WebServer.h синхронный: обрабатывает один запрос за раз. Дашборд опрашивает /api/data каждые 5 секунд, и этот поллинг влезал в тот же сокет посреди длинных операций:
/api/periodотдаёт лог одним потоком (CONTENT_LENGTH_UNKNOWN+ чанки) — поток обрывался,JSON.parseпадал → «Ошибка загрузки»;window.open('/api/log')— передача файла срывалась на середине;- вдобавок сам
archLoad()дёргал/api/configпараллельно с/api/period— тот же конфликт, только внутри одной функции.
Исправлено:
- флаг
_ioBusy— на время длинной операции 5-секундный поллинг молчит; /api/configв архиве запрашивается до стрима периода, а не параллельно;- все четыре точки скачивания CSV переведены на
dlOpen()— глушит поллинг на 30 сек; - текст ошибки стал осмысленным: подсказывает сузить период.
Чтобы понять, почему плата перезагружается, esp_reset_reason() теперь отдаётся в /api/data полем resetReason и выводится строкой «Причина старта» в карточке «Статус системы». POWERON (питание подал DS3231), SW и EXT — норма, показываются обычным цветом. PANIC, INT_WDT, TASK_WDT, BROWNOUT, DEEPSLEEP — подсвечиваются красным.
DEEPSLEEP здесь означает, что hardware power-cut не сработал и прошивка ушла в fallback-сон (SleepManager.cpp:130-137) — сон в этом режиме ест ~14 мА вместо ~2 мА.
IP-адрес весов печатался только в Serial. Чтобы открыть веб-интерфейс, приходилось заходить в админку роутера и искать устройство в списке DHCP-клиентов. После долгого простоя (весы были офлайн 2 недели) аренда истекает и адрес меняется. mDNS в прошивке был (beehivescale.local), но нигде не задокументирован — про него просто не знали.
- Новый экран меню 7/9 — IP (
display_screen_ip()): строка 1 — адрес (192.168.0.145в STA,AP 192.168.4.1в AP-режиме), строка 2 —mDNS: vesy.local. Нет связи —WiFi: net svyazi. - Показ адреса 3 сек сразу после подключения к WiFi в
setup()— видно при включении, без нажатий. - mDNS-имя укорочено:
beehivescale→vesy(MDNS_HOSTNAMEв Connectivity.h). Влезает на 16-символьную строку LCD вместе с подписью и легче набирается с телефона. Тем же именем представляется ArduinoOTA (порт в Arduino IDE теперьvesy).
MENU_SCREENS8 → 9. Порядок: 1 Вес, 2 Дельта, 3 Батарея, 4 Температура, 5 Дата/время, 6 Статус, 7 IP, 8 Калибровка, 9 Диагностика.- Магические числа экранов заменены на
SCR_IP/SCR_CALIB/SCR_DIAG— индексы калибровки и диагностики сдвинулись с 6/7 на 7/8.
load_alerts() при пустом EEPROM-блоке отдавал batLowV = 0.0 — то есть предупреждение о разряде было выключено. Реальные последствия: банка села в улье 30.07.2026, весы молча умерли, потеря замеров обнаружена только 15.08 по тишине в Telegram. Новый дефолт — 3.6 В (≈15% заряда, остаётся 1-2 дня на снятие и зарядку). Кто сохранял настройки алертов раньше — значение в EEPROM уцелеет, порог надо выставить вручную на вкладке «Telegram».
Инструкция отставала на 46 версий (заголовки v5.0.21). Исправлено:
- DS3231 SQW → D33 везде описывался как «задел на будущее, в коде не используется». На деле с v5.0.47 на нём держится весь power-cut: без припаянного SQW и живой CR2032 весы не проснутся по расписанию вообще. Переписано в manual.html, 01_components, 02_connection, 03_settings, 04_operation, 07_troubleshooting.
- Автономность заявлялась как «30–40 дней». Приведена к расчёту под реальную банку 21700 4500 мА·ч: ~17–19 дней с текущим AMS1117 и 40–44 дня после замены LDO на HT7333.
- Алерты батареи / температуры / RTC / роения не были описаны нигде — добавлен раздел в 03_settings.html.
- Как узнать IP — новый раздел в 05_web.html (mDNS / Экран 7 / резервация DHCP в роутере).
- Битая навигация: все 7 страниц в
pages/ссылались на несуществующие../index.htmlи../User_Guide.html, аmanual.htmlне ссылался наpages/вообще — страницы были осиротевшими. Ссылки сшиты в обе стороны. Удалён<link>на несуществующийstyles/main.css. - Экраны LCD перенумерованы (8 → 9 штук, IP вставлен седьмым).
ESP32 после wake-up из deep sleep одновременно запускала: ядро, LCD-инициализацию, Wi-Fi подключение. Суммарный пик потребления 600-700 мА за 100мс. MT3608 Boost (заявлено 2А пик, реально ~1.5А) уходил в защиту по току (OCP) и защёлкивался — выходное напряжение падало до 1В, ESP32 крашила, на LCD остаётся только подсветка («синий экран»). Telegram не отправлялся, расписание не срабатывало. Симптом проявлялся именно после wake-up по расписанию в улье — никто не мог нажать reset/подключить USB.
- Отложенный WiFi: ESP32 + LCD + датчики стартуют первыми (~200мА — легко тянет MT3608), показывают splash «WiFi cherez 8s..», ждут 8 секунд. Капы заряжаются, банка восстанавливается. Только потом вызывается
wifi_init()отдельным пиком ~200мА (вдвое меньше прежних 500мА). - WiFi TX power 11dBm (
WiFi.setTxPower(WIFI_POWER_11dBm)) — снижает пиковый ток передачи с ~500мА до ~200мА. Дальность падает на ~30%, но в улье или рядом с домашним роутером хватает. - Modem-sleep (
WiFi.setSleep(true)) — между передачами WiFi-чип засыпает. Снижает средний ток в active фазе.
- Пик при wake-up: 600-700мА → ~200мА (×3 меньше)
- 8 секунд паузы дают времени MT3608 восстановиться после холодного старта
- LCD показывает диагностику с первой секунды (раньше — кубики до WiFi init)
- Boot loop / защёлка MT3608 не возникают
- Если железо принципиально слабое (плохая банка с большим внутренним сопротивлением, либо MT3608 нерабочий) — поможет только замена компонентов
- При сильно посаженной банке (<3.3В) — DW01 будет отрубать OUT независимо от софта
- Капы 2200µF на VOUT MT3608 + 470µF на VIN
- Замена MT3608 → XL6009 (3А, нет защёлки)
- Новый модуль
Alerts.h/cpp— проверка пороговых условий и отправка алертов черезtg_send_message(). Анти-спам: каждый алерт шлётся один раз, флаг хранится в RAM. Сброс с гистерезисом (например, низкая батарея сбросится только когда напряжение поднимется на 0.1 В выше порога). - 4 типа алертов, все настраиваются через сайт:
- 🔋 Низкая батарея — порог в вольтах (например 3.6В). Текст: «Низкая батарея! Напряжение: 3.58 В (18%). Пора зарядить». Сброс при ≥порог+0.1В.
- ❄️ Низкая температура — например -10°C (для зимовки). Сброс при ≥порог+2°C.
- 🔥 Высокая температура — например 38°C (перегрев). Сброс при ≤порог-2°C.
- ⏰ RTC error — автоматический алерт когда DS3231 теряет валидность (села CR2032). Включается/выключается чекбоксом.
- EEPROM addr 342-356 (magic 0xAD):
AlertSettings { batLowV, tempLow, tempHigh, rtcEn }. Дефолты: всё выключено кроме RTC error. - Memory.h/cpp:
save_alerts(AlertSettings)/load_alerts(&)с валидацией диапазонов. - Endpoint
/api/alerts(GET/POST):GET→{batLowV, tempLow, tempHigh, rtcEn}POST→ сохраняет в EEPROM (поля опциональные — можно прислать только то что меняется)
- UI на вкладке Telegram — новый блок «⚠ Пороги алертов» с 3 числовыми полями + чекбокс RTC + кнопки Сохранить/Загрузить.
- Интеграция в main loop:
alerts_check()вызывается при каждой записи в лог (schedLog/bootLog/intervalLog) — точно когда есть свежие значения батареи/температуры.
Раньше единственный способ узнать о низкой батарее — увидеть «!LOW BATTERY!» на LCD когда подойдёшь к улью. Но к этому моменту банка уже на 10% и через сутки разрядится. Теперь Telegram сам напишет когда напряжение упадёт до твоего порога (например 3.6 В) — есть несколько дней чтобы приехать. То же для температурных аномалий зимой/летом.
Wi-Fi 24h-watchdog не добавляли — на пасеке Wi-Fi может отсутствовать долго, ложные срабатывания.
- Калибровочный коэффициент делителя батареи в EEPROM (addr 336-340, magic 0xAC). Прежний
BAT_DIVIDER_RATIO = 2.0fбыл захардкожен вBattery.h, теперь читается из EEPROM при старте, дефолт 2.0 если запись пустая. Валидация диапазона 1.5–3.0 (защита от мусора). - Memory.h/cpp:
save_bat_calib(float)/load_bat_calib()с валидацией. - Battery.h/cpp: глобальная
_batRatioвместо#define, новые функцииbat_get_ratio(),bat_set_ratio(r),bat_read_raw_adc_voltage()(для калибровки). - Новый endpoint
/api/battery/calib(GET/POST):GET→ возвращает{ratio, adcV, batV, smoothed}для отображения текущих значенийPOST {realVoltage: 4.10}→ авто-расчёт коэффициента из реального напряжения с мультиметраPOST {ratio: 2.124}→ ручная установка точного коэффициента
- UI на вкладке «Калибровка» — новый блок «🔋 Калибровка батареи»:
- Показывает текущее напряжение (с EMA), сырое ADC, текущий коэффициент
- Поле ввода «Реальное напряжение (с мультиметра)»
- Кнопка «⚙ Откалибровать автоматически» — вычисляет и сохраняет новый коэф.
- Кнопка «↺ Обновить» — перечитать текущие показания
- Кнопка «↻ Сбросить на 2.0» — заводское значение
- При установке нового коэффициента сбрасывается EMA-сглаживание чтобы новые показания применились сразу.
Реальные резисторы делителя 100 кΩ × 2 имеют разброс ±1-5% по точности, плюс ADC ESP32 нелинейный (±2-3%). Суммарно показания батареи могут отличаться от реальности на 0.1-0.3 В (например, у Геннадия мультиметр показывал 4.10 В, а сайт — 3.86 В). Раньше для исправления нужно было править BAT_DIVIDER_RATIO в коде и перепрошивать. Теперь — один POST на сайт, коэф. живёт в EEPROM, прошивка не трогается.
BAT_DIVIDER_RATIOвBattery.hпереименован вBAT_DIVIDER_RATIO_DEFAULT(дефолт при пустом EEPROM).bat_read_raw()теперь использует динамический_batRatioвместо макроса.
- PWA-иконка
/icon.svg— заменена на минималистичную стилизованную пчелу на скруглённом медовом квадрате. Прежний простой геометрический SVG (3 чёрные полосы на оранжевом круге + 2 белых эллипса как крылья) заменён на полноценный bee-symbol: голова с антеннами и точками, овальное тельце с тремя медовыми полосками, заострённое жало внизу, два повернутых белых полупрозрачных крыла, тонкая платформа-весы под пчелой. Скруглённый медовый квадрат как фон даёт характерный «app icon» вид. ~1.5 КБ SVG, маскируемая иконка для PWA-манифеста (purpose: any maskable). - Источник дизайна: AI-сгенерированный логотип
docs/assets/icon.png(вариант №2 из Gemini Imagen). PNG используется в README/GitHub, SVG-версия — в прошивке (компактная, масштабируемая).
- EEPROM хранит timestamp прошлого открытия web (addr 331..336, magic 0xAB). При первом открытии вкладки Архив за boot возвращается СТАРОЕ значение, EEPROM обновляется на «сейчас» если разрыв >1ч. Кеш
_lastVisitCachedв RAM держит старое значение для всего boot — повторные открытия вкладки не сбрасывают пресет на «только что». - Кнопка
↩ С прошлого визитав архивной секции — появляется только если visit timestamp валиден. Подпись под пресетами: «Прошлый визит: вчера в 18:42» / «X дн. назад». - Авто-выставление дат при заходе во вкладку Архив: если прошлый визит был ≥1 дня назад, поля «с/по» автоматически устанавливаются на интервал «с прошлого визита по сегодня» и список загружается с этими параметрами.
- Новый endpoint
GET /api/last-visit→{lastVisit: unix_seconds, serverNow: unix_seconds}. - Memory.h/cpp:
save_last_visit(uint32_t)/load_last_visit()с валидациейts < 2019-01-01 = мусор.
1.27 МБ (40.5%, +3 КБ от v5.0.16). RAM 17.5%.
- Новая вкладка
📅 Архивв веб-интерфейсе (8-я по счёту, между Главной и Графиком). - Выбор периода: два HTML date-input («С» / «По») + 5 пресетов (Сегодня / 3 дня / Неделя / Месяц / Всё). Любой произвольный диапазон.
- Лента дней сверху-вниз (новые первыми), сгруппированы по месяцам. Каждая карточка дня содержит:
- Дату + день недели (Пн/Вт/Ср…)
- Цветной маркер аномалий (🟢 прирост, 🟡 ±0.1 кг, 🔴 падение >
alert_delta, ⚪ нет данных предыдущего дня) — переиспользует существующий порогalert_delta, новых настроек не вводится - Все измерения дня (время + вес + температура)
- Мин/Макс веса и температуры за день
- Дельту к предыдущему дню в кг
- Сводка периода сверху ленты: диапазон дат, кол-во записей и дней, суммарная дельта Σ за период (плюс/минус кг с цветом).
- Скачивание: кнопки «CSV за выбранный период» (через
/api/log?from=&to=) и «Весь лог CSV» (старый/api/log).
/manifest.json—name: BeehiveScale,short_name: Beehive,display: standalone,theme_color: #f5a623, иконка/icon.svg./sw.js— service worker, кешbeehive-v1для/,/manifest.json,/icon.svg. API-запросы (/api/*) НЕ кешируются. Прочее — network-first с fallback на cache (оффлайн доступ к UI)./icon.svg— SVG-иконка пчелы (амбер круг + 3 чёрные полоски + 2 белых крыла) 192×192. Один файл, браузер сам растеризует под нужный размер.- Meta-теги в
<head>PAGE_HTML:theme-color,apple-mobile-web-app-capable,apple-mobile-web-app-status-bar-style: black-translucent,apple-mobile-web-app-title: Beehive,link rel="manifest",link rel="apple-touch-icon",link rel="icon" type="image/svg+xml". - Регистрация SW в
window.onload:navigator.serviceWorker.register('/sw.js'). - UX установки: на пасеке → подключиться к Wi-Fi весов → открыть сайт в браузере телефона → меню → «Добавить на главный экран» → иконка
🐝 Beehiveпоявляется как у нативного приложения.
GET /archive— alias на главный SPA (страница та же, открывается на вкладке Архив).GET /api/period?from=YYYY-MM-DD&to=YYYY-MM-DD— JSON-стрим записей за диапазон. Формат[{dt,w,t,b},...], влажность пропущена. Стримит через_WebChunkStreamбез heap-аккумуляции.GET /api/log?from=&to=— CSV за диапазон. Старый?date=YYYY-MM-DDсовместим. Если заданы обаfrom/to— приоритет у диапазона.GET /manifest.json,/sw.js,/icon.svg— PWA-артефакты, без_auth()(по дизайну: SW должен регистрироваться без логина).
log_stream_csv_range(out, from, to)— стрим CSV за диапазон вStream. from/to принимают оба формата дат (YYYY-MM-DDиDD.MM.YYYY).log_stream_period_json(out, from, to)— JSON-стрим за диапазон._stream_filter_range()— внутренний единый обход файла +_date_to_yyyymmdd()конвертер для лекс-сравнения дат. Без heap-аллокаций в цикле.
_WebChunkStreamвынесен на файловый уровень — переиспользуется между/api/log,/api/periodи будущими стриминговыми эндпоинтами. Буфер 256 байт, flush каждые 512 + yield().- В
nav()JS обновлёнidxmap: добавленarchive:1, остальные индексы сдвинуты (chart:2, wifi:3, settings:4, calib:5, tg:6, api:7).
1.27 МБ (40.4%, +90 КБ от v5.0.15 за счёт ~150 строк HTML+JS архивной секции и PWA-артефактов).
Та же сессия что и 5.0.0 — продолжение по результатам тестирования с Геннадием.
- Auto-sleep timeout настраивается через сайт — поле «Уйти в сон после бездействия (сек, 0=не засыпать)». Раньше захардкодено 180 сек. Можно выставить 0 (отладка), 30 (экономный), 600 (медленно). EEPROM addr 317-319 (uint16_t + magic 0xA8).
- Auto-return на главный экран через 20 сек бездействия. Не применяется на экране 6 (calib menu) и 7 (diag).
- Двойная дельта в Telegram-отчёте + на сайте:
- 📈 «С прошлого замера» (от
lastReportWeightв RTC RAM, обновляется при каждой отправке отчёта) - 🎯 «От зафиксированной точки» (от
prevWeightв EEPROM, меняется только ручной кнопкой)
- 📈 «С прошлого замера» (от
- Дата фиксации опорного веса — Unix timestamp в EEPROM addr 313 (uint32_t). Записывается при ручном нажатии «📍 Зафиксировать». В Telegram и на сайте показывается «зафикс. 4.67 от 05.05.2026, N дн назад».
scale.power_down()перед deep sleep — HX711 в энергосбережение (1.5 мА → 1 мкА).lcd.clear() + noDisplay()перед сном — на LCD не остаются "квадратики" от старых символов.- Wake-grace через sticky флаг — в момент начала нажатия (когда подсветка ещё была off) ставится
wakeEventPending = true, держится до релиза. Гарантированно срабатывает: первое нажатие при погашенном экране = просто будит, не открывает диалог тары. Раньше был баг с проверкойwasBacklightOffпосле обработки кнопки — она уже включалась черезlcd_backlight_activity. - Окно подтверждения тары увеличено 3 → 4 сек.
- Текст в Telegram переведён на русский (Отчёт, Тревога, Время, Вес, Эталон, Δ, Температура).
- JSON-буфер для Telegram 384 → 2048 байт — большие сообщения с кириллицей и
<b>теперь не обрезаются. - Подробная диагностика TG-ошибок — расшифровка кодов (-1 CONNECTION_REFUSED, -5 CONNECTION_LOST, 401 Unauthorized и т.д.) + free heap before TLS.
- PlatformIO partition:
default.csv→huge_app.csv(3MB code / 1MB SPIFFS) — без OTA, но iOS прошивка ~1.36 МБ не влезала в default. - Точность веса 3 → 2 знака на LCD и в JSON веб-API.
- Период чтения HX711 1500 → 600 мс — отклик веса ускорился с 15 сек до 5-7 сек.
- Кнопка MAIN — возвращена старая логика (двойное нажатие подтверждения тары + удержание 5 сек = калибровка). Промежуточная попытка с
MEDIUM_PRESS(3 сек = тара) откачена по просьбе пользователя. - Названия дельт в TG/web для прозрачности:
- «За период» → «📈 С прошлого замера»
- «От опорного» → «🎯 От зафиксированной точки» + дата фиксации
- Кнопка «💾 Сохранить эталон» → «📍 Зафиксировать вес как точку отсчёта» — название самообъясняющее.
- 🔥 КРИТИЧНЫЙ ОТКАТ ОПАСНЫХ ОПТИМИЗАЦИЙ СНА в
SleepManager.cpp::sleep_enter():- Удалён
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF)— конфликтовал сesp_sleep_enable_ext0_wakeup(GPIO27). Симптом:assert failed: esp_sleep_pd_config sleep_modes.c:1974 (refs >= 0)при попытке уйти в сон → SW_CPU_RESET. - Удалён
Wire.end()— после assert/panic в WiFi context вызывал NULL-pointer panic (Guru MeditationInstrFetchProhibitedPC=0x00000000). - Удалён
esp_wifi_deinit()— двойной вызов послеWiFi.mode(WIFI_OFF)крашил. - Удалён
esp_bt_controller_disable()— BT не был инициализирован, попытка disable пустого = краш. - Теперь
sleep_enter()минимальный:esp_sleep_enable_timer_wakeup() + esp_deep_sleep_start(). ESP32 сам корректно отключает peripherals.
- Удалён
- Step
cfg-sleepв HTML 30 → 1 — браузер требовал кратное 30, не давал сохранить 120 сек. - WebData структура расширена полями
lastReportWeight*+hasLastReport*. tg_send_reportсигнатура расширена наprevWeightDate,lastReportWeight,hasLastReport.
- Потребление в "deep sleep" 70 мА (измерено USB-тестером RYKEN). ESP32-чип в deep sleep ест ~50 мкА; остальные 70 мА уходят на:
- AMS1117 quiescent ~5-10 мА
- LCD I2C контроллер PCF8574 ~3 мА
- Подсветка LCD: «логически off» через
noBacklight(), но физически модуль через VCC всё равно ест - HX711 модуль (вне чипа): ~1 мА на обвязке
- Реальные ~50 мА уходят в LCD backlight который не отключается полностью через PCF8574 P3-пин — нужен P-MOSFET high-side switch.
- Schottky-диод USB→VIN на ESP32 DevKit V1 спалён — Геннадий замкнул его перемычкой. Без диода защиты от обратной полярности нет. Нужно купить SS14 (Schottky 1A 40V SMA, на корпусе маркировка «S4»). Полоска корпуса = катод, должна смотреть в сторону регулятора.
- Светодиоды выпаяны частично — экономия ~7 мА.
- Battery monitoring — делитель 100к+100к не подключён физически на GPIO34.
- Cloudflare Worker
beehive-relay.darkvolg.workers.devработает, обходит блокировку Beeline - Telegram-сообщения с эмодзи и жирной кириллицей приходят
- HX711 калибровка точная: 4.67 кг показывает 4.67 (5л бутылка воды). Раньше 5.7 кг было потому что не убрал гирю 1 кг с платформы.
- Расписание
17:00сработало точно — wake в 17:00:10 - DS18B20 27.5°C, RTC 28.0°C
- LCD все 8 экранов
Целая сессия порта с ESP8266 NodeMCU v0.1 на ESP32-WROOM-32 DevKit V1. Причина перехода — на ESP8266 не получилось завести deep sleep wake (ни через GPIO16→RST, ни через DS3231 alarm, перепробованы все варианты резисторов/диодов/режимов). На ESP32 deep sleep работает железно из коробки.
- HX711: DT=GPIO16, SCK=GPIO17 (питание от 3V3 — стабильнее показания)
- LCD I2C: SDA=GPIO21, SCL=GPIO22 (адрес 0x27, питание 5V через VIN)
- DS3231 RTC: общий I2C bus с LCD (адрес 0x68), SQW=GPIO33 (для будущего EXT0 wake)
- DS18B20: GPIO4 + резистор 4.7кОм подтяжка к 3V3
- Кнопки MAIN=GPIO27, MENU=GPIO26 (RTC GPIO — поддерживают wake-from-sleep)
- Battery ADC: GPIO34 (через делитель 100к+100к, пока не подключено физически)
- Cloudflare Worker relay для Telegram (
beehive-relay.darkvolg.workers.dev) — обход блокировкиapi.telegram.orgроссийскими провайдерами. ESP шлёт через Worker по HTTPS с заголовкомX-Beehive-Secret, Worker форвардит в Telegram. См.cloudflare-worker/ESP32_INTEGRATION.md. - Telegram отчёты привязаны к расписанию замеров — каждое срабатывание расписания (например, в 09:00) автоматически шлёт отчёт. Раньше шло по интервалу, что не работало в deep sleep.
- Сообщения Telegram переведены на нормальную кириллицу + жирный HTML (
<b>Отчёт: улей</b>,Время:,Вес:,Эталон:,Δ:,Температура:). - Дельта в Telegram-отчёте — добавлены поля
Эталон(опорный вес) иΔ(изменение от эталона). Раньше было только текущий вес. - Wake-grace для тарирования — при пробуждении LCD (подсветка погасла) первое короткое нажатие MAIN не открывает диалог тары, только зажигает экран. Это решает старую проблему "случайно протарировал когда хотел просто посмотреть вес".
- Логи sleep —
[AutoSleep] Sleep for X secпоказывают сколько ESP проспит, помогает диагностировать расписание. - Диагностика TLS-ошибок Telegram — при failure печатается free heap + расшифровка кода (CONNECTION_REFUSED, CONNECTION_LOST, 401 Unauthorized, 400 Bad Request).
- Подробное логирование причин WiFi STA timeout (NO_SSID_AVAIL, CONNECT_FAILED, IDLE и т.д.).
- Точность веса до сотых (2 знака после запятой) — на LCD
Ves: 4.42kgи в JSON веб-API. Раньше было 1 знак на LCD и 3 знака в JSON. - Период чтения HX711 1500ms → 600ms — отклик веса с EMA-фильтром α=0.3 теперь ~5-7 сек до стабильного значения вместо 15.
- Auto-format LittleFS при первом boot —
LittleFS.begin(true)для ESP32. Без этого на свежем чипе FS не монтировалась и web-панель отдавала ошибку. - Эффективный режим WiFi определяется через
WiFi.getMode(), а не только через EEPROM. Это решает баг "после fallback STA→AP веб-сервер останавливается через 7 сек после Setup Done". wifi_ensure_connected()не дёргает реконнект если фактически в AP-режиме — раньше каждые 10 сек рвал сеть после неудачного STA.- Sleep guard 60 сек — если
sched_next_sec()вернул < 60 (попали ровно в минуту расписания), используем 60 сек минимум. Без этого был цикл "проснулся-уснул-проснулся" в минуту срабатывания расписания. - JSON-буфер для Telegram 384 → 2048 байт — большие сообщения (приветствие на ~500 байт UTF-8) обрезались, parse_mode HTML терялся, теги
<b>показывались как текст. - Вёрстка веб-панели — мобильная адаптация:
.tabsс горизонтальным скроллом,flex-shrink:0на табах, приmax-width:760pxшапкаflex-wrap:wrap, версия прошивки переносится под логотип, лишние строки скрыты. Раньше пункты меню (РОСТ/WI-FI/...) уезжали за край экрана и не были доступны. - Step Deep Sleep input в HTML 30 → 1 — браузер не давал сохранить значения вроде 120 (требовал кратное 30). Теперь любое целое число.
- Шапка веб-панели "ESP8266" → "ESP32" + описание
/api/reboot. - Battery.cpp:
ADC_ATTEN_DB_11→ADC_11db— совместимость с ESP32 Arduino Core 3.0+, где константа была переименована.
- Logger.cpp: ESP8266 LittleFS
Dir/openDir/dir.next()→ ESP32File/openNextFile/isDirectory()для перебора файлов лога. - Logger.cpp: убран
ESP.wdtFeed()(нет на ESP32) — оставлен толькоyield(), который кормит таск-watchdog корректно. - WebServerModule.cpp:
collectHeaders("X-CSRF-Token")→collectHeaders(arr, 1)— на ESP32 другая сигнатура (массив + размер). - ThingSpeak очередь автоочистка — если API key дефолтный (
YOUR_*),queue_addне пишет в файл, аqueue_processудаляет старый/queue.bin. Раньше копились 50+ элементов с[Queue] Send failed, saving remainingкаждую минуту.
- Поддержка PERIPHERAL_POWER_PIN (был = 26 для ESP32, конфликт с MENU button) — пока заглушено
-1. Вернётся в P4 (финализация) когда будет P-MOSFET для отключения периферии в deep sleep.
- 94.5% Flash в default partition — пришлось использовать Huge APP partition (3MB No OTA / 1MB SPIFFS) в Arduino IDE.
AUTO_SLEEP_MS= 3 минуты захардкожено — нельзя настроить через сайт.- Battery monitoring — делитель 100к+100к не подключён физически, web показывает 0.00В.
- Потребление в "deep sleep" — измерено 70 мА (со стола, через USB-тестер). Основные потребители: AMS1117 quiescent, LCD I2C контроллер PCF8574, HX711 standby. Для нормального автономного режима нужен P-MOSFET (P4).
- HX711 откалиброван точностью ±10 граммов (эталон 4.43, текущий 4.42, Δ -0.01 кг).
- WiFi STA подключается к домашнему роутеру (DHCP IP 192.168.0.58).
- Расписание
17:00сработало точно — wake в 17:00:10, отчёт в Telegram через 5 сек. - Cloudflare Worker отдаёт 200 OK, Telegram сообщения приходят в чат
1371763893.
TARE_COUNT1 → 2 (BeehiveScale.ino). Случайное нажатие при пробуждении LCD из режима ожидания больше не запускает тарирование. Защита от потери данных в улье при случайном касании.- Первое нажатие D3 → показывает запрос на LCD: «Tara? Nazhmite esche 1 raz/3sek».
- Второе нажатие в течение 3 секунд → выполняется тарирование.
- Таймаут 3 сек без второго нажатия → показывает «Tara: otmena» и возвращается к обычному экрану.
- Калибровка (D3 удержание 5 сек) — без изменений.
- При включении весов GPIO0 (D3, главная кнопка) — boot-strap пин ESP8266, в момент инициализации даёт переходные процессы. FALLING-прерывание ловило их как «нажатие», и в v4.2.6 (после уменьшения
TARE_COUNT4→1) это автоматически запускало тарирование с грузом на платформе. - Фикс 1: в конце
setup()сбрасываем ISR-флаги обоих кнопок (btnMain.irqFell,btnMenu.irqFell). - Фикс 2: в
handle_buttons()добавлен boot grace period — первые 2 секунды после старта кнопки игнорируются, ISR-флаги периодически чистятся.
LONG_PRESS_MS2000мс → 5000мс вButton.cpp. Калибровка (D3 hold) и «домой» из меню (D4 hold) теперь требуют удержания 5 секунд вместо 2. Защита от случайного запуска опасной калибровки. Тарирование (D3 одно короткое нажатие) — без изменений.
TARE_COUNT4 → 1 (BeehiveScale.ino). Раньше для тарирования требовалось 4 коротких нажатия за 5 секунд (без визуальной обратной связи на LCD), что воспринималось пользователем как «работает через раз». Теперь — одно короткое нажатие = тарирование, как на стандартных весах.- Долгое нажатие главной кнопки = по-прежнему калибровка (защита от случайного запуска опасной операции).
- Двойное нажатие меню-кнопки = по-прежнему отмена тары (undo_tare).
- Главный экран веса:
%6.2f kg→%6.1f kg(сотые → десятые). Для пасеки точность 100г более чем достаточно (прирост 0.5–3 кг/день видно отлично), убирает «дрожание» младшей цифры. - Экран разницы (D:): аналогично
%6.2f→%6.1fдля текущей и предыдущей разницы. - В CSV-логах и в Telegram точность сохранена на уровне
%.2f— для аналитики и графиков. - В экране ручной подстройки калибровочного фактора (CF) точность веса оставлена
%6.2f— там нужно видеть мелкие изменения для точной настройки.
- В этой сборке HX711 на пинах D0 (GPIO16) / TX (GPIO1) — соответствует физической распиновке весов Геннадия. Serial Monitor не работает (TX занят SCK).
- Интервал чтения HX711: 2500мс → 1500мс (
SCALE_READ_INTERVAL_MSвBeehiveScale.ino). Время отклика на полное изменение веса упало с ~25 сек до ~10 сек. - EMA α по умолчанию: 0.2 → 0.3 (
Memory.cpp). Более быстрый отклик при сохранении фильтрации импульсов от пчёл (<1 сек длительности). - Auto-zero deadband: 0.001 кг (1 г) → 0.05 кг (50 г) (
ZERO_DEADBAND_KGвBeehiveScale.ino). Убирает дрожание показаний возле нуля (-0.01 / +0.02 → теперь стабильно 0.000).
- Поле «EMA сглаживание α» переименовано в «Скорость отклика весов» с понятным описанием: 0.1 = медленно (улей), 0.3 = средне (рекомендуется), 0.6 = быстро (тесты).
- Подсказка в блоке «Описание» теперь объясняет рекомендуемые значения для разных режимов работы.
- Существующие EEPROM-настройки сохраняются: если у пользователя уже стоит α=0.2, она не перезапишется. Чтобы получить новый дефолт 0.3 — задать вручную через веб-интерфейс или через POST
/api/settings.
- Build-флаг
LEGACY_HX711_PINSвBeehiveScale.ino— позволяет временно вернуть HX711 на старые пины D0 (DT) / TX (SCK) для диагностики. По умолчанию выключен. При активации автоматически делаетSerial.end()передscale_init()чтобы UART TX не конфликтовал с HX711 SCK.
- HX711 модуль конкретного экземпляра весов вышел из строя. Симптомы: тарирование показывает близкие к 0 значения (-0.7 кг шум), но нагрузка не считывается (90 кг → реакции нет). Воспроизведено на двух разных тензодатчиках и двух распиновках (D5/D6 и D0/TX). Решение — замена HX711 модуля. Подтверждается мультиметром: между E+ и E− должно быть ~3.3 В (excitation), при смерти HX711 = 0 В.
- Splash на LCD теперь показывает реальную версию из
Version.h(FW_VERSION) — раньше был хардкодVersiya 4.1, не обновлялся при бампе версии. Теперь будет автоматически. - В JSON
/api/backupполе_verтоже завязано наFW_VERSION(было хардкод"4.1").
- HX711 перенесён с GPIO16/GPIO1 на GPIO14/GPIO12 (D5/D6) — стандартные пины ESP8266 для HX711, как в эталонной прошивке Bee_Lite_D1 v1.1. Прежняя распиновка не имела внутреннего pull-up на DOUT (GPIO16) и использовала UART TX как SCK (GPIO1), что давало плавающий вес и ложные срабатывания spike-фильтра / watchdog.
- Убран костыль
Serial.end()передscale_init()— больше не нужен, UART TX свободен. - Удалён anti-stuck блок из
scale_read_weight()(3 лишних raw чтения послеget_units) — удваивал окно ловли наводок и не давал реальной защиты. - Удалён дублирующий вызов
set_scale()/set_offset()послеwifi_init()— это были no-op в RAM, не имели смысла. - Интервал чтения веса 800мс → 2500мс — снижает температурный дрейф HX711 и даёт WebServer окно на обработку запросов.
- Эталонный груз калибровки: верхний предел 5 кг → 50 кг (для калибровки по тяжёлым гирям при больших ульях).
- SD-карта отключена (
#define USE_SD_CARDзакомментирован в Logger.h). Логи и бэкапы теперь только в LittleFS (внутренний flash ESP8266). Причина: D5/D6/D7/D8 освобождаются от SPI, чтобы HX711 мог встать на стандартную D5/D6. В полевых условиях LittleFS надёжнее SD (нет окисления контактов слота, выдерживает -40..+85°C, FS с journaling).
LOG_INTERVAL_MS: 1 мин → 5 мин (для пасеки достаточно, снижает износ LittleFS в 5x).
- ⚠ Требуется перекоммутация HX711: DT с D0 на D5, SCK с TX на D6. SD-карту физически можно оставить на коннекторе — она просто не будет использоваться.
- ⚠ При прошивке в Arduino IDE выбрать: Tools → Flash Size → 4MB (FS:2MB OTA:~512KB) — для запаса места в LittleFS.
- Admin-пароль и OTA-пароль вынесены в EEPROM (больше не хардкод в исходниках)
- Добавлена CSRF-защита на все POST endpoints
- Санитизация CSV (защита от формула-инъекций в Excel/LibreOffice)
_maskSecretбольше не обрезает токены >63 байт, а маскирует середину- Rate limit на
/api/logи/api/data
start_webserver()теперь идемпотентный при Wi-Fi reconnect- Guard на
sleepDur=0вloop() /api/rebootотдаёт JSON доESP.restart(), а не после- RTC fallback больше не использует
__DATE__— берёт последнее время из EEPROM - Atomic rotate лог-файла на SD (write→verify→remove вместо copy+rename)
- Убран автоматический
LittleFS.format()при ошибкеbegin() - Сброс
spikeRejectCntпри NaN - Partial queue send: позиция снимается до закрытия файла
Stringзаменён наchar[]+snprintfв горячих путях (Logger, WebServer, RTC)/api/backup/restoreделает одинEEPROM.commit()вместо 18 подряд (износ flash)yield()добавлен в BearSSL handshake- Log rotate: при ошибке ротации лог truncate'ится вместо роста в бесконечность
apPassBufстал локальной переменной (race fix)
- Dummy-поле
humidityиз API (нет физического датчика) - Посторонний
сервер.txt(документация VPN от другого проекта)
- Добавлен
LICENSE(MIT) - Добавлен
CHANGELOG.md - Добавлен
platformio.iniдля сборки через PlatformIO - Добавлен
.github/workflows/build.yml(CI на каждый push) - Расширен
.gitignore(корректные правила) - Исправлена распиновка DS18B20 в
README.md(GPIO3 вместо GPIO13)
Baseline версия. Полный функционал:
- HX711 тензодатчик с EMA-сглаживанием и spike-фильтром
- DS18B20 термометр, DS3231 RTC
- LCD 1602 I2C с 7 экранами меню
- SD-карта CSV логирование
- Веб-интерфейс с REST API (Basic Auth
admin/beehive) - Telegram уведомления
- ThingSpeak облако
- Deep Sleep через 3 мин неактивности
- EEPROM калибровка с magic bytes
- OTA обновление (пароль
ota_beehive)