Перейти к содержанию

Транспорт и транзакции

Обмен с прошивкой идёт HID feature-репортами. Одна запись настроек всегда состоит из одной и той же последовательности команд, а каждая область прошивки сохраняется во флеш только своей командой 04 f0.

Linux: hidraw

Устройство открывается как /dev/hidrawN, обмен идёт через ioctl.

Операция Запрос Буфер
Отправка HIDIOCSFEATURE 65 байт: [0] = report id 0
Чтение HIDIOCGFEATURE 65 байт: [0] = report id 0

Особенности:

  • драйвер usbhid не отцепляется, поэтому клавиатура продолжает печатать во время настройки;
  • между запросами выдерживается пауза 0,04 с — вендорский тайминг;
  • root не нужен, если установлено udev-правило 70-ganss-katana.rules;
  • pyusb с detach_kernel_driver использовать нельзя: после этого клавиатура перестаёт печатать.

macOS: IOKit

Поддержка экспериментальная. Тот же протокол работает поверх IOKit напрямую через ctypes, сторонние HID-библиотеки не обязательны.

Операция Функция IOKit Тип репорта
Отправка IOHIDDeviceSetReport kIOHIDReportTypeFeature
Чтение IOHIDDeviceGetReport kIOHIDReportTypeFeature

У IOKit своя нумерация типов: Input = 0, Output = 1, Feature = 2. С типом Output прошивка отвечает мусором: приходит один байт 04.

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

  1. через hidapi (пакет hid), если он установлен: библиотека различает интерфейсы и отдаёт registry entry ID в пути устройства (DevSrvsID:…);
  2. перебором IOHIDDevice с фильтром по PrimaryUsagePage=1 и PrimaryUsage=6 (boot keyboard);
  3. первым интерфейсом с нужными VID и PID, если boot keyboard не найден: не тот интерфейс просто не ответит на ACK.

Если устройство не видно ни через hidapi, ни через IOKit, выводится сообщение «not found on USB».

Грабли macOS:

  • IOServiceMatching возвращает CFDictionaryRef, поэтому без restype = c_void_p ctypes обрезает указатель и процесс падает;
  • IOHIDDeviceGetProperty принимает IOHIDDeviceRef, а не registry entry; плоских idVendor в свойствах реестра нет;
  • доступ к HID-клавиатурам выдаётся вручную в «Input Monitoring», после чего терминал нужно перезапустить;
  • Karabiner-Elements и подобные утилиты захватывают клавиатуру монопольно, и IOHIDDeviceOpen возвращает kIOReturnExclusiveAccess (0xE00002C5);
  • udev-правило на macOS не нужно;
  • reset через pyusb на macOS работает, хотя пакеты собираются только под Linux.

Проверено на железе: при выключенном Karabiner команды mode, paint, keys, remap, macro и reset работают.

Общая транзакция

SET 04 18                          begin            ACK: 04 18 00 01
SET 04 13 00*6 01                  data-begin       ACK: 04 13 00 01
SET <payload 64B>                  данные           (без ACK)
SET 04 02                          commit           ACK: 04 02 00 01 <LE16 sum(payload)>
SET 04 f0                          save (flash)     ACK: 04 f0 00 01

Разбор шагов:

  • begin открывает сессию; ответ ff вместо 01 означает, что сессия уже открыта, и это не ошибка;
  • data-begin объявляет начало блока данных, байт [8] задаёт параметр блока: у таблиц раскраски и переназначений это 09, у макросов — число пакетов;
  • данные отправляются пакетами по 64 байта без подтверждения;
  • commit фиксирует блок, а в байтах 4–5 ACK возвращается контрольная сумма;
  • save записывает область во флеш.

ACK и контрольная сумма

Поле Смысл
ack[0..1] эхо команды, например 04 02
ack[3] статус: 01 — успех, 00 или ff — отказ
ack[4..5] sum(payload) & 0xFFFF, little-endian, только у commit

Контрольная сумма проверена на десятках команд вендорской утилиты и на записях проекта. Если сумма не совпала, утилита печатает предупреждение, но продолжает работу.

Сохранение областей

У каждой области прошивки свой save. Чужой save чужую область не сохраняет и не затирает.

Область Команда записи Свой save
Режим подсветки 04 18 04 f0
Раскраска 04 23 04 f0
Переназначения 04 11 04 f0
Макросы 04 19 04 f0

Практическое правило: область переживает переподключение только тогда, когда после её записи был её собственный save. Вендорская утилита после записи макросов save не делает, но в проекте apply сохраняет каждую секцию.

Открытие сессии и клин

Сессию нужно закрывать командой 04 f0. Если этого не сделать, прошивка залипает: begin начинает отвечать 00, а команды отклоняются.

Лечение:

  1. повторить команду;
  2. выполнить katana.py reset — USB port reset без переподключения, обычно занимает 2–12 секунд;
  3. если reset не помог с первого раза, повторить его с паузой 5–10 секунд;
  4. в крайнем случае выключить и включить клавиатуру физически.

Тайминги

Операция Время
write_key_table(save=False) ~0,6 с
write_key_table(save=True) ~0,7 с
read_frame() ~0,6 с
lighting() (смена режима) ~0,4 с
Чтение кадра без save ~15 кадров/с

Паузу 0,04 с сокращать не стоит: прошивка начинает терять пакеты и подтверждения. Чтение живого кадра идёт по отдельной схеме с паузой 5 мс — о ней рассказано в разделе Раскраска клавиш.