Так как в первой статье я сосредоточился на схемотехнике, в этой хотелось бы пройтись по разработке ПО и прошивки.
Напомню, я решил, что вместо прошивки мне подойдёт и нейрослоп - в конце концов это не серийное устройство, а поделка для себя. И тут, как обычно, в процессе работы пошли фейерверки.
ESP32-S3 имеет два USB выхода - обычный через микросхему CH340, которая конвертирует USB в UART, и второй USB OTG, который подключен к чипу напрямую. То есть чип может управлять тем, какое "устройство" он предоставит хостовой ОС! Более того, он может предоставлять несколько устройств одновременно. Это было как раз то, что надо: одним устройством будет USB HID клавиатура для "сырого" ввода в консоль, например, а вторым - USB RAW устройство, которое будет слушать софт бекенда - запускать приложения по ярлыкам, регулировать громкость, отправлять в устройство настройки. И всё это минуя тормознутый UART. Сказка. Но, как оказалось, для взрослых.
Казалось бы, полный интернет примеров того, как это настраивать. Даже в самом фреймворке esp-idf есть пример композитного устройства, значит, информация уж точно легкодоступная, бери да пользуйся. Я попросил ИИ добавить это в прошивку и... Оно не смогло. То есть вообще. Два дня и токенов примерно на 30 баксов - и я взял дело в свои руки, потому что ИИ выдумывал всё более и более изобретательные причины того, почему прошивка не компилируется, но вот выдумать компилирующуся прошивку не мог никак.
Я слышал, что Sonnet очень хорошо справляется с кодом, и он действительно быстро накидал красивый нерабочий boilerplate. Вообще, у меня сложилось впечатление, что модельки обожают писать md файлы вместо реальных результатов - у меня этих md-шек как у дурака фантиков.

Видимо, "место проклято". Иными словами я не могу обьяснить тот факт, что, имея доступ ко всей документации, железке для тестирования и примеру, написанному мной - нейронка не могла повторить составное USB устройство. Уже даже я понял (с), а она - нет. Этот барьер удалось взять только с переключением на Opus и тыканьем его носом в пример, раза с третьего.
До этого тоже было весёлое: freertos тоже вполне себе известная, документации по ней предостаточно. Однако сделать так, чтобы устройство не валилось набок при старте из-за срабатывания watchdog, удалось только на третий день. До этого стабильный крэш из-за того, что таски инициализации профилей занимали секунды (!), что для реалтаймовой ОС слегка перебор, особенно в блокирующем режиме. Я уж молчу про "ой, что-то сигнатуры вызовов у тебя тут не те, что в моей обучающей выборке, давай я перепишу 80% кода сейчас, а вторые 80% на следующем шаге".
Впрочем, если оставить эти придирки, то результат ошеломительный. За неполную пару недель (напомню, это пет-проект, которым я занимаюсь от силы пару часов по вечерам) я получил готовую прошивку, которая:
имплементирует кастомный протокол работы с будущим бекендом
командует экранами и подсветкой
читает состояние кнопок и энкодера - и это по прерываниям, а не в цикле (было жесткое требование)
имеет кастомный VID, PID и текстовые строки внутри (потому что хочу)
вторым устройством реализовывает HID-клавиатуру, причем успешно
Как кодогенератор пет-проекта - восхитительно. Но на что-то более серьёзное я бы её не поставил, страшно. И дорого. Я тут совершил классическую ошибку, дав права Roo Code дебажить. Три прохода чтения логов загрузки устройства - $80 на токены. Тут я слегка пришёл в чувство и стал фильтровать то, что отправляется в LLM, а заодно сам стал сильнее контролировать процесс.
И да, примерно в этот момент финансовая часть проекта окончательно перестала сходиться. Точнее, перестала бы: у меня есть доступ к Claude от компании, потому стоимость кодирования для меня была нулевой. Я, конечно, старался сильно не наглеть, но получалось не всегда. И как "железка-на-коленке" то, что изначально задумывалось ради экономии, полностью провалило эту цель. Но погоню за опытом и самореализацию в виде хобби было уже не остановить...
Через месяц после старта кодинга я сменил модель с Sonnet 4.6 на Opus 4.6 - и, чёрт возьми, это стало заметно. Именно в этот момент работа пошла плавно: Opus делал точечные правки вместо "лепёшечных" от Sonnet, задавал правильные контрвопросы и вообще начало казаться, что я говорю с джуном, а не с лоботомированной обезьяной. К этому моменту core-функционал был почти готов, до проверки гипотезы оставалось всего пара шагов, и именно Opus мне помог их сделать за вменяемое время. Это давно забытое ощущение "да, я смог!", когда подключаешь USB шнурок к железке, замыкаешь пинцетом контакты - и оно пишет в открытый текстовый документ "Hello world", мгновенно окупило все ранние мучения.
Фронт (то, что не прошивка)
Ранние версии фронта (это что-то в райное v0.3)



Моим первым языком (исключая Basic на ZX Spectrum) стал C#, потому в наивной надежде хотя бы минимально сохранить контроль над кодом я выбрал его в качестве основного языка для разработки той части решения, которая будет крутиться на компе. Плюс достаточно давно .net стал кроссплатформенным, так что и результат можно будет запустить где-то ещё, и мне для работы не надо будет выныривать из привычной WSL. Да, usbip (которым я пользуюсь для пропихивания USB устройств в линукс-вм) - не самая удобная в мире штука, но эту боль я готов был терпеть. Я дал Sonnet несколько референсов, описал базовые требования к функционалу и послал его генерировать очередные очень важные md-файлы, а сам пошёл к гопоте с вопросом об интерфейсах.
Вообще, за многие годы в айтишке я приобрёл титаново-вольфрамовую уверенность, что админов, девопсов и вообще людей без профильного опыта надо расстреливать на месте за попытки задизайнить интерфейс. Это люди, которым "лишь бы работало", а удобство после многих лет в bash или поиске выхода из vim уже не нужно. И да, я тоже из их числа, но я хотя бы отдаю себе отчёт в этом и стараюсь быть лучше хоть иногда. Время от времени мне приходится писать что-то для личного пользования, и, когда не стараешься, выходит примерно так
Дёшево и очень сердито

А уж сколько я повидал на своём веку интерфейсов в виде генераторов генераторов генераторов Jenkins-джобов или панели внесения изменений в БД на основе GitHub Actions... Короче, это было то, что я ни в коем случае не хотел повторять. Тут без компромиссов - я делаю для себя, и мне должно быть красиво и удобно, или оно не должно быть совсем. Точка. Примерно с таким потоком сознания столкнулась гопота перед тем, как крякнуть и зависнуть посоветовать мне потыкать палочкой Avalonia. Я почитал и мне внезапно понравилось. В двух словах - это рисовальщик интерфейса, в чем-то похожий на декларативный HTML, а не на WinForms, к которым я привык. В теории обещалась возможность настроить буквально всё, и я на неё купился. Дальше всё пошло как обычно...
Готовых решений, которые бы меня на 100% устроили, нет. В целом, я их и не ждал, думал, что можно будет своими руками что-то настроить, это же должно быть просто. В итоге я разрывался между функциональностью, дизайном и, что немаловажно, попытками ничего не упустить в архитектурном плане, так что хотелки пришлось придушить. В визуальном стиле меня всегда привлекал киберпанк, Tron и Fallout. Первый вариант пока отпадает, стиль второго на коленке реализовал с помощью DeepSeek, а по Fallout есть офигенно атмосферный Pipboy.Avalonia. Остановиться я решил на самодельном таки интерфейсе, потому что переключение между TRON и PipBoy LLM так и не осилила нормально сделать, а мне и так нравится.
Интерфейс в стиле Tron

"Да, вы верно подметили, я сделал херню"
В некоторых моментах модель адски, космически, хтонически тупа. Это заставляет прожигать стулья, если нет опыта жизни с маленькими детьми. Хотя даже с ними не сильно проще. Пример:
первая сборка, первый старт. Проверяю то, что напаяно на плате (адресные светодиоды WS2812). Ничего. И так, и эдак, и растак - ну не пашет оно. LLM разводит ложноножками. Пишу руками из интернетов тестовый скетч, который мигает диодами - диоды работают. Оформляю его в example, тыкаю носом, говорю - "вот рабочий пример, сделай так же". О, чудо из чудес! LLM радостно бросается что-то там генерить и диоды загораются. Почему? "Да, вы абсолютно правы, в файлах была ошибка. В блоке управления диодами было реализовано только return true" (на минутку, до того было два полных цикла промптоп вроде "прочитай весь проект, найди TODO и нереализованные вещи и сделай их перечисление")
С другой стороны, есть и обратный пример:
Начинаю тестировать SPI экраны. Очень туго идёт - максимально, что добиваюсь, это еле видимого мерцания дисплея. Причём подсветка включается, это видно. Я так же гоняю примеры из интернетов, трачу два вечера - никак. В комментариях читаю заветное "Код инициализации отличается от стандартного, написал продавцу - он дал пример, с ним работает" - оппачки. Копаю сайт производителя, нахожу незаметную кнопку с документацией, качаю - там архив с чем-то. "Что-то" оказывается блоком прошивки под STM32 для работы с этим конкретно дисплеем. Распаковываю, тыкаю LLM носом, говорю "вытащи инициализацию и преобразуй в скетч" - и спустя пару минут получаю наконец работающий дисплей. Ещё 4 минуты на промпт "вот пример, из него сделай контроллер для проекта" и всё работает.
Третья половина проекта была посвящена асинхронщине, которую щедро нагенерила LLM. Ошибки были буквально везде: racing conditions на запрос состояния, отсутстсвие нумерации пакетов при передаче картинки в esp - то есть везде был оптимистичный сценарий в вакууме. Реальный обмен данными, конечно, в клочья рвал логику работы бекенда. Естественно, реконнектов не было нигде и в помине - померла так померла. Но шаг за шагом, работая QA и выдавая атомарные задачи, я смог добиться стабильной работы прошивки. UI пока что не так хорош, как я его планировал, но для некоммерческого личного использования это уже не блокер.
Что оно умеет
Сейчас прошивка реагирует на нажатие клавиш, рисует на экранчиках картинки и отсылает сигналы по двум каналам: как usb клавиатура (например, для ввода паролей и команд) и как vendor-usb устройство. Второй канал слушается приложением-бекендом, который имплементирует в себе основную логику взаимодействия с системой. Отдельно есть UI приложение, которое говорит с беком и посылает туда накливанные конфигурации. Также управление светодиодами для красивенькой подсветки клавиш, конечно.
Имплементированные команды:
текстовое поле (для клавиатуры)
запуск программы по указанному пути
управление громкостью
переключение по кнопке на другой профиль в устройстве
выполнение shell-команды
sequence: последовательное выполнение нескольких действий
отсылка "сырой" команды в USB для бекенда
папка - при входе в неё все кнопки меняют свои команды на настроенные, повторное нажание - выход на уровень выше. Поддерживается до 8 уровней вложенности.
взаимодействие с elgato плагинами (WARNING: не тестировалось вовсе!)
Ну и как это выглядит в реализованном виде
Скрытый текст

Итог
В итоге от этого проекта у меня странное послевкусие. Было много и хороших вещей, и плохих. Я явно заметил у себя рост навыка использования ЫЫ от начала проекта до текущей точки - но не уверен, что это тот навык, который мне прямо необходим. С другой стороны - "сегодня я многое понял" (с), опыта получено немерено. С третьей - это один из крайне немногих проектов, который удалось довести до хотя бы какого-то конца, а не отвлечься и не бросить на полпути. Ну и у меня теперь новая блестящая игрушка и понимание, как делать такие игрушки быстрее и лучше. Так что, в целом, я рад, что решился ввязаться в это ЫЫ-казино: по деньгам выиграло казино, а по остальным параметрами таки я.
Ссылка на гитхаб, если кому надо, прилагается. WARNING: там всё тот же кривой футпринт для ESP, так как я не нашёл в библиотеке верный, и небольшая проблема с позицией ESP (он глубже, чем я хотел). Может, позже поправлю, но пока так. И спасибо за внимание.