Привет, хабр. Решил слегка разбавить нескончаемый поток статей "K8S для ёжиков", "ИИ добавлен в туалетную бумагу" и прочих авторов нейростатей, пиарящих телеграм-каналы. Я просто напишу, как захотел руками сделать дверной звонок на ESP32 и какие трудности я при этом преодолевал.

Результат, если кто хочет побыстрее
Первая ревизия
Вторая ревизия (текущая)

Часть 0. Disclaimer

У меня умный дом на HomeAssistant. Давным-давно, лет 10 назад, я пытался создать платформу на AVR, которая бы запускала в себе нечто вроде интерпретатора команд, чтобы логику можно было задавать без перепрошивки устройства, но столкнулся с набором проблем, из-за которых проект и похоронил. Потом я узнал о существовании esphome и всё засияло яркими красками. Сейчас стараюсь использовать его для конечных устройств, благо пока возможностей хватает. Потому и тут всё описание и код будет с использованием этой платформы.

Часть 1. Идея

Однажды, бродя по cults3d, я наткнулся на модельку для игрушки на Хэллоуин. Она меня зацепила, я полез внутрь. Оказалось, что это не только печатная игрушка - автор сделал в ней возможность поставить электронные компоненты, чтобы слегка её оживить. Тут мысль побежала вперёд: я давно хотел сделать что-то "эдакое", а тут я прямо увидел вариант сделать из этого дверной звонок, от которого дети будут писаться по ночам. Окей, начнём.

Но, как говорится, какое ТЗ, такое и ХЗ, так что надо подумать, что именно я вообще хочу сделать.

  • Очевидно - ловить нажатие кнопки и передавать его внутрь, на HA. Т.е. нужны мозги.

  • Наверное, будет круто, если будет какая-то анимация. То есть дисплей.

  • Звуки тоже не помешают. Динамик.

Лирическое отступление: в квартире, в которой я живу сейчас, 30 лет назад было сделано хитрое видеонаблюдение: в стыки панелей в лифтовом холле (!) была встроена миниатюрная камера и микрофон. Потом это всё было заштукатурено, оставили лишь малую дырочку. Провода провели в квартиру и повесили экран. Что происходит с техникой за 30 лет без обслуживания, тем более замурованной в бетон - догадайтесь сами. Мне этот функционал показался интересным, потому я решил - почему бы и да?

  • Видеокамера. Не скрытая, конечно - закон мы чтим!

  • Микрофон - вдруг пригодится слышать, что творится снаружи?

  • Автономность? Не, у нас штука, которая всегда висит на одном месте, провода протяну.

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

Часть 2. Подбор железа

Начнём с мозгов. На тот момент (2024 год) из удобного железа было представлено только ESP32-Wrover-Dev, вроде такого:

Внешний вид платы
Меня покорила уже готовая колодка на плате для подключения к камере
Меня покорила уже готовая колодка на плате для подключения к камере

С дисплеем оказалось ещё проще: уже несколько лет у меня в ящике валялся экранчик на 1,77" на базе ST7735, типа такого:

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

Со звуком тоже достаточно просто: из современных плееров кроме dfplayer я ничего и не знаю. Бонусом в расходам идёт мелкий динамик, на 2 Ом или вроде того, и microsd для файлов плеера. У меня была на 64Мб, более чем достаточно для нашей цели.

Сам плеер
Маленький, но классный
Маленький, но классный

Труднее всего было с микрофоном - я до этого с ними вообще не контактировал. После долгих изысканий я выяснил, что мне нужно устройство, поддерживающее I2S, поэтому просто купил первый попавшийся модуль всенаправленного микрофона. Я не был уверен, что звук будет распознаваем в условиях эха, потому особо не заморачивался.

Часть 3. Эксперименты

Когда ко мне приехали все железки, я начал изучать, как их связать. И тут началась самая увлекательная часть всего процесса - фигурные танцы на граблях.

Первое огорчение меня ждало с ESP-модулем. Взглянем на его распиновку:

Распиновка

Я совершенно не ожидал, что бОльшая часть пинов на модуле при установке камеры в слот будет недоступна! Все пины, отмеченные голубым цветом, нужны камере и не могут быть использованы иначе. То есть мои возможности урезаются вдвое. Больно, но тогда я этому значения не придал. А зря, и вот почему:

Распиновка

Вот распиновка плеера. Он работает по UART, ��ак что ему нужно выделить две ноги: RX и TX.

Рапиновка

А вот распиновка дисплея. На нём остановлюсь чуть подробнее. Он работает по I2C:

  • SDA/SCL - линии данных и тактовых импульсов

  • CS - линия выбора устройства. На I2S может быть несколько устройств, выбор того, с кем говорит модуль, осуществляется подачей сигнала на этот вход

  • RES - линия перезагрузки дисплея

  • DC - Data/Command линия. По ней экрану поясняют, идёт ли сейчас команда или данные об изображении.

  • BL - управление подсветкой модуля.

Итого 2 на плеер, 6 на экран и 1 на кнопку - уже 9 пинов. Отбросим системные пины, которые не рекомендуют трогать при загрузке ESP, и остаётся грустная картина - на всё задуманное нам уже не хватает. Даже если завести BL напрямую на питание, чтобы подсветка была всегда включена, и не использовать RES - всё равно надо 7. А что свободно на контроллере:

  • 32

  • 33

  • 1

  • 3

  • 14

  • 12

Окей, но ведь можно же... поставить два контроллера! Один отвечает чисто за камеру, второй за всё остальное. Отличный план... в теории. Ведь так?

Часть 4. Принятие

Оказалось, что идея плоха прямо по всем пунктам. Вот они:

  • Два контроллера в корпус влезают еле-еле, чуть ли не с треском.

  • Две wifi-антенны рядом, судя по всему, начинают дико мешать друг другу. Эта сборка просто отваливалась, если я открывал на ноуте видеопоток с камеры.

  • Внезапно, на модуле с камерой сумрачные китайские гении использовали один из системных пинов под камеру! Это приводило к тому, что при холодном старте контроллер не запускался. Только после физического нажатия на RESET он стартовал в рабочем режиме. Что, как вы понимаете, для звонка, который должен висеть и которому никто не гарантирует питание 24/7 как-то не подходит.

В итоге пришлось отказаться от части фич. На тот момент проект уже сожрал 3 месяца свободного времени, хотелось его побыстрее закончить, потому я урезал хотелки: убрал камеру и микрофон, выкинул модуль камеры и заменил основной ESP на ESP-WROVER-KIT. На нём не было распаянной колодки, потому ног контроллера хватало на всё. Распределил я их так:

Распиновка WROVER

16

TX DFplayer

17

RX DFplayer

32

SPI CLK

для дисплея

33

SPI MOSI

для дисплея. MOSI - "Master Out Slave In"

14

DC

DC дисплея

13

CS

CS дисплея

25

INPUT

Сигнал с кнопки, другим концом подвешенной на GND

BL привязал на VCC дисплея, подсветка включена всегда. RES не использовал - оказалось, что esphome после загрузки автоматически в процессе инициализации дисплея его сбрасывает.

Часть 5. Корпус и контент

Так как корпус "ванильной" модели не годился, мне пришлось методом тыка осваивать Blender. Не самый приятный опыт для начинающего, хотя конкретная задача слегка облегчала путь. Под спойлером два скрина: лицевая часть модели с двух сторон и задняя крышка.

Вот они
Верхняя часть
Верхняя часть
Нижняя часть
Нижняя часть

Не буду описывать полтора десятка неудачных принтов и попыток, просто дам пояснения: на верхней крышке пришлось увеличивать отверстие глаза, т.к. родное было слишком маленьким. Из-за того там есть небольшой артефакт в виде "скола", который, впрочем, в глаза не бросается. Также изначально хотел сделать кнопку в виде торчащего языка, потому там такое странное отверстие и есть маленькие крепления. В итоге от этого отказался: во-первых, неочевидно, что это кнопка, во-вторых - слишком хрупко получилось, т.к. маленькое. Но отверстие отлично открывает динамик, так что всё ж полезное.

По нижней части: слева доработанное, справа оригинальное. Добавил дополнительные ножки по центру: внизу лежит ESP, над ним дисплей, высота подобрана так, чтобы они друг друга не касались. Также сверху сделал вырез для шлейфа камеры. Нижний упор мешал, его убрал вовсе.

С медиаконтентом, конечно же, тоже не обошлось без проблем. И если mp3-файл я нашёл достаточно легко среди бесплатных, то с анимацией глаза случилась неожиданная проблема. У esphome нет нативной возможности грузить что-то извне во время работы, вся анимация должна быть вкомпилирована в код и загружена на устройство. И, хоть устройство и имеет 4Мб флеша, библиотеки по работе с камерой, дисплеем и вайфаем тоже весят более 0. Более того, при компиляции происходит следующая магия: гифка режется на кадры, каждый из них превращается в отдельный двумерный массив пикселей и зашивается в код. То есть обычная гифка с более-менее плавными движениями просто не влезала в кристалл! Пришлось искать сервисы по редактированию: выкидывать кадры, уменьшать глубину цвета, ещё что-то, уже не помню. И даже так я еле влез во флеш.

RAM: [= ] 12.1% (used 39784 bytes from 327680 bytes)
Flash: [========= ] 91.3% (used 1676266 bytes from 1835008 bytes)

Всё это было засунуто в распечатанный корпус, обильно обмотано синей изолентой и повешено за дверь. Внутрь были выведены только кабели питания и линия reset, на всякий случай. И так оно проработало больше года, пока...

Часть 6. Возвращение творческого зуда

Работа в IT имеет свою специфику. Которая, к сожалению, последние годы заключается в том, что ни один проект не доделывается до конца — всегда «и так сойдёт, потом пофиксим». В какой‑то момент мне стало от этого физически некомфортно и захотелось закончить хотя бы что‑то. И я вспомнил, что звонок у меня в стадии MVP и я когда‑то обещал довести его до ума. Это был знак!

Так как в этот раз я твёрдо хотел получить полный функционал, я подошёл к выбору железа недостаточно серьёзно. В качестве платформы я решил взять ESP32-Wrover-Dev. Да, на нём опять не хватало ног.. Но есть же IO expander! Если вкратце, то это микросхема, которая слушает SPI-шину и предоставляет 8 или 16 дополнительных линий контроллеру, которые, по уверениям esphome, можно использовать совсем как родные ноги. А SCL и SDA у нас уже есть, на дисплее. То есть ценой одной дополнительной ноги и небольшой задержки я смогу получить дополнительно 16 ног? Продано! Что же может пойти не так?..

Оказалось, что дьявол кроется в деталях. Действительно, экстендер может заменить собой любой GPIO. Т.е. General Purpose Input/Output. Проблема в том, что пины, которые используются, например, для описания интерфейса UART - не считаются general! То есть я могу написать так:

uart:
  id: player
  tx_pin: GPIO43
  rx_pin: GPIO44
  baud_rate: 9600

И это будет корректно. Но если я захочу использовать тут экстендер:

pcf8574:
  - id: 'extender'
    address: 0x21 #or 0x21, it depends
    pcf8575: true

# Вот так - можно
binary_sensor:
  - platform: gpio
    pin:
      pcf8574: extender
      number: 2
      mode: 
        input: true
      inverted: true
    id: button1

# А вот так уже нельзя, не компилируется!
uart:
  id: player
  tx_pin: 
    pcf8574: extender
    number: 3
    mode:
      input: false
  ...

То есть экстендером можно подменить только то, что описано как pin. А все "системные" штуки, типа шин, интерфейсов и прочего, объявляются иначе. У меня было ровно одно место, где это можно было использовать: кнопка. То есть плата, по сути, не давала ничего, потому пришлось думать дальше.

Часть 7. Итерация N+1

В этот момент я сделал то, что следовало сделать давным-давно: спросил нейросеть "контроллер esp32 с камерой и самым большим количеством доступных выводов". Из результатов поиска мне приглянулся монстр под кодовым именем ESP32-S3-CAM, вот такой

Распиновка
Заодно и с распиновкой
Заодно и с распиновкой

Тут я уже сначала посмотрел на распиновку и харастеристики. 240МГц в дверном звонке - не, ну а что, он же будет поддерживать HD и ИИ. По распиновке видно, что левая сторона будет почти полностью съедена камерой, на неё больше не смотрим. А что справа? Исключая PSRAM-ные пины, у нас 13 свободных ног! Подходит с запасом, надо брать. И только сильно позже я заметил, что у него ещё и 16Мб флеша, что означает, что в него влезет и другая анимация, покруче! А раз так - то и звуков тоже можно добавить.

Вот таблица с распиновкой компонентов:

Компонент

Пины контроллера

Примечание

Дисплей

GPIO1 (CLK), GPIO2 (MOSI)

SPI

GPIO42 (DC), GPIO41 (CS)

Линии управления

GPIO40 (RES), GPIO39 (BL)

Сброс и подсветка

DFPlayer

GPIO43 (TX), GPIO44 (RX)

UART связи с плеером

Камера

GPIO6-13, 15-18

Разведено производителем, шина данный с камеры

I2C

GPIO4 (SDA), GPIO5 (SCL)

Шина управления камерой

Кнопка

GPIO19

Подтянута вверх, замыкание на землю

Дальше - дело техники. Описал LLM задачу, мол, делаю хеллоуинскую игрушку, нужны фразы. Сгенерировал около 70 штук. Десяток из них понравился, выбрал их и перешёл к этапу озвучки. Тут мне сильно помог проект Silero (на Хабре видел статьи от @snakers4 ) прям огонь-штука: выбираешь в ТГ голос, закидываешь фразу и получаешь mp3, который просто надо переименовать и закинуть на sd-карту.

И тут я почти совершил классическую ошибку любого DIY-инженера - мы не знаем, когда следует остановиться. Я нашёл проект Uncanny eyes и очаровался им - это прям то, что нужно. И уже даже есть готовые скрипты и код, правда, под другой контроллер, но разве это проблема, когда есть Gemini и Claude, на которые молится половина разработчиков? С ними адаптировать это под мои нужды - буквально пара промптов. Что может пойти не так? Оказалось, очень многое. Почти неделю я убил на эти попытки - ничего адекватного не вышло. Сгенерировать gif по описанному алгоритму LLM не смогли. Преобразовать готовый скетч в .h файл смогли обе модели, но это приводило лишь к аварийной перезагрузке ESP при обращении к анимации. Создание кастомного подключаемого компонента получилось лишь через 3 дня попыток и борьбы с классами и компиляцией, и то - безуспешно, у меня был анимированный чёрный экран, который жрал 75% ресурсов модуля при работе. Я понял, что опять попал в ловушку "сделать идеально" и решил, что сейчас надо закончить как есть, а с кодом разбираться в следующей итерации, если она будет.

Я уже писал, что Blender я не осилил, потому работу с примитивами в моделях продолжил в TinkerCAD. Оказалось очень удобно добавлять там места для модулей и привязывать их к существующим моделям. В черновом варианте это выглядит примерно так

Корпусирование
На фоне творческого бардака и грязной клавиатуры
На фоне творческого бардака и грязной клавиатуры
Сама электронная начинка, готова к монтажу в нижнюю часть корпуса
Сама электронная начинка, готова к монтажу в нижнюю часть корпуса

Ну и вот так выглядит в сборе

Звонок в сборе
Анфас
Анфас
В профиль, обратите внимание на отверстие для быстрого доступа к SD карте
В профиль, обратите внимание на отверстие для быстрого доступа к SD карте

Часть 255. Заключительная

Это не пошаговая инструкция к созданию железки, скорее - путевые записи о том, как делать не надо. В итоге из пригоршни рассыпухи и неудержимого инженерного зуда рождается устройство "с характером" - такое, как хотелось, а не то, какое есть на рынке. Трудно? Да. Можно ли сделать лучше? Конечно. Стоило ли оно того? Более чем. И этот опыт, и решение сопутствующих проблем и недосмотров. Я не стал крутым инженером - но определённо стал круче, чем был.

Для тех, кто хочет себе такую же штуку (или улучшить мою) - прикладываю ссылку на репозиторий на GitHub. И обязательно подпишитесь на мой телеграм-канал, если я его когда-нибудь заведу.