Каждый мой проект на ESP32 заканчивался одинаково: микроконтроллер готов, датчики читаются, реле щёлкают — а дальше надо руками писать HTML, CSS, JS и протокол поверх веб‑сокета, чтобы увидеть пару графиков в браузере. На третий раз я решил, что хватит, и сделал библиотеку RisalDash: интерфейс описывается на C++ в несколько строк, а весь фронтенд и протокол генерируются сами и отдаются самим контроллером.

cpp

dash.gauge ("Voltage", &volts, 0, 14, "V");

dash.chart ("Temperature", &temp, "C");

dash.toggle("Pump", &pump, [](bool on){ digitalWrite(PUMP, on); });

dash.begin();   // сохранённый Wi-Fi → подключиться; первый запуск → captive-портал

Под катом — два сюжета. Первый инженерный: как линкер помогает уместить 26 типов виджетов в килобайты и почему дашборд работает полностью офлайн. Второй честный: библиотеку я спроектировал сам, а большую часть кода написал в паре с ИИ под жёсткие лимиты памяти — расскажу, где это сработало, а где модель галлюцинировала и её приходилось ловить.

Почему не Blynk / ESPUI

Blynk — это облако. Мне нужен был дашборд, который работает без интернета и без чужого сервера.

ESPUI — ближе по духу (UI на самой плате), но визуально это «админка из 2010-х», и набор виджетов ограничен.

Хотелось: дизайн «как в приложении», полностью офлайн, real‑time, и чтобы добавление виджета было одной строкой — а не правкой HTML‑шаблона.

Идея Zero‑Waste: платить только за то, что используешь

Главное ограничение, которое я себе поставил: ничего лишнего не должно попадать во флеш. Если в скетче нет графика — в бинарнике не должно быть ни строчки CSS/JS графика.

Реализация опирается на то, как линкер выбрасывает мёртвый код (-ffunction-sections -fdata-sections + --gc-sections, в Arduino‑тулчейне это включено). Каждый тип виджета — отдельный класс со своими статическими PROGMEM‑строками CSS и JS. Пока виджет не инстанцирован, на него нет ссылок — и линкер выкидывает и класс, и его строки.

Замерил на ESP32 (Arduino), прирост флеша от первого использования каждого типа над голым ESPAsyncWebServer:

| Виджет | +флеш | Виджет | +флеш |

| led | ~1.3 КБ | metric | ~2.3 КБ |

| badge | ~1.6 КБ | table | ~2.4 КБ |

| ai | ~1.8 КБ | slider | ~2.4 КБ |

| number | ~1.8 КБ | gauge | ~3.1 КБ |

| toggle | ~2.0 КБ | chart | ~3.4 КБ |

Второй и последующие экземпляры того же типа — это уже единицы байт (объект + указатель). Неиспользуемый тип — ровно 0 байт. Это и есть весь смысл.

Отдельная боль — не держать ничего в RAM: HTML стримится прямо в сокет из PROGMEM кусками, без сборки большой String в памяти.

Offline‑first и captive‑портал

begin() на чистом устройстве поднимает точку доступа с captive‑порталом: телефон сам открывает страницу настройки, выбираешь сеть, вводишь пароль — креды и таймзона сохраняются в NVS, устройство перезагружается уже в STA. Системные шрифты, ноль внешних запросов, ноль CDN — дашборд живёт целиком во флеше.

Для real‑time — WebSocket, причём пушатся только изменившиеся значения; элементы управления зовут колбэки обратно в твой код.

Дизайн, который не раздувает флеш

UI сделан в стиле OKLCH «liquid glass»: полупрозрачные стеклянные карточки, статус‑бар в стиле iOS, многостраничность со свайп‑листом, мультиязык EN/RU/AR с полным RTL. Важный момент — всё это строки в PROGMEM, а не рантайм‑фреймворк: дизайн «дорогой» визуально, но дешёвый по флешу, и языки компилируются только те, что используешь.

ИИ как со‑разработчик: где сработало, где врал

Теперь честная часть. Библиотеку я архитектурно спроектировал сам — модульность виджетов, стратегию Zero‑Waste, разбиение на классы, контроль размера бинарника. Но рутину — генерацию 26 типов виджетов, парсинг пресетов сенсоров, мультиязычные строки, компактный CSS стеклянного UI — писал в связке с ИИ (Claude Code).

Что зашло хорошо:

Однотипная масса. 26 виджетов по одному шаблону (css/js/html + сериализация) — ИИ генерит быстро и ровно.

CSS/SVG. Компактные стеклянные стили и SVG‑иконки/дуги — экономия часов.

Рефакторинг под ограничения. «Перепиши так, чтобы не держать String в RAM, стримь в Print&» — выполняется аккуратно.

Где приходилось ловить за руку:

Указатели и время жизни в C++. Модель уверенно предлагала хранить ссылки на временные объекты — ровно тот класс багов, что молча роняет МК.

Веб‑сокеты и асинхронность. Путала колбэки ESPAsyncWebServer, придумывала несуществующие сигнатуры.

«Оптимистичные» цифры. В тексты лезли заниженные оценки размера («ядро <8 КБ, виджет ~200 байт») — я их перемерил и поправил на реальные (таблица выше). Технический читатель такое проверит первым делом.

Вывод без хайпа: ИИ под надзором архитектора — это сильный junior+, который кратно ускоряет рутину, но не заменяет того, кто ставит задачу, держит инварианты по памяти и проверяет каждое «магическое» число. Эффективный embedded в этой связке писать можно — но решает не модель, а постановка и контроль.

Бонус: устройство как инструмент для ИИ‑агента (MCP)

Раз уж про ИИ — добавил Model Context Protocol: dash.enableMCP("token") превращает каждый виджет в тулзу get_*/set_*. Через мост npx risal-dash-mcp Claude может прочитать датчик и щёлкнуть реле на реальной плате. Плюс есть автодискавери в Home Assistant через MQTT. То есть из тех же строк C++ получаешь и дашборд для человека, и интерфейс для агента.

Итог

Zero‑Waste: неиспользуемый тип виджета = 0 байт; используемый = ~1.3–3.4 КБ.

Offline‑first: AP + captive‑портал, ноль внешних запросов.

Real‑time по WebSocket, 26 виджетов, пресеты сенсоров, EN/RU/AR + RTL, OTA, MQTT + Home Assistant, MCP.

— ESP32 + ESP8266, MIT.

Живое демо (код ↔ дашборд, без железа)

Репозиторий на Гитхабе

Буду рад фидбеку — особенно по API и по тому, какие виджеты/пресеты добавить. И отдельно интересно мнение про связку «архитектор + ИИ» в embedded: где у вас она реально экономила время, а где ломалась?

Комментарии (0)