Smart Plant OS: что обнаружилось между HTML-макетом и первой прошивкой

В прошлой статье я рассказывал про идею открытой локальной системы ухода за растениями. На тот момент у проекта были документация, спецификации и большой HTML-макет. Можно было открыть страницу, посмотреть на растения, походить по настройкам и представить, как всё будет работать. Теперь этот интерфейс открывается с настоящей ESP32 в домашней сети. Между этими двумя состояниями пришлось решить несколько инженерных вопросов, которых на красивой странице не видно.
После публикации я услышал много критики, хороших идей и правильных вопросов. Особенно оживлённым получилось обсуждение избыточности и надёжности ESP32 на каждый горшок. Зачем столько вычислительных ресурсов для датчика и насоса? Почему не поставить один главный контроллер, а к нему подключить простые устройства по проводам? Что произойдёт, когда растений станет пятьдесят?
Я действительно задумался над этими вопросами. Свою квартиру я представлял хорошо: несколько растений, разные комнаты, частая смена локации горшка. В таком сценарии беспроводная связь удобна. Но если поставить пятьдесят растений рядом, аргументы в пользу общей шины и дешёвых периферийных контроллеров становятся гораздо сильнее. И спорить здесь о том, какой вариант правильный вообще, довольно бессмысленно. Сначала нужно договориться, для какого дома и какого количества растений мы его выбираем.
Для первой версии я оставил ESP32-S3 N8R2 в роли главного контроллера и ESP32-C3 для отдельных растений. На данных платах удобно разрабатывать и проверять систему. Если позже появится более простой контроллер или проводной вариант подключения, приложение и правила обмена не должны отправиться в корзину вместе с первой платой. С этой мыслью я и перешёл от описания архитектуры к прошивке.
Для S3 и C3 подготовлены отдельные проекты ESP-IDF с общими компонентами. Пока прошивка и обновления выполняются через USB; сетевого OTA ещё нет. Приложение можно обновлять отдельно от NVS, сохраняя настройки и ключи действующей установки.

Первое неожиданное ограничение оказалось довольно прозаичным: данные, которые прекрасно выглядят в спецификации API, не помещаются в радиопакет. Для браузера удобно получить один JSON со всем состоянием растения. Там могут быть показания, качество данных, время обновления, состояние полива и причины отказа. Такой ответ легко посмотреть в отладчике, сохранить в файл и использовать в будущей интеграции.
Но для внутреннего обмена я выбрал ESP-NOW, а в основу первой версии заложил совместимость с кадрами до 250 байт. Пример State из спецификации в компактном JSON занимает около 678 байт. Он уже почти втрое больше доступного кадра, причём ещё нужно место для служебных полей и аутентификации. Казалось бы, достаточно перейти на бинарную сериализацию. Я сравнил варианты: CBOR со строковыми ключами уменьшает тот же документ примерно до 564 байт, с числовыми — до 287. Всё ещё слишком много.
И вот здесь пришлось менять сам подход к обмену. Устройство больше не должно пересылать браузерную модель целиком. Оно отправляет небольшие сообщения, а Hub собирает из них состояние для приложения. В результате появились два связанных, но разных контракта. Снаружи — JSON и REST API. Между платами — компактные сообщения Smart Plant Protocol в CBOR. Например, результат команды передаётся отдельно от телеметрии, а история запрашивается порциями. Числовые ключи и их значения зафиксированы в спецификации, поэтому экономия байтов не превращается в набор непонятных констант. Это добавляет работу по преобразованию данных. Зато размер радиокадра теперь не диктует, какие поля можно показать в приложении. Измерения и принятое решение описаны в ADR о сериализации.

Следом возник вопрос, где заканчивается транспорт и начинается сама система. Было бы удобно передать полученный пакет прямо в функцию обработки команды. Пока устройств два, а запросы приходят редко, такой код даже выглядит вполне аккуратно. Проблемы начинаются, когда одновременно приходят радиосообщения, браузер запрашивает состояние, меняются настройки и нужно что-то сохранить во flash. Если каждый обработчик начинает самостоятельно менять общие структуры, появляются зависимости, которые потом тяжело воспроизвести: одна задача читает состояние, пока другая его обновляет, а третья ждёт завершения записи.
Поэтому в текущей реализации изменяемым состоянием приложения владеет одна задача. Радиоколбэки копируют кадры в ограниченную очередь. Обработчики HTTPS тоже передают запросы через очереди. Разбор сообщений и изменения модели происходят уже в основном контексте приложения.
Так проще понять порядок событий и проверить, что будет при одновременных запросах. Но появляется другой обязательный вопрос: что делать, когда очередь заполнена? Ответ “выделить ещё памяти” для микроконтроллера не подходит, ведь она у него и так быстро заканчивается. Поэтому размеры очередей и структур ограничены, а протокол должен нормально переживать потерю сообщения. Повтор запроса — предусмотренная ситуация. Он сохраняет тот же идентификатор, по которому устройство узнаёт уже обработанную команду.
В репозитории есть host-тесты на повторы и перестановку сообщений, восстановление состояния, ошибки сохранения и повреждённые пакеты. Для кодека предусмотрены проверки усечённых кадров и набор из 20 тысяч повреждённых пакетов. Их задача — проверить, что неожиданный набор байтов отклоняется, а не становится командой или выходом за границы памяти.
Отдельно проверяются аутентификация и защита от повторного воспроизведения сообщений. Здесь пришлось учитывать перезапуск платы: обычный счётчик в оперативной памяти после загрузки начинается заново. Для криптографического протокола этого недостаточно, поэтому в инженерном профиле используются сохраняемый номер загрузки и последовательность сообщений. Это повлияло и на процедуру установки: восстановить старый образ NVS с прежними ключами и счётчиками нельзя считать обычным обновлением прошивки. Такие проверки помогают разбирать ошибки в логике обмена. Но по ним нельзя судить о качестве радиосвязи в квартире. Успешный тест на компьютере ничего не говорит о потерях пакетов рядом с загруженным роутером, задержках при работе HTTPS или поведении питания. Эти вещи нужно измерять отдельно на платах.
Когда я занялся подключением Hub к домашней сети, проявилось ещё одно ограничение выбранной схемы. Wi‑Fi и ESP-NOW на ESP32 используют один радиоканал. Если S3 подключается к роутеру, канал этой сети начинает влиять и на обмен с C3. В интерфейсе пользователь видит простую настройку: имя сети и пароль. Внутри этого недостаточно. Нужно учитывать, где находятся остальные устройства, что произойдёт при смене канала роутером и как система восстановит связь после перезапуска.
Для первого стенда я зафиксировал требование согласованного канала и описал ограничения. Законченной автоматической процедуры, которая надёжно восстанавливает все такие ситуации, пока нет. И это как раз тот вопрос, который нужно решить до удобного Plug&Play. Иначе пользователь успешно подключит главный контроллер к Wi‑Fi и одновременно потеряет связь с растениями.
С первым запуском вообще получилось больше работы, чем я ожидал. Устройство должно получить сетевые настройки, индивидуальные ключи связи и доступ к административному API. Сейчас для инженерного профиля используется первоначальная настройка через USB. Она позволяет подготовить конкретные платы и проверить обмен. До удобного пользовательского мастера здесь ещё есть работа: смена сети, восстановление доступа и добавление нового устройства должны стать понятными отдельными операциями.
После этого удалось дойти до первого видимого результата: S3 загружается, подключается к домашнему Wi‑Fi, получает адрес и отдаёт интерфейс по HTTPS. Большая страница с растениями и настройками теперь открывается с самой платы. Уже проверен путь от сборки и записи прошивки до подключения из браузера. Но содержимое страницы пока остаётся демонстрационным: красивый макет ещё предстоит последовательно связать с реальным API. К испытаниям с датчиками и насосом я пока не успел перейти.
Длинную историю тоже пришлось отложить, хотя на макете графики выглядят как само собой разумеющаяся часть продукта. У истории свой набор задач: куда писать данные, как восстанавливаться после сбоя, как читать записи порциями и что делать при заполнении носителя. Сейчас на Hub предусмотрен ограниченный буфер и интерфейс хранилища. За ним позже можно добавить файловую реализацию, затем экспорт или NAS. Это позволяет разрабатывать обмен и приложение, пока ещё не выбраны карта памяти, формат индексов и процедура восстановления файлов. При этом отказ истории не должен останавливать остальную систему.
Так постепенно выясняется, что «заложить поддержку интеграций» — это не добавить в настройки переключатели Home Assistant и NAS. Нужно заранее определить, какие данные им доступны, как они узнают о потере связи, какие команды могут отправлять и как получить результат операции. Самих интеграций пока нет, но для них уже есть общий API и идентификаторы устройств, которые не должны меняться вслед за IP-адресом или названием растения. Сейчас Smart Plant OS находится между архитектурным прототипом и первым аппаратным стендом. Уже есть код, который можно собирать и проверять, есть работающий веб-сервер на плате. При этом остаются вопросы, на которые документация ответа пока не даст.
Репозиторий Smart Plant OS открыт. В нём есть код, спецификации и принятые архитектурные решения с известными ограничениями. Если хочется поучаствовать, сейчас ещё можно повлиять на основу проекта: предложить сценарий, который я не учёл, проверить обмен на своих платах или помочь превратить отдельные инженерные процедуры в удобную установку.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.