ESP-KVM: как я сделал IP-KVM на ESP32-P4 за копейки — и обо что споткнулся по дороге

в 9:04, , рубрики: esp32, esp32p4, ip-kvm

*Полностью открытый IP-KVM, который собирается из двух плат без единой капли припоя, прошивается прямо из браузера и умеет уже почти всё, что умеет “взрослый” KVM-over-IP. В этой статье - вся история проекта: от “сервер не грузится, а монитор на антресоли” до маленького круглого экранчика, который показывает IP прямо на корпусе.


С чего всё началось

А началось всё с того, что коллега - пусть будет Олег - забэкал на Kickstarter JetKVM. Маленький аккуратный IP-KVM, приехал, работает, красота. Я посмотрел на это как гик: с одной стороны - реально классная штука, хочу такую же. С другой - за плечами был кое-какой опыт с ESP, руки чесались, и вместо “просто купить” включилось родное “а могу ли я сам?”.

Напомню на всякий, что такое IP-KVM и зачем он вообще нужен. Это коробочка, которая видит HDMI-выход вашей машины, притворяется для неё USB-клавиатурой и мышью и отдаёт всё это в браузер. Вы садитесь за ноутбук/планшет/телефон(O_o) на другом конце квартиры (или города) и видите ровно то, что было бы на мониторе - вплоть до BIOS и меню загрузки. Никакого агента, никакой ОС на целевой машине не нужно: ей вообще всё равно, есть ли там операционка.

Сценарий, ради которого это всё, знаком любому, у кого дома живёт сервер. Стоит где-то за коробками гипервизор/NAS, и в один прекрасный момент не грузится: не пингуется, по SSH молчит, веб-морда мертва. То ли BIOS ушёл в настройки, то ли ядро запаниковало, то ли ждёт нажатия F1 после новой планки памяти. Чтобы узнать - надо тащить к железке монитор с клавиатурой. А с IP-KVM ты просто открываешь браузер.

Штука известная и, в общем, не новая: собрать KVM на ESP пытаются давно - на ESP8266, на ESP32-S3, кто во что горазд. Упирается всё в видеозахват: вытащить HDMI на копеечном микроконтроллере - задача нетривиальная.

И тут появляется ESP32-P4 - довольно свежий чип от Espressif с интерфейсом камеры (MIPI-CSI), кучей PSRAM и USB 2.0 HS. Раз есть вход для камеры - значит, к нему можно прицепить мост HDMI->CSI и получить видеозахват. За копейки.

Но дело было не только в “дешевле”. JetKVM - правда хороший продукт, придраться особо не к чему. Но именно поэтому стало интересно: а можно ли на ESP сделать не “ну, для микроконтроллера сойдёт”, а по-настоящему качественное решение - в первую очередь по софту? Хотелось довести веб-консоль до уровня, на котором её не стыдно поставить рядом с полированными коммерческими продуктами: чтобы UI был отзывчивым, аккуратным и приятным, а не “инженерным интерфейсом на минималках”.

Так, вместо того чтобы просто заказать себе JetKVM вслед за Олегом, я сел паять… то есть, забегая вперёд, - как раз ничего не паять. Так родился ESP-KVM.

Ссылки сразу, чтобы не искать:

Плата ESP32-P4-ETH с прицепленным через MIPI-шлейф мостом C790, HDMI и USB воткнуты в ноутбук - "вот эта вся коробочка"

Плата ESP32-P4-ETH с прицепленным через MIPI-шлейф мостом C790, HDMI и USB воткнуты в ноутбук - "вот эта вся коробочка"

Откуда взялся видеозахват

Оказалось, что к той же мысли - “а давайте на P4” - пришёл не только я. jrowny в проекте p4kvm уже проделал самую муторную часть: поднял мост Toshiba TC358743 и вытащил кадры из CSI-приёмника ESP32-P4.

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


Из чего это собрать

Вся прелесть в том, что паять не нужно (кроме опционального ATX - там пара проводков в готовый модуль оптопар). Нужны две платы и шлейф:

  1. Waveshare ESP32-P4-ETH (Но лучше сразу брать ESP32-P4 Function EV rev 3.2 или другую p4x) - мозг: ESP32-P4, 32 МБ PSRAM, 16 МБ флеша, 100-мегабитный Ethernet (для KVM за глаза), слот microSD и USB OTG. Плюс 40-пиновый разъём в стиле Raspberry Pi.

  2. Geekworm C790 - мост HDMI->CSI на TC358743. Превращает HDMI-сигнал в поток, который понимает интерфейс камеры P4. Держит до 1080p и даже вытаскивает звук с HDMI по I2S (пока не задействовано).

  3. Шлейф MIPI-CSI, которым эти две платы соединяются.

Плюс два USB-кабеля и HDMI. Итого - сумма, за которую готовый IP-KVM даже не улыбнётся в вашу сторону. Ладно, улыбнётся, NanoKVM. Но вы же здесь не за этим. Или за этим?

Важный нюанс с ревизией чипа. У меня плата с ESP32-P4 ревизии v1.3. От ревизии зависит очень многое, особенно скорость H.264: на свежих кремниевых ревизиях (v3.0+) встроен аппаратный конвертер цвета, и там картинка летает. На старых - нет, и это отдельная сага (см. ниже). Подробности - в HARDWARE-NOTES.


Прошивка за пять минут прямо из браузера

Вот тут я старался сделать по-человечески. Никаких idf.py flash, тулчейнов и танцев с бубном (хотя если хотите собрать сами - исходники открыты, всё описано).

  1. Заходите на https://espkvm.io/ из браузера на движке Chromium (Chrome, Edge - им нужен Web Serial).

  2. Втыкаете ESP32-P4-ETH в USB.

  3. Жмёте кнопку прошивки, выбираете COM-порт - прошивальщик сам зальёт последнюю сборку.

Причём сборка берётся прямо из релизов на GitHub, собранных в CI. То есть вы прошиваете ровно тот бинарник, который публичный и воспроизводимый, а не “доверься мне, братан”.

Дальше - физика: шлейф между платами, HDMI с целевой машины в C790, USB от неё в OTG-порт P4 (по нему устройство притворится клавиатурой и мышью), Ethernet в сеть, питание. Через несколько секунд устройство поднимется под именем espkvm.local. При первом входе логин admin/admin, и консоль сразу заставит задать нормальный пароль - причём на уровне прошивки, а не только в интерфейсе (пока дефолтный пароль в силе, сессия дотягивается только до auth-эндпоинтов, устройство нельзя рулить по проводу).

Веб-консоль: рабочий стол целевой машины, статус-бар сверху (разрешение, кодек, fps), боковая панель настроек

Веб-консоль: рабочий стол целевой машины, статус-бар сверху (разрешение, кодек, fps), боковая панель настроек

Что оно умеет сегодня

Чтобы не растекаться - честный список того, что работает уже сейчас, без “скоро завезём”:

  • Видео с автоопределением режима. Меняется разрешение на целевой машине - картинка подстраивается сама. Видно и BIOS в 640x480, и загрузчик, и рабочий стол в 1080p. Уснула машина, выдернули HDMI, сменилось разрешение при загрузке - поток восстанавливается сам.

  • Два кодека: MJPEG (простой, ~20 fps) и аппаратный H.264 (экономный по трафику).

  • Точная мышь. Абсолютное позиционирование - курсор попадает ровно туда, куда кликнули, без “уплывания” из-за акселерации на целевой стороне. Есть и относительный режим для игр/pointer-lock.

  • Полноценная клавиатура, медиа-клавиши, Ctrl+Alt+Del и другие макросы одной кнопкой, вставка текста с учётом раскладки.

  • Работа с телефона. Нормальный мобильный интерфейс с тач-трекпадом и экранной клавиатурой, а не сломанная десктопная вёрстка. Ставится как PWA на домашний экран. Но там нужно добавить CA сертификат в браузер. Потому как сертификат самоподписанный. (Можно скачать в интерфейсе)

  • Несколько зрителей одновременно, управляет один - с “перехватом управления”.

  • Виртуальный привод. Грузите целевую машину с ISO/IMG на microSD или с маленького “спасательного” образа во встроенной флешке. .iso отдаётся как CD-ROM, всё остальное - как флешка. На rev-3.x картой можно отдать целиком, на чтение-запись. Но со скоростью записи и чтения SD карты есть проблемы. К сожалению скорость минимальная. :(

  • Управление питанием (ATX). Кнопки Power/Reset материнки через оптопары + чтение индикатора питания. И Wake-on-LAN в придачу.

  • Определение ОС целевой машины по тому, как она опрашивает USB-дескрипторы.

  • HTTPS с самоподписанным сертификатом (или своим), логин с PBKDF2, физический сброс пароля кнопкой, обновление прошивки по сети с откатом, тепловая защита, диагностика.

  • Home Assistant через MQTT - авто-обнаружение сенсоров и кнопок питания.

  • VPN: WireGuard или нативный Tailscale (об этом ниже - там тоже интересно).

Чего пока нет: звука с HDMI - мост его отдаёт, но для KVM это низкий приоритет (подробнее в конце).

Устройство в Home Assistant - все значения приходят вживую по MQTT, обнаружены автоматически

Устройство в Home Assistant - все значения приходят вживую по MQTT, обнаружены автоматически

А теперь про грабли

Расскажу обо что именно я споткнулся.

H.264 упёрся не туда, куда я думал

Логика подсказывала: аппаратный кодер H.264 -> мало трафика -> много кадров. На практике на моей ревизии чипа (v1.3) в блоке H.264 нет конвертера цвета, и кадр приходится гонять через отдельный аппаратный преобразователь RGB->YUV (PPA). А он упирается в пропускную способность PSRAM: конвертер таскает ~9 МБ на кадр, и когда он работает параллельно с энкодером, они дерутся за шину памяти.

Итог: 1080p по H.264 на старом кремнии выдаёт около 7 fps - довольно печально. Я даже развёл конвертацию и энкод на две задачи через два YUV-буфера в надежде распараллелить - прирост оказался копеечным (~6.7 -> ~7.3 fps), потому что бутылочное горлышко не в вычислениях, а именно в памяти. Поэтому MJPEG со своими ~20 fps остался основным вариантом, а H.264 - для узких каналов.

На rev 3.0+ эта проблема снимается встроенным конвертером - и там H.264 уже 22 fps. Но это уже другая плата, и до неё мы ещё дойдём.

microSD на этой плате - отдельная сага

На дефолтных 20 МГц карта нормально читает одиночные секторы: смонтировалась, показала каталог - вроде живая. Но валит любое многоблочное чтение - а именно так её читает USB-хост. То есть целевая машина образ прочитать не могла в принципе, при внешне рабочей карте.

Помогло только опускание шины до 4 МГц (медленно, ~1.5 МБ/с, зато читается 200 МБ без единой ошибки) и отказ от UHS/DDR-режимов. Запись на старом кремнии так и осталась ненадёжной, поэтому там карта только на чтение, а образы готовятся во внешнем картридере. На rev 3.x запись работает штатно - проверено на железе. Это известное ограничение ESP32-P4, а не мой косяк (я себя в этом убедил).

Кнопка сброса пароля чуть не убила сеть

Физический сброс пароля я повесил на кнопку BOOT (GPIO 35). Оказалось, этот пин делит функцию с Ethernet, и стоит захватить его после подъёма сети - сеть умирает намертво: устройство живёт, логи идут, но не отвечает даже на ARP. Пришлось читать кнопку ровно один раз, в узком окне сразу после старта, до инициализации Ethernet, и тут же отдавать пин обратно. Нашёл, как водится, не с первого раза.

Два устройства с дефолтным именем ссорились сертификатами

Устройство при первом старте само становится маленьким удостоверяющим центром, выписывает себе сертификат и подписывает его. Красиво - пока у вас не появилось два устройства с дефолтным хостнеймом espkvm: их самоподписанные CA получали одинаковое имя (CN), и, доверившись одному, браузер отвергал другой с ERR_CERT_AUTHORITY_INVALID. Лечится добавлением в имя CA суффикса из MAC-адреса и авто-перевыпуском.

Бонус: WebSocket, которого не было

Один из самых обидных. В какой-то версии в браузере вдруг отвалились H.264, клавиатура и мышь - при том, что все REST-роуты работали. Оказалось, таблица обработчиков HTTP-сервера была ровно на единицу меньше нужного: эндпоинты ATX её переполнили, а /video и /ws регистрировались последними - их-то молча и выкидывало. Поднял лимит с запасом.


Как проект оброс сетью: WiFi, Tailscale и WireGuard

KVM без нормального удалённого доступа - не KVM вовсе. Поэтому дальше история пошла в сторону сети.

WiFi. У ESP32-P4 нет своего радио. Но на плате ESP32-P4 Function EV рядом стоит ESP32-C6, и P4 общается с WiFi через него по SDIO. Появился выбор линка - Ethernet / WiFi / собственная точка доступа, - со сменой на лету из статус-бара. Плюс спасательный хотспот: если устройство не достучалось до своей сети, оно поднимает свою ESP-KVM-xxxx, а само продолжает ломиться в сеть и переподключается, как только та вернётся. И captive-портал - подключился телефоном к точке, консоль открылась сама, как в отеле.

Tailscale, нативно. Хотелось дотягиваться до устройства из любой точки без проброса портов и VPS. Классический вариант - WireGuard-клиент (split-tunnel, ключ генерится на устройстве, только свой туннельный адрес идёт через VPN). Но я пошёл дальше и прикрутил нативный Tailscale: устройство само вступает в tailnet, получает 100.x-адрес (или MagicDNS-имя), доступный откуда угодно, а обход NAT (через DERP/DISCO) берёт на себя. Под капотом - нативный ts2021-клиент (microlink), портированный на ESP-IDF 6. Сертификат консоли при этом перевыпускается так, чтобы быть валидным и по адресу в tailnet. Работает и с self-hosted control-сервером (Headscale/Ionscale).

Оба VPN-бэкенда живут на одном WireGuard-стеке и выбираются в рантайме - это отдельно пришлось разруливать, чтобы символы двух реализаций не конфликтовали в одной прошивке.


Вторая итерация железа: приехала новая плата

“Бутерброд из двух плат на проводах” просился в нормальный форм-фактор, а старый кремний v1.3 держал H.264 за горло. Поэтому я очень ждал плату ESP32-P4 Function EV ревизии 3.2 - со свежим кремнием, где H.264 наконец должен полететь.

Она приехала.

плата ESP32-P4 Function EV rev 3.2 ещё в упаковке - только приехала

плата ESP32-P4 Function EV rev 3.2 ещё в упаковке - только приехала

И новый камушек подтвердил ожидания. На rev 3.x картинка захватывается сразу в YUV422 прямо в оба энкодера, минуя проход цветоконвертации: это освободило ~4 МБ PSRAM и поставило H.264 и JPEG на один формат. Плюс третий буфер захвата, чтобы энкодер не ждал камеру. Результат: 1080p по H.264 поднялся с ~15 до ~22 fps, а на 720p - под 28-34. H.264, который на старой плате еле полз, здесь наконец стал юзабельным. Причём старый путь для rev <3.0 я аккуратно не трогал - обе платы продолжают работать.

Немного подкрутил прошивку и вот уже 21 fps

Немного подкрутил прошивку и вот уже 21 fps

Заодно у P4 Function EV есть ESP32-C6, ради которого и появился весь WiFi-функционал. Короче нормальный получился апгрейд. Не понятно только почему Espressif выпускает эту плату ка P4 хотя камушек при этом знатно переработан.


Маленький экранчик (IYKWIM)

Мне прилетел вполне резонный feature-request: когда устройство работает как “безголовая” коробочка где-то рядом с сервером, найти его IP - это лезть в DHCP-таблицу роутера, а проверить, что HDMI-сигнал ловится, - открывать веб-консоль. А хочется просто посмотреть на саму железку и всё понять.

Самое прекрасное в этом запросе было то что именно в этот момент я уже был на замершающем этапе добавления поддержки этих oled экранов.

Так в v0.21.0 появился маленький статус-дисплей. Втыкаете в пины крошечный OLED (SSD1306 или SH1106 - они авто-детектятся на той же I2C-шине, что и мост захвата), включаете в настройках - и устройство показывает прямо на панели свой IP, состояние линка, статус захвата и здоровье: циклом страницы с иконками и прогрессбарами температуры/RAM. Работает мимо энкодера, так что на стриминг не влияет. (Уже не влияет :) )

Монохромный OLED SSD1306/SH1106 на I2C: устройство показывает свой IP и статус прямо на корпусе

Монохромный OLED SSD1306/SH1106 на I2C: устройство показывает свой IP и статус прямо на корпусе

А потом я не удержался и добавил поддержку круглого цветного LCD GC9A01 - того самого “часового” кругляша. Он не только красивее, но и показывает больше: тот же статус плюс, например, адрес в Tailscale.

Круглый цветной дисплей GC9A01 со статусом ESP-KVM - IP, линк, температура

Круглый цветной дисплей GC9A01 со статусом ESP-KVM - IP, линк, температура

Оба варианта включаются одной галкой, а пины назначаются прямо из консоли: в этом же релизе настройки пинов стали выпадающими списками свободных GPIO, появилась вкладка Pins с полной картой (что зарезервировала плата, что вы назначили, что свободно), и ATX-пины тоже переехали на эту карту - чтобы ничего молча не столкнулось.

Как круглый GC9A01 выглядит вживую - на видео (без звука, просто короткая демонстрация):

https://www.youtube.com/watch?v=-snDjOOjbSA


Немного о безопасности

KVM - это полный доступ к машине, поэтому выпускать его в сеть “как есть” нельзя. Что сделано:

  • HTTPS с самовыпущенным CA (импортируете корневой один раз - браузер перестаёт ругаться, а заодно включается H.264, которому WebCodecs требует “защищённого контекста”). Можно и свой сертификат подсунуть.

  • Логин с PBKDF2-хешем пароля, ограничение перебора и обязательная смена дефолтного пароля - на уровне прошивки.

  • Физический сброс пароля кнопкой - логика простая: кто может дотянуться до кнопки, тот и так может выдернуть питание.

  • Весь интерфейс самодостаточный: никаких внешних CDN, шрифтов и запросов наружу. Устройство обязано работать в изолированной сети.

Не выставляйте это в публичный интернет. Логин и TLS есть, но security-review проект не проходил, а железка, держащая клавиатуру на чужой машине, стоит для атакующего дороже почти всего остального в сети. Держите её в доверенной сети или дотягивайтесь через VPN/Tailscale.


Что дальше и как поучаствовать

Из очевидного, чего пока нет, - звук с HDMI. Руки до него не доходят, для KVM звук - штука не то чтобы сильно важная (ну правда, часто вам нужно слышать зависший BIOS?), а вокруг хватает задач куда интереснее. Так что лежит и ждёт настроения. Полный roadmap - в репозитории.

С корпусом, кстати, тоже пока не очень. Я начал было рисовать его под Waveshare-“бутерброд” - а тут приехала плата совсем другого форм-фактора, и весь чертёж отправился в стол. Так что корпус пока отложен: смысла вылизывать коробку под железо, которое на глазах меняется, немного. Сначала - определиться с “эталонной” платой, потом уже одевать её.

Проект живой и открытый (Apache-2.0). Если хочется:

И напоследок - ради чего всё затевалось. Представьте: дома или на даче стоит машина, к которой в норме никто не подходит - NAS, медиасервер, ретро-ПК. Что-то пошло не так - а вы с телефона из метро заходите, видите зависший BIOS, жмёте пару клавиш, при необходимости монтируете загрузочный образ и переставляете систему. Или чините компьютер родителям в другом городе, не объясняя им по телефону, “какую кнопочку нажать”. Вот ради этих сценариев оно и делалось.

Спасибо, что дочитали. И если проект окажется полезным - скиньте ссылку на статью/проект другу-гику и звёздочку в репе поставьте если не жалко :)

Автор: Dexif

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js