
Разрабатываем робота с нуля - от ИИ-дизайна до полного проектирования и реализации всех компонентов устройства.
Зачем? Ради фана, конечно - этот проект практически квинтессенция моих увлечений - электроника, 3d-печать, программирование микроконтроллеров, ИИ и в целом все, что можно включить в сферу DIY.
Но если уж придумывать практическое обоснование - то было бы удобно иметь возможность из отпуска покататься по квартире и проверить, выключен ли утюг и не заливают ли квартиру соседи. Но ключевое, конечно - покататься.
Тогда
Задумал я проект, страшно сказать, 10 лет назад. У меня уже был репозиторий на github с описанием хотелок и даже некоторой реализацией. Кажется, это даже был мой первый репозиторий, который я без знаний git вел через сайт.

Построено все было на Arduino Uno и платформе DFRobot iRover 5, подключенной к драйверу двигателя с 2 УЗ-дальномерами, без камеры. Реализовать задуманное полностью не было ни сил, ни средств, поэтому проект был отложен на неопределенный срок.
Сейчас
За минувшие 10 лет изменилось многое: появилась куча возможностей, новых устройств. Появился 3д принтер (даже 3), освоена печать различными материалами, опыт программирования, умный дом и вот это вот все.
Поэтому новый план на проект выглядит следующим образом:
esp32 cam в качестве контроллера и стриминга видео;
своя прошивка с управлением по wifi и BT с передачей видео, фонариком, двумя вариантами управления, симпатичной адаптивной версткой и переключением качества видео;
свой дизайн устройства с ИИ (и дизайном вовлечение ИИ не ограничилось);
свое разработанное шасси - 2WD, возможно 4WD (технически можно хоть 6WD подключить, но смысла не имеет);
полностью 3д-печатный корпус и гусеницы из TPU;
зарядная станция, на которую можно приехать и встать удаленно;
плагин для подключения к home assistant.
Статья получилась объемной, оглавление не помешает:
Дизайн
Идею создания некоего устройства я вынашивал давно, требований было немного - камера впереди, гусеницы, wifi антенна и интересный дизайн (минимализм, футуризм, киберпанк, etc).
От дизайна, а тем более промышленного, я далек, в этом качестве на помощь были приглашены нейросетевые модели. GPT-4o генерировал вариативные промпты по моим требованиям, локально развернутый forge с flux1.dev на борту по каждому промпту генерировал 10 картинок (на 3060 Ti 8Gb 1 генерация занимает ~1 минуту).
По ходу дела и отсмотра результатов корректировались требования, но в конечном итоге было получено и использовано 150 промптов (соответственно, сгенерировано 1500 изображений). Вот пример случайных 100 изображений из всех:

Если интересно - можно ознакомиться с диалогом в ChatGpt. Все промпты, часть генераций и фото 10 финалистов можно найти в проекте на github, а я остановился на этом:

Какой-то он харизматичный, надеюсь, под его надзором все будут вести себя порядочно.
Шасси и корпус
Дисклеймер: я не имею отношения к машиностроению, робототехнике, промышленному дизайну и соответствующие дисциплины вроде сопромата и метрологии прошли мимо меня (а жаль). Поэтому при проектировании я руководствуюсь принципом избыточной надёжности.
А теперь мы будем изобретать колесо.
В качестве двигателей для небольших устройств выбор как будто не очень большой. Есть моторчики без редукторов, для которых ещё нужно его собрать - такие стоят в iRover 5. Есть с редуктором различных форматов, но с пластиковыми шестернями и выходными валами:

Ну и моторчик с полностью металлическим редуктором - GA-12-N-20, есть различные варианты по передаточным числам. Так как металлический редуктор внушает больше доверия - остановимся на таком варианте. По картинкам даже с размерами сложно было осознать, насколько он окажется маленьким:

Обычно в гусеничных шасси двигатели стоят внутри корпуса, а ролики вращаются на их осях. Но я не придумал ни как избавиться от бокового перекоса ролика при натяжении (в Rover 5 таковой присутствует) ни как удлинить ось.
Зато прикинул, что моторчик с осью и редуктором в длину соответствует планируемой ширине траков. Почему бы не сделать колесо вокруг моторчика?
Изначально я заказал подшипники минимального размера, внутрь которых бы помещался моторчик - 6802 (15x24x5мм). Но не учёл, что редуктор выступает за габариты моторчика и внутрь не поместился. Поэтому компоновка могла быть только такой:

Этот вариант не очень подходил под мои критерии надёжности, хотя и был теоретически работоспособным. Поэтому подшипники были заменены на следующие по размеру - 6803 (17х26х5мм), а эти составлены для колёс без двигателей.
Еще несколько итераций ушло на подгон зазоров для сборки и на усовершенствование внешней части колеса. Конечная схема приняла следующий вид:

Моторчик GA12-N-20, 2 подшипника 6803 (17х26х5мм) и 3 детали, распечатанные на FDM принтере (не считая кронштейн крепления к корпусу). Все держится на безлюфтовой посадке, канавки по внешнему периметру диска - одновременно для удержания гусениц и для удержания половинок колеса гусеницей.
Колесо при сборке выглядит так:

Кронштейн особого внимания не заслуживает, был смоделирован с прицелом на референсный дизайн и стыковку с корпусом. Крепление к колесу и корпусу на болтах и впаечных резьбовых втулках. Внутри канал для прокладывания кабеля питания моторчика.
Гусеницы
Шасси у нас уже есть, нужный периметр гусеницы известен, внутренний профиль определяется колёсами, внешний - дизайном.
Гусеница получилась следующего вида:

Здесь есть несколько моментов, на которые нужно обратить внимание:
Печатать нужно круглую гусеницу, чтобы в ней не было никаких напряжений и неровностей структуры, которые могут впоследствии сказаться на плавности движения.
Параметры печати подобрать таким образом, чтобы структура была одинакова по всему периметру - по той же причине.
Так как мы печатаем круглую гусеницу, а колеса у нас меньшего диаметра - нужно предусмотреть возможность гусенице беспрепятственно сгибаться до нужного радиуса. Для этого посчитаны и учтены "разрывы" во внутреннем профиле гусениц.
Изначально сделал толщину в 3мм - но напечатанное изделие оказалась избыточным даже для меня, моторчики такую гусеницу крутили с трудом и рывками. В финальном варианте толщина уже 1.2мм (3 периметра при печати соплом 0.4). Печатал тоже на FDM принтере пластиком Bestfilament TPU Soft.
3мм и 1.2мм гусеницы для сравнения:

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

Корпус
Моделирование тоже является одной из сфер, которые мне интересны, но в отличие от многих остальных - практики здесь очень мало. Поэтому на помощь здесь еще раз приходит генеративный ИИ.
Берем выбранную картинку, находим первый попавшийся AI img2model генератор (meshy) и наслаждаемся:

Ожидание / реальность, как говорится. На самом деле не все так плохо, давайте возьмем генератор получше - таких несколько, мне больше всех понравился результат от tripo3d (ссылка на модель):

Дно там, конечно, ничего общего с реальностью не имеет, но это не так чтобы важно, гусеницы кривые, ролики даже не подходят к ним вплотную, мелкие детали зашумлены и отвратительная сетка:

Но гусеницы и шасси у нас уже есть, а даже эта кривая сетка модели вполне подходит как основа для ретопологии. Ставим блендер, читаем пару вводных уроков по ретопу и пару вечеров обрисовываем модель, на выходе имеем уже нечто более похожее на реальность (сетка местами все еще ужасна, если это видят 3д-художники - не бейте сильно):

Дальше в блендере можно накинуть модификатор сглаживания сетки (можно применить несколько раз для лучшего результата), и для сравнения 2 модели - где ретоп, а где генеративная думаю понятно:

Экспортируем в stl, загружаем в Fusion 360, и долго мучаемся с подгонкой корпуса к шасси, компоновкой всех деталей внутри корпуса, проектируем все необходимые разъемы, углубления, вырезы и вот это все - пришлось повозиться.
В результате имеем подготовленные к печати модели базы и крышки:


Формы, как ни крути, получились достаточно замысловатыми, проба печати на FDM принтере вышла весьма грубой:

Помимо ступенек, с которыми что-то можно было придумать (уменьшить высоту слоя с черновых 0.28, например), были еще и проблемы с отделением поддержек и точностью форм.
По этим причинам я решился попробовать распечатать корпус на фотополимерном принтере (SLA). Сомнения были из-за характеристик напечатанных на SLA деталей - корпус должен выдерживать какие-никакие нагрузки, а детали, напечатанные на фотополимерном принтере обычно довольно хрупкие.
Также я был уверен, что смола даёт большую усадку, причём неравномерную, и опасался проблем с подгонкой. Но вопреки всему тестовая печать калибровочного куба показала отсутствие какой бы то ни было усадки и корпус был напечатан фотополимерником:

Все детали шасси я тоже перепечатал, но на FDM - решил что это проще и быстрее чем красить зеленый пластик в черный цвет (мы же ориентируемся на ИИ-дизайн).
Крышка в длину не поместилась на столе, так что пришлось повернуть, вот так оно печаталось (что-то вроде 8 часов):

При этом с первой попытки мне не хватило смолы и получился такой терминатор, что поначалу даже хотел его оставить:

Но потом все же перепечатал, а эту оставил на эксперименты. Печатал смолой ABS-Like Resin Pro 2 от Anycubuc, результат удивил прочностью (возможно, из-за толщины, хрупкая художественная печать обычно содержит тонкие элементы).
Про качество говорить не приходится - фотополимерная печать даёт практически финальное качество поверхности под покраску:

Компоненты
Не думаю, что здесь получится кого-то удивить, максимально использую готовые модули, полный список деталей таков:
Esp32cam - основной мозг - можно ещё найти под названием AI thinker.
Широкоугольная камера OV2640 с удлиненным шлейфом - угол обзора не так критичен, как длина шлейфа. В варианте с коротким шлейфом было бы сложнее придумать эстетически приятный корпус.
Wifi антенна с коннектором - часто идёт в комплекте с платой.
3 Li-Ion батареи, соединенные последовательно (3s, 12.6v) с держателями.
2 dc-dc понижающих преобразователя (на 12v и на 5v).
Драйвер двигателя 2-канальный - L298N, L293D или аналог - у меня L293D из-за меньших габаритов платы, хотя моделировал под L298N.
2 моторчика GA12-N20 с передаточным числом 150 (можно брать 200 - будет резвее). Конструкция шасси позволяет использовать до 6 моторчиков.
12 подшипников 6803 (по два на колесо).
BMS плата зарядки для 3s сборок (если планируете заряжать в устройстве).
Mosfet IRLML0030 + 1 кОм резистор для управления светодиодами.
Переключатель для подачи питания с батареи.
Светодиоды и резисторы - по вкусу. У меня в зрачках мощные 8mm белые светодиоды, для подсветки "радужки" - обычные 5mm.
Размещение компонентов внутри корпуса тоже прикидывал в Fusion 360 (и изначально подгонял масштаб модели под их размещение). Отдельное спасибо всем тем людям, которые отрисовывают модели для готовых компонентов и выкладывают в сеть (почти все нашлось на grabcad). Внутри все размещается как-то так:

Электроника
Принципиальная схема устройства:

Разместил на ней вообще все, кроме зарядной станции. Внимательные и имевшие опыт работы с esp32 наверняка заметят на схеме проблему, расскажу о ней когда будем разбирать прошивку.
Важно, что для использования внешней антенны на плате esp32 нужно запаять соответствующую перемычку, плата по умолчанию работает только со встроенной (на изображении нулевой резистор):

Изготовление платы
Esp32-cam поставляется с уже запаянной гребенкой и отладочной платой для подключения и прошивки по usb, припаиваться напрямую к плате не хотелось, а соединительные провода занимают кучу места.
Поэтому для коммутации была изготовлена своя плата (ну и mosfet с резистором размещен здесь же). Ранее делал пару плат ЛУТом, но раз мы тут осваиваем новые технологии - решил попробовать фоторезист.
Меня всегда останавливала необходимость в печати на плёнках, совмещении и засветки отдельными лампами, но в наличии есть фотополимерный принтер, который этот этап может заменить, засвечивая непосредственно плату без печати шаблонов. Подробно останавливаться на этом не буду, но важные именно для использования фотополимерного принтера аспекты опишу.
В любом случае нам нужно подготовить шаблоны платы (слои меди, шелкографии и паяльной маски). Фоторезист и маска негативне, поэтому и шаблоны соответствующие (засвечиваться будут чёрные участки):

Дальше нам нужно как-то преобразовать эти svg в формат, понятный принтеру. Мой Photon Mono X6Ks не понимает ничего кроме своего собственного формата, поэтому нужно сделать stl модель. На просторах сети есть множество различных способов с кучей софта (фотошоп, различные CAD, UVTools и вот это все) - даже инструкции выглядят достаточно громоздко.
Но у нас же тут бум ИИ, а задача выдавливания SVG в STL кажется достаточно тривиальной - напишем свою реализацию. Несколько попыток ушло на то, чтобы заставить все это работать приемлемо, в конечном итоге получилась утилита svg2stl (написана Sonnet 3.7
в Cursor). Указывается физический размер пикселя, для моего фотополимерника он составляет 0,034мм, модель с таким разрешением выглядит так (дорожки 0.5мм):

Для позиционирования платы соорудил нечто из пленки от "файла" для документов и прямоугольных пластин, оставшихся от тестирования принтера. Размещал ничего не вымеряя, на глаз по картинке с принтера (это ультрафиолет, смотреть только через очки или камеру):

Засвечивал 20 секунд, дальше все более-менее стандартно: кальцинированая сода для проявки, лимонная кислота с перекисью для отравления, раствор щелочи для смывки фоторезиста и плата получилась даже с дорожкой между пятаками в 0.3мм (3 подхода и 7 образцов ушло на понимание техпроцесса):

Паяльную маску нанес и засветил по такой же схеме. Процесс оказался настолько прост, что я решил и шелкографию сделать паяльной маской (изначально задумавался о ЛУТ). Приведу только фото платы с нанесенной шелкографией в импровизированном солярии (самодельный бокс для УФ-засветки), потому что красивое:

Компоновка всего этого железа в корпусе следующая:

Если немного раскидать, чтобы были видны основные компоненты:

Зарядная станция
Раз уж одна из целей - удалённое управление - стоит озаботиться и вопросом питания.
Батарею расширять некуда - размеры и так минимально возможные. Значит, нужна зарядка, да такая, чтобы на неё можно было встать по видеосвязи. Также нужна индикация процесса заряда и некая контактная пара. Что-то подобное зарядным станциям роботов пылесосов, но из доступных материалов.
Рассматривал варианты магнитных контактов, но ввиду кажущейся сложности реализации все же остановился на подпружиненных. В теле робота я разместил стержни, а на зарядной станции пластины. В целях экономии места пружины и индикация будут на стороне станции.
Руководствуясь все той же избыточной надёжностью были приобретены медный пруток 5мм и пластины 20х20х1.5мм. Под них и был спроектирован корпус зарядной станции:

В качестве источника энергии взял внешний блок питания для 3S батарей, подходящее гнездо для разъёма которого по счастливой случайности у меня нашлось в закромах. Детали получилось подогнать достаточно плотно, но без закусываний. Пластины на 1 пружине в углу со стороны стержня работают идеально:

Что касается индикации - была идея собрать схему с 2 светодиодами, но, честно говоря, быстро не разобрался с элементарной базой и принципиальной схемой, а время поджимало.
Вместо них взял вольтамперметр и расположил на зарядной станции аккурат напротив камеры. По его показаниям будем определять статус заряда. Небольшой ток он, правда, не показывает и ориентироваться можно либо по падению напряжения, либо, при большом разряде - уже на амперметр.
Финальный вид изделия таков:


Ну и робот, стоящий на зарядке. Фокусное расстояние камеры не позволяет видеть все чётко, но для индикации такого вида достаточно.

Картинка в трансляции выглядит так (от разрешения не сильно зависит из-за фокуса, но здесь оно низкое):

Что касается схемы зарядки самого робота - внутри стоит ещё 3S BMS плата, контролирующая заряд и обеспечивающая поддержание одинакового напряжения на банках. Можно было бы обойтись и без неё, многие заряжают 3s сборки напрямую, но я предпочитаю более безопасный вариант.

Из-за избыточной упругости пружин поставить робота на зарядку дело нетривиальное - он отпружинивает и остается без контакта, нужно ловить нужное положение. Думаю, схема на магнитах была бы легче в использовании. До её реализации я не добрался, возможно, зарядную станцию будет ждать апгрейд.
Софт
Esp32cam - довольно популярный контроллер. Управляемые по wifi девайсы с камерой - также очень популярны. Но несмотря на все это мне не удалось найти сколь угодно пригодной прошивки - чтобы и управление устройством было удобным и работа с камерой и симпатичный дизайн.
Отдельно интерфейс для камеры с кучей настроек - пожалуйста, но самое близкое, что я нашел для дистанционного управления, выглядело вот так:

Причем этот или похожий интерфейс встречается почти во всех даже самых популярных видео и инструкциях.
Железо у нас не ограничено 2 двигателями, поэтому и интерфейс будем делать свой. Что в нем будет:
настройка разрешения и качества видео, старт и остановка стрима;
2 режима управления - джойстик и слайдеры (как в танке или тракторе - 2 рычага, отвечающие за левый и правый мотор);
активация подключения к Bluetooth пульту с выводом состояния;
управление яркостью светодиодов (яркие led в глазах);
отображение значения FPS при трансляции;
адаптивная верстка и полноэкранный режим.
Прошивку я написал в Cursor c Sonnet 3.7, про процесс писать смысла не вижу, весь хабр последнее время завален статьями на тему вайб-кодинга. Скажу лишь, что с Си и программированием под esp32 у ИИ много больше проблем, чем с более популярными языками (python, js, php, etc). Без самостоятельного погружения в тему никак.
Результат получился таковым, окно настроек и режим управления джойстиком:

Режим управления слайдером:

Опишу наиболее интересные технические аспекты прошивки, рожденные коллективным бессознательным.
Разработано под Platformio с Arduino framework и с использованием FreeRTOS (инструкцию по включению функций сбора статистики я описывал в прошлой статье именно в рамках этого проекта). Т.е. все задачи (BT, стриминг, http, управление двигателями, лог загрузки) разделены на FreeRTOS задачи и распределены по ядрам (esp32 - двухядерный).
Весь UI написан в одном html файле, который при прошивке транслируется в Си, при этом сжимаясь в gzip - экономим память, ресурсы и время загрузки страницы. Фокус с gzip подсмотрел на гитхаб, но к трансляции в Си пришел сам - при использовании SPIFFS для хранения ресурсов есть дополнительные сложности в виде необходимости отдельной прошивки этих ресурсов и сложностей с OTA (прошивка по воздуху - пока не реализована). Да и наш объем html настолько мал, что не стоит отдельного хранения.
Мой BT-джойстик - это аппаратура Radiomaster TX12 (v1) с модулем ELRS, который научился прикидываться BT-джойстиком после обновления прошивки внешнего модуля - изначально хотел реализовать связь по ELSR с rx-приемником, но пока остановился на BT.

С форматом и структурой передачи данных через Bluetooth я особо не разбирался ввиду отсутствия времени и желания тратить на это время - силами ИИ по логам были определены байты из пакета, которые отвечают за X и Y левого и правого джойстика - они и были использованы для управления в тех же режимах, как в веб-интерфейсе. Если решите подключить свой джойстик - помимо изменения названия устройства в конфиге наверняка придется менять и обработку BT-пакетов.
С управлением слайдерами все прозрачно - каждый устанавливает значение двигателя в диапазоне [-1; 1]. А вот с джойстиком такая логика управления оказалась неочевидна. Поэтому мой джойстик работает так (только эту схему я делал, пожалуй, час):

Несмотря на то, что визуально джойстик круглый, зона управления - квадрат (также и на аппаратуре, кстати). Управление линейное, левый верхний угол - [1;0] (левый двигатель максимально вперед, правый стоит), средняя точка наверху - [1;1] (полный вперед), остальные по аналогии.
Есть и зоны разворота - два небольших сектора слева и справа по центру - в этих зонах двигатели вращаются в противоположные стороны так, чтобы устройство поворачивало на месте влево или вправо соответственно (тоже линейно, в крайних положениях на максимальной мощности). Центр джойстика при этом - мертвая зона. Вся эта логика реализована на стороне контроллера (пороги конфигурируются), фронтенд передает только "координаты".
MJPEG стриминг, управляющие сигналы, запрос статуса BT, смена качества картинки - все реализовано HTTP-запросами. Так как esp32 не супер производительный http-сервер, положение стиков передается при изменении с ограничением в ~200мс (т.е. при непрерывном изменении положения обновление будет улетать каждые 200 мс), при сбросе положения в 0 задержки нет.
Немного об упомянутой проблеме с изначальной схемой: я планировал замерять напряжение батареи через аналоговый вход esp32-cam - для чего на схеме изображен делитель напряжения. Увы, ADC2 контроллера задействуется при работе WiFi, причем на нем работают абсолютно все аналоговые входы, выведенные на гребенку esp32-cam. Решения, кроме установки отдельного чипа с подключением по SPI/UART, я пока не нашел - поэтому и измерения и вывода в интерфейс уровня заряда нет (что обидно - я сначала реальзовал это полностью в прошивке, включая интерфейс, и только после в логах увидел ошибку, что ADC2 уже занят WiFi соединением).

Из нюансов, которые могу отметить - скорость передачи видео (FPS) очень сильно зависит от качества сигнала wifi. Разведенная на плате антенна при этом работает, но на удалении от точки доступа работает достаточно посредственно, у меня качество плавало в зависимости от времени работы стрима (причем чем дольше стрим и горячее плата - тем лучше передача) и от наличия рядом объектов, усиливающих или блокирующих сигнал (условно, при касании рукой платы сигнал становился четким). При подключении внешней антенны эти проблемы частично ушли - не забывайте только запаять перемычку на плате для переключения на внешнюю антенну.
По итогу получилось весьма удобное управление как из браузера мышкой, так и со смартфона и физического джойстика.
Интеграция с Home Assistant
Проект в том числе позиционируется как удаленный мониторинг, но как его сделать, если устройство доступно только из внутренней сети? Вариант в лоб - пробросить доступ к устройству вовне (роутер и белый IP, туннель на сервер, сторонний сервис), но придется самостоятельно решать вопросы с безопасностью. Устройства умного дома, а тем более DIY устройства редко с этим заморачиваются, а зря.
Мы пойдем по пути встраивания устройства в существующую экосистему - у меня есть Home Assistant с публичным доступом (через свой VPS) - на него перекинем вопросы с безопасностью, нужно только реализовать интеграцию.
Казалось бы - у нас уже есть готовый html интерфейс робота с http-запросами, есть куча передовых ИИ, которые могут делать что угодно - интеграция даже без глубоких знаний python и home assistant не должна доставить проблем?

Прокси
Изначально я пошел по пути проксирования web-интерфейса робота в интерфейс Home Assistant. В результате совместной разработки получилась интеграция с следующими возможностями:
добавление и установка через HACS (пользовательский репозиторий);
для добавления интеграции нужно указать IP-адрес робота во внутренней сети;
добавляется lovelace-карточка (ui Home Assistant), которая показывает статус робота (в сети или нет) и кнопку открытия интерфейса;
в iframe открывается стандартный web-интерфейс робота, все запросы заворачиваются в самописный прокси.
Из интересного по реализации - сам прокси, который добавляет свой js-код при открытии оригинальной страницы в iframe, чтобы переопределить все url, к которым осуществляются запросы. Чем-то похоже на встраивание рекламы в незащищенный http трафик некоторыми мобильными операторами.
И все бы ничего, но мне так и не удалось ни пробросить механизм аутентификации Home Assistant через iframe, ни использовать long-lived токен (документация умалчивает как его можно проверять, все найденные варианты оказались недоступны в актуальной версии, да и светить его небезопасно) или короткоживущий authSig в контексте прокси (подписанный токен уникален для каждого url, этот вариант не очень подходит для iframe, который сам к себе делает кучу запросов).
IoT, конечно, не может похвастаться идеальной безопасностью, но в своем решении я не смог себе позволить оставить дыру в безопасности. Любой находчивый пользователь не просто смог бы посмотреть трансляцию камеры, но и поездить по дому.
Все по новой
Из уже работающего решения пришлось выкинуть почти все, что было написано, кроме проверки статуса, и реализовать свой интерфейс в Home Assistant, который без iframe взаимодействует с сервером. Такую интеграцию можно считать нативной, используется штатный механизм аутентификации, т.к. без iframe браузер прокидывает в запросы заголовки авторизации (если запросы посылать не обычным fetch, а через api hass).
Но авторизация через заголовки работает не везде - использовать в img.src для стрима такую ссылку не выйдет, на этот случай у hass есть механизм подписанных ссылок. Т.е. вы через websockets получаете подписанную короткоживущую ссылку (добавляется параметр authSig) и подставляете в контейнер для стриминга видео - подпись проверяется 1 раз при получении запроса и дальше стрим может жить вечно. После использования ссылки подпись инвалидируется, так что ссылка получается одноразовой и её перехват ничего не даст. Штатные компоненты трансляции видео с камер работают по этой же схеме.
Актуальная версия интеграции умеет не все - нет работы с BT, нет переключения режима между джойстиком и слайдером и нет отдельного включения отображения FPS - он отображается всегда.


Еще в копилку неочевидных решений hass - чтобы добавить лого для своей кастомной интеграции - нужно отправить PR в специально созданный для этого отдельный репозиторий brands. Поэтому моя интеграция пока иконкой не обзавелась, хотя она даже отрисована - впоследствии добавлю.
Заключение
Финальный вид после покраски с включенными на 1% светодиодами в глазах:

Проект занял 3 месяца почти всего моего свободного времени и не случился бы (как минимум так скоро) без Хабра. Лучший мотиватор для меня - дедлайн. Спасибо Технотексту-7, в котором я решил принять участие и который меня дедлайном обеспечил.
Статья достаточно объемная, но если вдруг какие-то разделы показались вам похожими на инструкцию по рисованию совы - еще более развернуто (по суммарному объему контента в ~2 раза) об этапах можете почитать в моем блоге на сайте (не тг) - не все опубликовано, выйдет еще ~5 отдельных статей.
Реализовать получилось в срок почти все, что хотел (кроме измерения напряжения, разве что), робот получился, на мой взгляд, очень похожим на референс, но главное - это вполне законченное устройство, готовое к использованию. Сколько у меня проектов, брошенных на середине или в начале - не перечесть (а заготовок статей в папке еще больше). Приятно, что этот проект является доведенным до финала исключением.
Ссылки
Проект - полностью opensource, всё что реализовано можно скачать и использовать:
модели для печати (stp, stl, f3z архив Fusion 360) - models/ или проект на thingiverse.
схема и чертежи платы (KiCad) - board/;
прошивка для esp32cam с web-интерфейсом - esp32-caretaker;
интеграция с Home Assistant - hass-caretaker;
утилита для конвертации svg в stl для засветки фоторезиста на фотополимерном принтере - svg2stl.
И в заключение видеодемонстрация работы, совмещенная с экскурсией по квартире, снято одним дублем и 3 камерами с 7 точек:
Для тех, кто соблюдает ограничения - видео залито на яндекс диск.
P.S. Если нашли лису - лайк за внимательность.