Шумомер для посуточных квартир: ESP32, радар 24 ГГц и год с нейросетями

—

от автора

Прибор для посуточных квартир, апартаментов, домов: считает шум в дБА, радаром 24 ГГц видит, есть ли в квартире люди, и шлёт хозяину push, когда гости заехали или устроили шум-гам. ESP32, MQTT поверх TLS, Node.js, PostgreSQL, PWA и своя плата, сгенерированная кодом в KiCad. Три ревизии, два отзыва приборов с объектов и ни одной строки кода, написанной руками. Я таможенный декларант, и всё это сделано с нейросетями. Хотя нет, обманываю, вначале даже пытался вникнуть и что-то писать сам.

Третья боевая ревизия приборов

Третья боевая ревизия приборов

Откуда вообще задача

Красная Поляна, беседка, с другом сидим и обсуждаем, что пора бы научиться нейронки в работе применять. Потыкал в DeepSeek своими рабочими вопросами, утвердили, что надо пробовать внедрять в повседнев. Там же познакомился с девушкой. И вот в районе нового года приходит ссылка от нее, мол где взять такое. Датчик шума для помещений с уведомлениями. Поискали — нет ничего. Я сказал «давай сделаю свой». Не, ну а чё? Мы ж теперь дружим с нейронками.

Ну а дальше новогодние каникулы с DeepSeek и Arduino IDE, первая ревизия на аналоговом микрофоне с Телеграм‑ботом, вторая в красном корпусе — обе уезжали работать на Поляну и обе возвращались из‑за глюков. Разработку первых двух приборов управляющая компания даже оплатила, так что отдать им нормально работающую вещь было делом чести, уже независимо от себестоимости итоговой.

Требования

После двух отзывов список сложился сам:

1. Работает без обслуживания. Передёрнуть питание некому, значит нужны сторожевой таймер, автопереподключение и обновление по воздуху с откатом.

2. Видеть человека. Вот прям однозначно.  По звуку это не решается, ИК‑датчик не видит неподвижного.

3. Хозяйка ставит сама, без меня: подключила к розетке, ввела Wi‑Fi с телефона, привязала кодом с наклейки.

4. Уведомления на телефон без Телеграма и без магазинов приложений. Первая ревизия жила через Телеграм, и когда уведомления перестали доходить, приборы стали бесполезны.

5. Несколько квартир в одном кабинете и доступ для сотрудников без права что‑то сломать.

6. Компоненты с LCSC и сборка на JLCPCB.

Архитектура

[ESP32: I2S-микрофон, LD2410C, DHT22/SHT40, SGP41]
│ MQTT поверх TLS (свой логин и ACL на каждый прибор)
▼
Mosquitto ──► worker ──► PostgreSQL
│ ▲
Redis (шина между процессами)
▼ │
PWA в браузере ◄── api ──► Web Push на телефон
cron: подписки, отчёты, чистка

Принцип простой: прибор только измеряет и раз в минуту отдаёт сводку, а все решения — шумно ли, заехали ли — принимает сервер. Не сразу пришел к этой идее тоже, сначала вся логика жила на приборе, потом понял, что на сервере поправить логику проще, чем шить прибор. А менять пришлось немало.

Табличка версий

Ревизия 1

Ревизия 2

Ревизия 3 (сейчас)

Плата rev A (следующая)

Микрофон

аналоговый на MAX 9814

аналоговый на MAX 9814

INMP441, I2S

ICS-43434, I2S

Присутствие

по шуму

адаптивная модель по звуку

радар LD2410C

радар LD2410C

Климат

—

DHT22

DHT22

SHT40

Воздух

—

—

—

SGP41

Связь и интерфейс

HTTP + Телеграм‑бот + простая страница [проверь]

MQTT, свой сайт

MQTT/TLS, PWA + push

MQTT/TLS, PWA + push

Железо

DevKit на проводах

DevKit на макетке

DevKit на макетке. Дорожки сделаны припоем, залито компаундом.

своя плата JLCPCB

Корпус

чёрный и жёлтый

красный с сеткой

серый с оранжевой решёткой

тот же

Итог

отозван

отозван

работает

готова к заказу

Железо по узлам

Микрофон: от аналога к I2S

В первой ревизии стоял аналоговый модуль, сигнал шёл во встроенный АЦП ESP32. Это та ещё идея: АЦП у ESP32 нелинейный и шумный, а радиочасть Wi‑Fi радостно наводится на провода. Замеры врали регулярно, и это одна из причин первого отзыва.

Дальше — цифровой MEMS‑микрофон INMP441 по I2S. На новой плате будет стоять ICS-43434, потому что INMP441 снят с производства фактически. В прошивке ничего менять не пришлось.

Калибровка по бытовому шумомеру: в админке есть мастер, который снимает несколько точек, считает смещение и усиление и отправляет поправки на прибор. Прибор хранит их в NVS.

Модуль LD2410C на 24 ГГц различает движущуюся и неподвижную цель: спящий человек дышит, и радар это видит. Дальность до 6 м. Сидит на UART. Кадры прошивка разбирает сама и отдаёт на сервер четыре величины: есть ли цель, движется ли, дистанция, энергия сигнала.

Главный минус — радар прожорливый, от батарейки прибор проживёт недолго, поэтому питание пришлось организовывать только от розетки.

Почему не ИК‑датчик движения? Просто решил, что радар точнее. Ну а батарейку все равно не очень хотелось внедрять.

Скрин с дашборда

Скрин с дашборда

Температура, влажность, воздух

DHT22 дешёвый и везде есть, но иногда он выдаёт одиночные выбросы. На новой плате его место занимает Sensirion SHT40 по I2C. Прошивка сама определяет, какой датчик стоит, чтобы старые и новые приборы жили на одной прошивке.

На той же шине сидит SGP41 — датчик летучих органических веществ. Очень уж просили внедрить возможность определения курения. Сырые данные обрабатывает открытый Gas Index Algorithm от Sensirion.

Питание и USB

· USB‑C с резисторами 5,1 кОм на CC1/CC2 — без них нормальные зарядки USB‑C просто не дают 5 В. Узнал об этом когда неделю пытался понять, почему вдруг сдохли разъемы на обоих приборах.

· Самовосстанавливающийся предохранитель 0,75 А и USBLC6 на D+/D− от статики.

· LDO AP7361C на 1 А вместо привычного AMS1117. У ESP32 с Wi‑Fi плюс радар пики до 500 мА, а у AMS1117 на это мало запаса.

Прибор прошивается прямо из браузера через Web Serial с помощью удобного мастера настройки нового прибора.

Прибор прошивается прямо из браузера через Web Serial с помощью удобного мастера настройки нового прибора.

· CP2102N плюс два транзистора MMBT3904 для автопрошивки.

Новая плата, сделанная кодом

Макетка с распаяными дорожками — это хорошо, но заявлять, что это прибор стыдно. Компаундом конечно все залито для красоты, но все равно. В KiCad я никогда не работал. Поэтому плату целиком сделал ИИ скриптами на Python, а я писал техзадание, имерял корпус и проверял.

Gerber дополнительно проверен отдельной библиотекой gerbonara: контур сходится, неметаллизированных отверстий семь (четыре под M3, два под фиксаторы USB‑C, одно звуковое). Зазоры и дорожки не меньше 0,15 мм, переходные 0,6/0,3 мм.

Параметры: 2 слоя, 1,6 мм, сплошная земля снизу, чёрная маска. Встаёт в текущий корпус на те же четыре винта M3 (83,5 × 65 мм между центрами). Монтаж двусторонний: микрофон стоит снизу. Руками паяется только гнездо под радар.

Узел

Компонент

Решение при расстановке

Мозг

ESP32-WROOM-32E‑N4

антенна у нижнего края, под ней без меди

Микрофон

ICS-43434

на обратной стороне, звук через отверстие 0,5 мм, подальше от ESP32 и LDO

T/H

SHT40

на «язычке», отрезанном пазами, без заливки — иначе греется от платы

Воздух

SGP41

ниже паза, подальше от SHT40

Радар

гнездо 1×5 под LD2410C

слева сверху, модуль меняется без пайки

USB

USB‑C + USBLC6 + PTC 0,75 А

по центру верхнего края, напротив выреза в корпусе

Питание

AP7361C-33

с медным полигоном, далеко от SHT40

Сервис

контрольные точки 3V3, GND, TX, RX, EN, IO0

для заводской прошивки и проверки

Рендер новой платы

Рендер новой платы

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

Прошивка

ESP32 на Arduino‑ядре, сборка в PlatformIO, разделы min_spiffs — две OTA‑партиции. Библиотеки: ArduinoJson, PubSubClient, ld2410, DHT. Всё лежит в одном могучем main.cpp на 2800 строк. Сейчас это версия 3.3.27, примерно 60-я сборка.

Звук

Отдельная задача FreeRTOS, читает I2S блоками при 16 кГц. Для каждого отсчёта: убрать постоянную составляющую, пропустить через А‑фильтр, накопить энергию. Раз в секунду — энергетическое среднее, раз в минуту — сводка на сервер. А‑фильтр — три биквада, полученные билинейным преобразованием аналоговой кривой из IEC 61 672 (вот этот абзац, честно, писала нейронка, я таких слов не знаю):

// A-взвешивание для 16 кГц: 3 биквада, |H(1 кГц)| = 1.// Ошибка к IEC: 100 Гц −0.04 дБ, 4 кГц −0.5 дБ, 6 кГц −4 дБ.static const float A_SOS[3][5] = { { 1.000000000f, -2.000000000f, 1.000000000f, -1.983886757f, 0.9839516664f }, { 1.000000000f, -2.000000000f, 1.000000000f, -1.705509634f, 0.7159875753f }, { 0.5290927743f, 1.058185549f, 0.5290927743f, 0.8215638180f, 0.1687417768f },};static inline float aWeight(float x, float z[3][2]) { for (int k = 0; k < 3; k++) { const float* c = A_SOS[k]; float y = c[0] * x + z[k][0]; z[k][0] = c[1] * x - c[3] * y + z[k][1]; z[k][1] = c[2] * x - c[4] * y; x = y; } return x;}

Про точность честно: при дискретизации 16 кГц фильтр выше 5 кГц занижает уровень (на 6 кГц — на 4 дБ). Голоса и музыка в основном ниже, так что для задачи «в квартире вечеринка» этого хватает. Но это не сертифицированный шумомер.

Какие были сложности:

· Старт микрофона. Первые секунды после включения у INMP441 постоянная составляющая — половина шкалы и ещё дрейфует, и первая минута давала ложные ~90 дБ. Теперь первые 2 секунды отбрасываются, а состояние фильтра сбрасывается.

· Мёртвый микрофон. Если размах сигнала в блоке меньше четырёх единиц, блок считается пустым. Прибор сообщает об этом и перезапускает I2S, а не шлёт тишину в 0 дБ. Если пуст левый канал, прошивка сама пробует правый — так не важно, как распаян пин L/R на модуле.

Так выглядел график до проверки

Так выглядел график до проверки

Связь, настройка

· MQTT поверх TLS, сводка раз в минуту: шум, температура, влажность, радар, индексы воздуха, состояние микрофона.

· Первая настройка: прибор поднимает свою точку Wi‑Fi со страницей настроек, пароль точки у каждого прибора свой и напечатан на наклейке.

· Команды с сервера (настройки, калибровка, обновление) подписаны HMAC‑SHA256 с меткой времени. Старая или повторная команда отбрасывается.

· OTA: файл прошивки подписан, перед установкой прибор проверяет подпись открытым ключом через mbedtls. Если новая версия не поднялась, загрузчик откатывается на старую. Каждый шаг обновления пишется в NVS.

· Сторожевой таймер задач: зависший прибор перезагружается сам.

Сервер

Node.js + Express, три процесса под PM2:

Процесс

Что делает

api

сайт, личный кабинет, админка

worker

подписан на MQTT: принимает пакеты, считает шум, заезды, воздух, шлёт push

cron

подписки, проверка онлайна, чистка старых данных

Между собой процессы говорят через Redis. Данные лежат в PostgreSQL, и это третий вариант: сначала был JSON‑файл, потом SQLite. Брокер — Mosquitto на TLS. У каждого прибора свой логин и ACL. Учётные данные прибор получает при привязке.

Личный кабинет — PWA на JS. Вход по паролю или по passkey (WebAuthn). Очень долго мучался с засыпанием PWA на iOS, кстати.

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

В цифрах: около 44 тысяч строк кода (JS, HTML, CSS, C++), 222 теста в 49 файлах на встроенном node:test.

Почему не ESPHome? Конечный пользователь — хозяйка квартир или управляющая компания. Им нужен прибор, который включил — и он работает, им не хочется и не нужно разбираться в этом всем. Требуется просто решение.

Как сервер принимает решения

Алгоритмы работают на данных минутных пакетов как машины состояний с гистерезисом. Иначе на границе уведомления шли пачками. В первой версии было именно так: «шум прекратился», «превышение +0.0 дБ».

Шум. Порог ставит хозяин и видит его на графике.

Ситуация

Условие

Что происходит

Сильно громко

среднее за минуту ≥ порог + 15 дБ

уведомление сразу, повтор не чаще 10 мин

Умеренно

порог + 5…15 дБ три минуты подряд

уведомление, повтор не чаще 20 мин

Затихло

ниже порога − 5 дБ три минуты подряд

«шум прекратился»

Обрыв связи

больше 10 мин без пакетов

эпизод сбрасывается

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

Заезды и выезды. Радар видит кого‑то два пакета подряд — заезд. Двадцать пакетов пусто — выезд. Такое количество пакетов выбрали эмпирическим методом. Мало ли, в уборную гость ушел или на балконе стоит. Если радар четыре минуты ничего не присылает, его данные считаются устаревшими и не используются. Для УК это оказалось чуть ли не важнее шума: заселение часто бесконтактное, и без прибора не понять, доехали ли гости вообще, а шумят далеко не все и не во всех апартах.

Курение. датчик выдаёт сырые значения, а Gas Index Algorithm учит обычный фон комнаты и превращает их в индекс VOC. Сигарета даёт резкий скачок. Тревога — если минутный пик выше порога две минуты подряд или один раз сильно выше порога. Учитывая, что новые платы еще не заказаны, есть ряд вопросиков к этому датчику, надо будет оперативно проверять.

Про адаптивную модель. До радара я разными путями пытался определять присутствие по звуку. Прибор строил статистику фона по часам, я руками размечал периоды «было пусто / были люди», модель дообучалась. 64 тысячи замеров спустя она была «уверена на 93%» — и регулярно расходилась с реальностью.

Как это разработано

Весь код написан AI. Инструменты менялись вместе со сложностьюи моим погружением в процесс: сначала чат с DeepSeek и Arduino IDE, потом Cursor, сейчас Claude, который сам работает с репозиторием.

Где ИИ подводил: уверенно выдумывал функции библиотек, которых нет. На раннем этапе наращивал код, пока я сам не перестал в нём ориентироваться, какой блок за что отвечает.

Итого

Сейчас готовлю к отправке приборы третьей ревизии. В планах:

· заказать новые платы и собрать уже красивый завершенный прибор;

· проверить датчик дыма;

· разнести прошивку по файлам.

Итоги сам себе подвожу так. AI заменили мне программиста и схемотехника лучше, чем я ожидал. А вот человека, который знает, что нужно, понимает, какая логика работы ожидается — не заменили. Поэтому уже почти год и занимаюсь этим по вечерам. Доделать было делом чести и пытливость ума не давала остановиться. Хотя, очень частно хотелось бросить это дело.

ссылка на оригинал статьи https://habr.com/ru/articles/1103842/