Большинство настольных гаджетов просто показывают информацию. Мне хотелось такого, который реагирует на людей: робота, который смотрит на вас, когда вы говорите, отвечает вам и делает что-то забавное, когда рядом никого нет. Для этого у Espressif ESP-VoCat v1.2 есть всё необходимое аппаратное обеспечение. В нём объединены SoC ESP32-S3, круглый сенсорный экран 360×360, два микрофона, динамик и аккумулятор в корпусе с кошачьими ушками.
VoCat Eyes — это лицо, которое я написал для него, и первый робот из серии Cute Desktop Robot. Его основные возможности:
- 56 выражений лица и 47 коротких анимаций
- Небольшой конечный автомат, который решает, чем занимается робот: играет, реагирует, скучает, засыпает
- Глаза, поворачивающиеся к говорящему человеку
- Голосовые беседы через Gemini Live API, запускаемые по слову активации «Hi ESP» или касанием
- Настройки Wi-Fi, громкости, яркости и голоса на экране самого робота
- Эмулятор, который запускает тот же код на ПК и на этой странице
Прошивка, эмулятор и инструменты имеют открытый исходный код в репозитории cute-desktop-robot.

На изображении выше показан 3D-вид эмулятора: модель корпуса, опубликованная Espressif, с живым экраном эмулируемого робота внутри.
А вот настоящий робот на столе, работающий на той же прошивке:
Робот на реальном аппаратном обеспечении: посмотрите на YouTube.
Попробуйте робота в браузере
Эмулятор ниже запускает C++-код робота, скомпилированный в WebAssembly: тот же движок лица, конечный автомат и экраны, что работают на ESP32-S3. Заменён только аппаратный слой — на симулированные компоненты. Это не требует ни аккаунта, ни установки. Только голосовая беседа требует настоящего робота, поскольку у эмулятора нет сети.
Если окно слишком маленькое, откройте эмулятор в полном окне.
Окно состоит из трёх частей. Слева — робот в 3D, под ним — конечный автомат, а справа — панель с графом эмоций и датчиками. Чтобы попробовать:
- Коснитесь экрана робота. Кликните один раз по круглому экрану 3D-робота. Робот проиграет следующую анимацию.
- Откройте настройки. Нажмите и удерживайте экран, пока не появятся часы и индикатор батареи, затем кликните один раз по часам. Откроется меню настроек. Кликните по левой половине строки, чтобы уменьшить значение, и по правой — чтобы увеличить. Нажмите и удерживайте, чтобы закрыть меню.
- Говорите с одной стороны. На правой панели в разделе SENSORS перетащите ползунок рядом с from на нужный угол, затем нажмите talk [V]. Фиолетовая точка на полу покажет, откуда исходит симулированный голос, и глаза робота повернутся к ней.
- Наблюдайте за конечным автоматом. Каждый раз, когда робот меняет активность, подсвечивается соответствующий блок в конечном автомате, а полосы внизу показывают таймеры, которые могут изменить её следующими.
- Поверните робота. Перетаскивайте в любом месте за пределами экрана, чтобы вращать 3D-вид, и используйте колесо мыши для масштабирования. Нажмите
M, чтобы переключиться на плоский вид экрана.
Клавиатура тоже работает после клика по эмулятору: 1–9 показывают выражения, Y — кивок, U — покачивание головой, а H — речевой пузырь. Касание и настройки работают так же и на настоящем роботе.
На следующем скриншоте показано всё окно эмулятора, как оно выглядит на настольном ПК:

Аппаратное обеспечение
ESP-VoCat поставляется в собранном виде — с корпусом, аккумулятором и экраном на месте. Руководство пользователя ESP-VoCat подробно описывает плату. Всю работу выполняет один модуль ESP32-S3. У него 16 МБ флеш-памяти и 16 МБ PSRAM (внешней оперативной памяти). Именно PSRAM делает возможным лицо 360×360: один полный кадр в 16-битном цвете занимает 253 КБ — больше, чем может вместить внутренняя память SoC.
На следующей схеме показаны компоненты, которые использует прошивка, и шина, соединяющая каждый из них с ESP32-S3:
Два компонента потребовали дополнительной работы:
- Экран включал подсветку, но оставался чёрным. В последовательности инициализации, которую я адаптировал из драйвера производителя, не хватало двух финальных команд: формата пикселей (
0x3A) и включения дисплея (0x29). - Микрофоны сначала возвращали только нули. АЦП микрофонов (ES7210) и ЦАП динамика (ES8311) используют одни и те же тактовые линии I2S. Поэтому шина I2S должна использовать одинаковый формат кадра в обоих направлениях: TDM (мультиплексирование с временным разделением) с четырьмя 16-битными слотами. После этого изменения два микрофона появляются в слотах 0 и 2, а слот 1 несёт копию сигнала динамика для эхоподавления.
Как робот рисует своё лицо
Робот перерисовывает своё лицо каждые 33 мс — примерно 30 раз в секунду. Схема ниже прослеживает один из таких кадров от датчиков до экрана:
Каждый блок на схеме — это один модуль прошивки:
- Sensors собирает касания, уровень заряда батареи, время и звуковые события в одну структуру.
- Brain — это конечный автомат с состояниями awake, show, reacting, dozing, sleeping, info и manual. Каждое состояние покидается по определённым событиям, таким как касание, громкий шум, время суток или долгое одиночество. Brain также отслеживает медленные настроения: робот, которого часто гладят, склоняется к весёлым лицам, а тот, кого слишком часто тыкают, становится раздражённым.
- FaceEngine перемещается между выражениями по графу. 56 выражений — это его узлы, и смена лица идёт по кратчайшему пути, поэтому робот переходит от радости к грусти через промежуточные лица. Каждый глаз описывается несколькими параметрами, такими как размер, радиус скругления, наклон, веки и цвет, и движок интерполирует их.
- Canvas рисует кадр в PSRAM и записывает, какие плитки 24×24 изменились. Моргание меняет несколько плиток, поэтому шина дисплея отправляет несколько килобайт вместо полных 253 КБ.
- VocatLcd отправляет изменённые плитки на дисплей ST77916 по QSPI (четырёхлинейной шине SPI).
Все выражения и анимации
Эмулятор включает инструмент командной строки facetool, который отрисовывает экран робота без открытия окна. Видео ниже созданы с его помощью. В каждом из них одновременно проигрываются все лица одной группы: сначала переход к каждому лицу, затем само лицо.
Выражения
Спокойные и задумчивые (16): обычное, любопытное, сосредоточенное, скептическое, скучающее, растерянное и слушающее, среди прочих.
Радостные (15): от лёгкой улыбки до смеха, влюблённости и восхищения.
Грустные (8): обеспокоенное, умоляющее, плачущее, одинокое и виноватое, среди прочих.
Злые (6): от раздражённого до взбешённого, а также отвращение.
Удивлённые и настороженные (7): удивлённое, испуганное, головокружение, нокаут и гипноз, среди прочих.
Сонные (4): сонное, зевающее, спящее и уставшее.
Анимации
Анимации, называемые в коде «актами» (acts), — это короткие сцены, многие с объектом, нарисованным рядом с глазами: дождевая туча, чашка кофе, экран радара. Мозг проигрывает их, когда робот бездействует, а касание экрана запускает следующую.
Язык тела (9): кивок, покачивание головой, закатывание глаз, чихание.
Экранные эффекты (5): глитч, перезагрузка, линия сканера, падающий код.
С объектами (18): сердечки, дождь, фейерверк, пузыри, мяч, танец.
Повседневные вещи (9): книга, наушники, кофе, ракета, снег.
Игры и эффекты круглого экрана (6): Pac-Man, Pong, радуга, радар, аквариум.
Те же сетки в виде анимированных GIF-файлов есть в README репозитория.
Как робот разговаривает
Для беседы используется Gemini Live API, которое принимает поток аудио и отвечает потоком аудио через одно WebSocket-соединение. На момент написания статьи робот использует модель gemini-2.5-flash-native-audio. На схеме ниже показан путь аудио:
Робот отправляет звук с одного микрофона как PCM-аудио с частотой 16 кГц, фрагментами по 100 мс. Gemini отвечает речью с частотой 24 кГц, которую робот преобразует в 16 кГц и воспроизводит.
Беседа ведётся в полудуплексном режиме: робот либо слушает, либо говорит, но никогда не делает это одновременно. Динамик находится в нескольких сантиметрах от микрофонов, поэтому робот отключает поток с микрофона, пока воспроизводит ответ. Иначе Gemini услышал бы собственный голос. Gemini на своей стороне решает, когда человек закончил говорить.
Gemini также получает четыре инструмента — функции, которые он может вызывать во время беседы: set_face, play_act, look и show_text. Когда разговор становится грустным, Gemini может вызвать set_face("sad"). Когда вы просите робота согласиться, он может вызвать play_act("yes"), и робот кивает. Каждая сессия начинается с короткого текстового сообщения, содержащего местное время и дату, чтобы Gemini мог отвечать на вопросы о них.
Три проблемы заняли большую часть времени разработки:
- Защищённое соединение. Робот подключается к Gemini по TLS. Функция
setInsecure()библиотеки ArduinoWebsockets не действует на ESP32-S3, поэтому TLS-рукопожатие завершалось неудачей. Теперь прошивка включает два корневых сертификата Google (GTS Root R1 и R4, действительны до 2036 года). Она также ждёт синхронизации часов по NTP, поскольку для проверки сертификата нужна текущая дата. - Внутренняя RAM. Активационное слово «Hi ESP» работает на ESP-SR, библиотеке распознавания речи от Espressif, которая занимает около 100 КБ внутренней RAM. Библиотеке TLS нужна внутренняя RAM для рукопожатия, и первые попытки завершались ошибкой
SSL - Memory allocation failed. Теперь робот останавливает ESP-SR при начале беседы, что освобождает 107 КБ, и загружает её снова, когда беседа заканчивается. Загрузка ESP-SR занимает около 1,2 секунды. - Отсутствующий поток с микрофона. В течение одного дня робот приветствовал пользователя, а затем игнорировал всё последующее. Его глаза по-прежнему следили за моим голосом, так что микрофоны явно работали. Причина была в том, что прошивка так и не включала аудиопоток к Gemini. Теперь последовательная консоль выводит транскрипцию того, что услышал Gemini, так что та же проблема была бы видна сразу.
Для использования Gemini нужен ключ API из Google AI Studio, а API оплачивается по факту использования согласно ценам на Gemini API от Google. Ключ хранится в локальном файле secrets.h, который никогда не попадает в репозиторий.
Настройки на экране робота
После настройки роботу не нужен компьютер. Долгое нажатие на экран показывает часы, уровень заряда батареи и состояние Wi-Fi. Нажатие на часы открывает настройки, где можно изменить:
- Громкость и яркость экрана
- Голос Gemini — на выбор из восьми голосов
- Активационное слово «Hi ESP» — включено или выключено
- Звуки, сопровождающие выражения лица
- Воспроизводит ли робот и засыпает ли он самостоятельно
- Сколько секунд тишины завершают беседу
Строка Wi-Fi открывает список сетей в зоне действия и экранную клавиатуру с заглавными буквами, цифрами и символами. ESP32-S3 не может сканировать сети, пока подключается к одной из них, поэтому прошивка приостанавливает попытки подключения во время сканирования и возобновляет их после. Если пароль неверный, экран часов показывает это как причину. ESP32-S3 поддерживает только сети 2,4 ГГц.
Одна кодовая база для робота, ПК и браузера
Код робота не обращается напрямую к драйверам дисплея, сенсора или аудио. Лицо, Brain и экраны проходят через тонкий слой аппаратного обеспечения в src/hw и src/audio. Эмулятор заменяет этот слой, поэтому остальной код собирается без изменений для трёх целей:
В эмуляторе небольшие заменяющие версии Arduino.h, Wire.h и SPI.h пишут в структуру, которая замещает аппаратное обеспечение. Она хранит память дисплея, регистры контроллера сенсора и показания батареи. Окно эмулятора, написанное с использованием графической библиотеки raylib, отрисовывает эту структуру и позволяет изменять значения датчиков.
Браузерная версия — это та же программа эмулятора, скомпилированная с помощью Emscripten, который превращает C++ в WebAssembly. Это стало возможным благодаря трём изменениям:
- raylib для веба. raylib собирается с Emscripten в статическую библиотеку, которая рисует через WebGL.
- Один кадр за вызов. Настольная версия работала в бесконечном цикле. Этот цикл стал функцией, которая рисует один кадр. Настольная версия вызывает её в цикле, а браузер вызывает её один раз за обновление экрана.
- Фиксированный размер окна. В браузере эмулятор рисует в canvas размером 1380×900, а страница масштабирует canvas по размеру. Изменяемый размер canvas нарушал соответствие между координатами мыши и canvas.
3D-корпус взят у Espressif. ESP-College опубликовал корпус ESP-VoCat (EchoEar) в виде STL-файлов на OSHWHub под лицензией GPL-3.0. Короткий скрипт на Python преобразует STL-файлы в один файл 3D-модели и снижает его детализацию, чтобы браузер загружал его быстро. Эмулятор рисует живой экран на диске в круглом окне корпуса. Когда вы кликаете по 3D-экрану, эмулятор находит точку клика на этом диске и превращает её в касание в соответствующем пикселе.
Соберите своего
Чтобы собрать VoCat Eyes, вам нужна плата ESP-VoCat v1.2 и, на момент написания статьи, версия 3.3 ядра Arduino для ESP32. В README VoCat Eyes перечислены библиотеки и точные команды. Кратко:
- Скопируйте
secrets.example.hвsecrets.hи добавьте свой ключ API Gemini. Там же можно указать сеть Wi-Fi или задать её позже на роботе. - Скомпилируйте скетч со схемой разделов
esp_sr_16и файломpartitions.csvв папке скетча. Такая разметка даёт приложению 6 МБ флеш-памяти. - Загрузите прошивку вместе с
srmodels.bin— файлом модели для активационного слова «Hi ESP». - На роботе нажмите и удерживайте экран, коснитесь часов, выберите Wi-Fi, выберите свою сеть и введите её пароль.
- Скажите «Hi ESP» или коснитесь маленького микрофона внизу экрана и начинайте говорить.
Если вы разрабатываете прошивку для ESP32-S3 или других плат, Velxio может смоделировать её в браузере, прежде чем вы прошьёте какое-либо оборудование.