Кто как, а я обожаю странные маленькие девайсы. Так я однажды захотел себе HID Remapper (оригинальный или собственный, не суть). Это такие устройства, которые позволяют вам митмить HID-репорты (от клавиатуры, мышки, руля, геймпада и т.д.), придавая им новые смыслы. Например, можно сделать любую клавиатуру чуть более умной (с поддержкой слоёв, макросов и т.д.). В общем, прикольная штука.

А после экспериментов с “тисканьем” юбикеев (см. H4ptiX: услада для ваших юбикеев) я решил, что страшно хочу HID Remapper с возможностью “тискать” YubiKey. Так и родился проект Twibby, о котором и пойдет речь в этом посте.

Дела железные

Несмотря на то, что у проекта HID Remapper есть целая линейка спроектированных бордов на любой вкус и цвет (см. Remapper: Custom boards), мне ни один из них не подошёл. Причина очевидна из описания поста — нужно было куда-то воткнуть YubiKey :)

Поэтому я решил пойти по своим собственным граблям и собрать HID Remapper с USB-хабом на борту.

В плане компонент я был не очень оригинален:

  • роль основного контроллера взял на себя RP2040 Core A. Можно было бы распаять RP2040 самому, но это не укладывалось в концепт (дальше поймете)
  • FE1.1S revB в роли контроллера USB-хаба. Тут выбор чипов довольно большой, и, наверное, самый популярный среди “USB2.0 хаб за $1” — это SL2.1A (стоит дешево, минимальный BOM, работает), но я обещал себе не брать IC без документации, а потому выбрал FE1.1S :)
  • BFR182 (прям как в H4ptiX) для замыкания контактной площадки YubiKey на землю по сигналу от контроллера.
  • пара Type-C коннекторов (upstream/downstream) и один Type-A для YubiKey.
  • сопутствующая SMD-рассыпуха.

С компонентами определились, время рисовать схемку:

Изначально у меня была [странная] идея сделать Twibby чуть более универсальным и дать возможность как митмить репорты, так и снифать их. Потому резисторы R1 и R2 выполняют роль джамперов и подключают/отключают HID downstream порт от хаба. Идея, конечно, так себе, но перезаказывать платы уже не хотелось, так что оставил как есть. Если будете повторять — не наступайте на мою граблю и выпилите эту часть логики совсем :)

Со схемой закончили, время развести платку:

Я понимаю, не произведение искусства, но я только учусь. С самого начала я целился в двустороннюю 4-слойную плату размером до 50×50 мм. 4 слоя — чтобы меньше страдать, а 50×50 мм — чтобы уложиться в промо от JLCPCB, где производство 4-слойной платы такого размера стоит $2 (+$1.5 доставка в Таиланд).

Для желающих поштырить поближе, я залил всё в онлайн-редактор.

С платой определились, время делать заказик на JLCPCB:

Итого: $25 за 5 плат вместе с доставкой. Из них $21 — это монтаж одной стороны платы (с учетом стоимости компонент), на которой расположены USB-хаб и прочая мелкая SMD-рассыпуха. Таков был план: одну сторону монтирует JLCPCB, вторую (более простую) — я сам. План сработал, я доволен :) Конечно, можно было бы чуть сэкономить и запаять хотя бы type-C разъёмы самому, но я чёт поленился.

Немного ожидания — и вот будущий HID Remapper ждёт допайки:

Немного запаха припоя — и наш HID Remapper собран:

Дела софтовые

Сначала я думал просто отснифать USB-пакетики (и потому закладывал поддержку режима сниффера на уровне железа), но потом так и не придумал, как корректно отличать репорты хоста от репортов подключённого устройства — и отбросил эту идею. Можно, конечно, напилить стейт-машину и считать флипы или опираться на тайминги, но звучит это не особо надёжно.

А вот с HID Remapper всё выглядит уже более безопасно: у него два USB PHY (один настоящий в RP2040, второй реализован на PIO с помощью Pico-PIO-USB), а значит, никакой путаницы. Но, несмотря на то что проект бесспорно клёвый, мне не понравились две вещи:

  1. Отсутствие прозрачного проксирования — вместо копирования оригинальных дескрипторов и митма репортов, он отправляет в хост собственный дескриптор. Почему сделано именно так — понятно, но это означает, что с точки зрения хоста подключено не оригинальное устройство, а сам HID Remapper.
  2. Отсутствие какой-либо авторизации (кроме файловых прав) для доступа к конфигурационному интерфейсу. Конфигурируется HID Remapper через WebHID (работает только в chromium-based браузерах), а значит, малваре на хосте ничего не стоит переписать фирмварь ремаппера по своему желанию и творить всякую дичь :/

И если первое для меня не критично, то второе — блокер, потому что ломает всю идею использования touch policy в YubiKey. Поэтому ничего не оставалось, кроме как форкнуть оригинальный проект и дописать авторизацию.

Долго думал, должна ли это быть отдельная кнопка на плате или клавиша клавиатуры. Решил, что с клавиатурой будет удобнее — так и сделал: добавил новый usage page и позволил настраивать включение/выключение конфигурационного интерфейса.

Ну да хватит болтовни — закидываем twibby_rp2040.uf2 на флешку RP2040, подключаем к хосту и настраиваем (интерфейс для настройки тоже пришлось форкнуть: https://twbcfg.buglloc.com/):

  • маппинги (“тискаем” юбик клавишей Pause и включаем/выключаем конфигурационный интерфейс медиа-клавишей Play/Pause):

  • и требование авторизации:

Пушим настройки на устройство и проверяем в деле:

То что доктор прописал :)

Итоги и мысли на будущее

Не могу сказать, что такой способ “тисканья” юбика радикально улучшает UX, но тянуться к Pause явно ближе :)

Из явных промахов — надо было добавить серию диодиков (или RGB вроде WS2812) для индикации текущего слоя клавиатуры. Но знаете ли, я сильно быстрее умных мыслей! Может быть, в будущем сделаю новую ревизию или, как вариант, попробую заменить RP2040-Core-A на RP2350-Core-A, у которого как раз есть встроенный WS2812 на платке.

Но сейчас выглядит так, что нужно:

  • ещё чуть пожить с этим. Пока шел второй день эксперимента и не все валенки собраны ^_^
  • проверсти полноценный аудит фирмвари или написать свою

А уже потом решать нужна ли новая ревизия, мусорное ведро или третья смешная опция ;)

На этом у меня всё. Всем кота! (づ˶•༝•˶)づ♡