Files
ardor-katana-linux/docs/PROTOCOL.md
T

578 lines
45 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GANSS ARDOR_Katana (0c45:8006) — протокол и состояние реверса
> Файл содержит ВСЁ необходимое для продолжения работы: протокол, карту клавиш, открытые вопросы, методику.
> Обновлять по мере находок.
> Пользовательская документация (установка, команды, режимы, свои скрипты) — по ссылкам из [README.md](../README.md).
## Устройство
- Клавиатура GANSS ARDOR_Katana, USB `0C45:8006` (Sonix SN32), 2 HID-интерфейса.
- iface0 = boot keyboard + **конфиг-канал** (feature-репорты rid=0, 64 байта).
- iface1 = consumer/mouse + vendor page 0xFF00 rid=5 (только input, не используется).
- udev-правило `70-ganss-katana.rules` установлено (доступ к hidraw без root).
- hidraw-узел iface0 ищется через `/sys/class/hidraw/*/device` (hid_dev, родитель `*:1.0`) + modalias `hid:b0003g0001v00000C45p00008006`.
## Транспорт (реализовано в katana.py)
Feature-репорты через hidraw ioctl (`HIDIOCSFEATURE`/`HIDIOCGFEATURE`), буфер 65 байт: первый байт = report id (0).
**usbhid не отцепляется** — печать никогда не прерывается.
pyusb-вариант НЕ использовать (отцеплял драйвер).
### macOS (экспериментально, класс `KatanaMacOS`)
Тот же протокол поверх IOKit напрямую (ctypes, без сторонних библиотек):
`IOHIDDeviceSetReport` / `IOHIDDeviceGetReport` с `kIOHIDReportTypeFeature` —
семантика 1:1 с hidraw, feature-репорты rid=0, 64 байта данных.
Устройство ищется через `IOServiceGetMatchingServices("IOHIDDevice")` + `IOHIDDeviceGetProperty`:
выбирается клавиатура 0C45:8006 с PrimaryUsagePage=1, PrimaryUsage=6 (boot keyboard = интерфейс 0).
Выбор транспорта автоматический по `sys.platform` (`open_katana()`): darwin → IOKit, остальное → hidraw.
Грабли macOS (все проверены на железе):
- у IOKit своя нумерация типов репортов: Input=0, Output=1, **Feature=2**;
с Output прошивка отвечает мусором (1 байт `04`);
- `IOServiceMatching` возвращает CFDictionaryRef — без явного `restype = c_void_p`
ctypes обрезает указатель и процесс падает segfault'ом;
- `IOHIDDeviceGetProperty` принимает IOHIDDeviceRef (от `IOHIDDeviceCreate`),
а не registry entry — с entry процесс падает segfault'ом;
плоских `idVendor` в свойствах реестра нет — только через GetProperty;
- доступ к HID-клавиатурам выдаётся вручную: Системные настройки → Конфиденциальность и безопасность → «Input Monitoring» для приложения-терминала, затем перезапуск терминала;
- Karabiner-Elements (и подобные утилиты) захватывает клавиатуры монопольно — пока он работает, `IOHIDDeviceOpen` возвращает `kIOReturnExclusiveAccess` (0xE00002C5); проверено диагностикой `misc/iokit_probe.py`;
- `reset` (pyusb) на macOS работает (проверено: USB port reset проходит);
- udev-правило не нужно.
Проверка на железе (macOS, Karabiner выключен): mode/paint/keys/remap/macro/reset — работают.
## Общая транзакция (все команды)
```
SET 04 18 begin ACK: 04 18 00 01 (или ff = сессия уже открыта, ок)
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
```
- ACK-байт [3]: `01` = ok, `00`/`ff` = отказ (залипшая сессия / неверное состояние).
- Контрольная сумма: `sum(payload) & 0xFFFF`, LE, в ACK коммита (байты 4–5).
Проверена на десятках команд вендора и наших записях.
- **Сессию всегда закрывать `f0`**, иначе прошивка клинится: begin отвечает `00`, команды отклоняются.
Лечение: `katana.py reset` (USB port reset, ~2–12 c на переподключение; иногда нужен повторный reset после паузы).
- Вендор всегда шлёт полную транзакцию begin → data → payload → commit → save, даже на каждый чих.
Подражать.
## Lighting payload (блок 1)
```
[0] режим: 1..19 (0x13) — список вендора; 0x80 — custom per-key таблица
[1..3] R G B
[8] flag: 0x00 для режимов 1 и 4, 0x01 для остальных
[9] яркость 0x01..0x0F (вендор: слайдер 15 позиций)
[10] скорость 0x02..0x0F (вендор); для статики (mode 1) всегда 0x0A,
слайдер скорости в UI неактивен — подтверждено katana2-speed
[11] подрежим: моно/радуга для статики, направление анимации (см. ниже)
[14..15] aa 55
```
### Байт [11] — подрежим (снято по color-toggle-*.pcapng)
| Режим | Значение | Смысл |
| ----------------- | ----------- | ---------------------------------- |
| 1 static | `00` / `01` | моно (`--color`) / радужный спектр |
| 10 waterfall | `02` / `03` | север→юг / юг→север |
| 16 marquee (0x10) | `01` / `00` | восток→запад / запад→восток |
Для остальных режимов вендор всегда шлёт `00`.
Направления режимов 11 flow, 12 rotate, 18 wind кодируются тем же байтом (значения не сняты — в захватах переключали только waterfall и marquee).
CLI: `--rainbow`, `--direction north-south|south-north|east-west|west-east`.
- Off = payload из нулей + `aa55` (последний пункт списка вPендора).
- Яркость масштабирует per-key цвета: 15/15 → `ee` (238), 1/15 → `0f`.
- Режим 0x80 отображает таблицу per-key (см. ниже) — это и есть «кастом».
### Таблица режимов (описания вендорской утилиты, docs/color-modes.md)
Номер = байт [0] payload.
«моно/спектр» — режим рисует одиночный цвет или полный спектр независимо от него.
У режимов 10, 11, 12, 16, 18 есть **направление анимации** (кодировка в payload пока не снята — ждём снимок установки настроек).
Алиасы реализованы в katana.py (`MODES`).
| № | Алиас | Описание | Параметры |
| --- | ----------- | ------------------------------------------------------------ | ----------------------------------- |
| 1 | `static` | Постоянный свет всех клавиш | моно/спектр, яркость |
| 2 | `keypress` | Подсветка нажатых: всё выключено, горят нажатые | моно/спектр, яркость, скорость |
| 3 | `fade` | Затухание нажатых: всё включено, гаснут нажатые | моно/спектр, яркость, скорость |
| 4 | `star` | Звёздный: случайные вспышки после полного затухания | моно/спектр, яркость, скорость |
| 5 | `snow` | Снежный: как звёздный, но больше соседних клавиш | моно/спектр, яркость, скорость |
| 6 | `flower` | Цветочный: плавные цвета на каждой клавише без затухания | спектр, яркость, скорость |
| 7 | `breathing` | Дыхание: плавное свечение и затухание всех клавиш | моно/спектр, яркость, скорость |
| 8 | `spectrum` | Полный спектр: плавные цвета на всех клавишах | спектр, яркость, скорость |
| 9 | `ripple` | Круги: плавные цвета кругами из центра к краям | моно/спектр, яркость, скорость |
| 10 | `waterfall` | Водопад: спектр по рядам клавиш | моно/спектр, яркость, скорость, ↑/↓ |
| 11 | `flow` | Течение: косые волны в одну сторону без затухания | моно/спектр, яркость, скорость, ←/→ |
| 12 | `rotate` | Вращение: волна по кругу вокруг центра | моно/спектр, яркость, скорость, ←/→ |
| 13 | `h-edge` | Горизонтальная грань: волна по ряду от нажатой | моно/спектр, яркость, скорость |
| 14 | `v-edge` | Вертикальная грань: косая волна от нажатой клавиши | моно/спектр, яркость, скорость |
| 15 | `splash` | Рябь по воде: плавные круги от нажатой клавиши | моно/спектр, яркость, скорость |
| 16 | `marquee` | Бегущая строка: подсветка каждой клавиши в ряду | моно/спектр, яркость, скорость, ←/→ |
| 17 | `mountains` | Горы: плавные углы из середины в стороны | моно/спектр, яркость, скорость |
| 18 | `wind` | Ветер: косые волны в одну сторону с затуханием | моно/спектр, яркость, скорость, ←/→ |
| 19 | `shuttle` | Шаттл: бегущие строки по чётным/нечётным рядам в обе стороны | моно/спектр, яркость, скорость |
| — | `off` | Выключена (пункт 20 списка) | all-zero payload |
Переключение «моно/спектр» — байт [11] (для статики `00`=моно, `01`=радуга), направление анимации — тоже байт [11], значения в таблице выше.
## Per-key таблица (кастомная раскраска)
**Запись** (реализовано, работает стабильно):
```
begin
→ SET 04 23 00*6 09
→ ACK
→ 9 пакетов по 64Б: 143 записи [index, R, G, B] (индексы 0x00..0x8e) + 0000 + aa55 (576 Б)
→ commit (ACK сумма = sum(576Б))
→ save
```
Затем lighting payload mode=0x80 показывает таблицу.
**Чтение (04 f5) — РАБОТАЕТ (вендорская схема):**
```
SET 04 f5 +09
→ GET ×9: поток [idx,r,g,b]×16, индексы 0x00,0x10..0x80 (0x00..0x8f)
→ commit (ACK 01, сумма 0000 — payload не писался)
→ save
```
- **НЕ делать begin+data-begin перед f5** — data-begin disarmит поток (ACK status 00, GET-ы возвращают эхо последнего SET).
- Первый GET после SET может быть ACK `04 f5 00 01` (наблюдалось на первом чтении после save у вендора, там же было только 8 пакетов) — читатель пропускает не-данные и ждёт пакеты с ожидаемыми индексами.
- Указатель сбрасывается на 0x00 каждым f5 SET (в рабочем состоянии).
- Ретрай: до 3 попыток, между ними `begin` (переоткрытие контекста).
- Кадр = ЖИВОЕ изображение: на статике — цвет режима (масштаб яркости, 15/15 → ee), на анимации — текущие цвета эффектов, меняются между чтениями.
Слот 0x6d всегда 000000 (null-слот прошивки, даже у вендора).
Слепые слоты в кадре на статике показывают цвет режима (буфер расчётный, физически LED нет).
## Переназначение клавиш (канал 04 11)
**Запись — РАБОТАЕТ** (снято по `raw-data/katana-v1/keybind-mouse-buttons.pcapng`: LCtrl → 5 кнопок мыши по очереди + возврат на default; проверено на железе):
```
begin
→ SET 04 11 00*6 09
→ ACK
→ 9 пакетов по 64Б: 143 записи по 4 байта (позиция слота = LED-индекс клавиши) + 0000 + aa55 (576 Б)
→ commit (ACK сумма = sum(576Б))
→ save
```
Формат блоба совпадает с per-key таблицей, но содержимое записи — назначенное действие, а не цвет.
В отличие от per-key записи, индекс клавиши в записи НЕ дублируется — слот определяется позицией в блобе.
**Содержимое записи** (4 байта):
- `[00, 00, 00, 00]` — действие по умолчанию (клавиша работает как обычно).
- `[01, 01, код, 00]` — кнопка мыши, код в байте [2].
- `[02, маска, код, 00]` — шорткат: маска модификаторов в байте [1],
HID usage ID клавиши (Keyboard/Keypad page 0x07) в байте [2]
(дампы keybind-editor, keybind-hotkeys).
- `[03, usage_lo, usage_hi, 00]` — мультимедиа/веб-действие, байты [1..2] — 16-битный LE usage ID из HID Consumer Page (0x0C) (дамп keybind-multimedia).
- `[06, индекс, режим, счётчик]` — запуск макроса (дампы macros-set, macros-modes).
Индекс 0-based: «Макрос 1» вендорской утилиты = 0.
Режим: `00` — однократно, `01` — повторить `счётчик` раз (наблюдалось 1, 5, 8), `02` — повторять до повторного нажатия.
**Коды кнопок мыши** (байт [2]; значения — степени двойки, вероятно битовая маска, но комбинированные записи железом не проверялись):
| Код | Кнопка |
| ---- | ------------------------------- |
| `01` | ЛКМ (левая) |
| `02` | ПКМ (правая) |
| `04` | СКМ (средняя) |
| `08` | «назад» (боковая) |
| `10` | «вперёд» (боковая) |
Пример из дампа: LCtrl (слот 0x5b) → ЛКМ = запись `[01, 01, 01, 00]` по смещению 91×4 = 364 (пакет 5, смещение 44).
**Шорткаты редактора** (дамп keybind-editor): 9 транзакций, каждая вешает на Caps (слот 0x37) запись `[02, 01, код, 00]`.
Код в байте [2] — HID usage ID буквы, порядок транзакций совпадает с порядком списка функций в UI вендора.
Реализация — `EDITOR_ACTIONS` в katana.py, офлайн-тест по дампу — `tests/test_macro.py` (`test_editor_actions_match_dump`).
Проверено на железе: `caps=copy` и `menu=undo` работают как Ctrl+C/Ctrl+Z.
| Код | Функция | Шорткат |
| ---- | ------------ | ------- |
| `12` | Открыть | Ctrl+O |
| `11` | Создать | Ctrl+N |
| `1d` | Отмена | Ctrl+Z |
| `16` | Сохранить | Ctrl+S |
| `06` | Копировать | Ctrl+C |
| `1b` | Вырезать | Ctrl+X |
| `19` | Вставить | Ctrl+V |
| `09` | Найти | Ctrl+F |
| `04` | Выбрать всё | Ctrl+A |
**Горячие клавиши** (дамп keybind-hotkeys): 10 транзакций, каждая вешает на Caps (слот 0x37) запись — по порядку списка UI вендора:
A, Shift+B, Ctrl+C, Alt+D, Meta+E, Esc, F1, Num1, Fn, «Мой компьютер».
Первые девять — записи типа `02`, десятая — мультимедиа `[03, 94 01, 00]` (usage 0x0194 AL My Computer, см. таблицу выше).
Реализация — `HOTKEY_MODIFIERS`, `HOTKEY_KEYS` и `parse_hotkey()` в katana.py, офлайн-тесты — `tests/test_macro.py` (`test_hotkey_actions_match_dump`, `test_parse_hotkey`).
Проверено на железе: `caps=ctrl+c` (одиночный модификатор) и `caps=ctrl+shift+c` (маска 0x03 — комбинация) работают как ожидается.
**Байт [1] типа `02` — битовая маска модификаторов:**
| Маска | Модификатор |
| ----- | ----------- |
| `00` | без модификатора |
| `01` | Ctrl |
| `02` | Shift |
| `04` | Alt |
| `08` | Meta (Win) |
У шорткатов редактора наблюдался только `01` (Ctrl); дамп keybind-hotkeys подтверждает остальные значения и показывает, что маска — степень двойки.
Комбинации модификаторов (сумма масок, например Ctrl+Shift = `03`) подтверждены на железе: `caps=ctrl+c` и `caps=ctrl+shift+c` работают.
Коды клавиш в байте [2] сверены с HID Usage Tables и дампом: A–E = `04`–`08`, Esc = `29`, F1 = `3A`, Num1 = `59`.
Fn = `AF` — собственный код прошивки: в HID Usage Tables такого usage нет, но вендор шлёт именно его.
| Код | Клавиша | Примечание |
| ---- | ------- | --------------------------------- |
| `04` | A | (буквы `04`–`1D`) |
| `29` | Esc | |
| `3A` | F1 | (F-клавиши `3A`–`45`) |
| `59` | Num1 | (нумпад `58`–`62`, Num0 = `62`) |
| `AF` | Fn | собственный код прошивки, не HID |
**Мультимедиа/веб-действия** (дамп keybind-multimedia): 18 транзакций, каждая вешает на Caps (слот 0x37) запись `[03, usage u16 LE, 00]`.
Порядок транзакций совпадает с порядком списка функций в UI вендора, все usages сверены с HID Usage Tables (Consumer Page 0x0C).
Реализация — `MULTIMEDIA_ACTIONS` в katana.py, офлайн-тест по дампу — `tests/test_macro.py` (`test_multimedia_actions_match_dump`).
| Usage | Функция |
| ------ | ----------------------------------------- |
| `0183` | Плеер (AL Consumer Control Configuration) |
| `00CD` | Воспроизведение/пауза |
| `00B7` | Стоп |
| `00B6` | Предыдущая песня |
| `00B5` | Следующая песня |
| `00E9` | Громкость + |
| `00EA` | Громкость − |
| `00E2` | Отключить звук |
| `0223` | Домашняя страница (AL Internet Browser) |
| `0227` | Веб: обновить (AL Web Refresh) |
| `0226` | Веб: остановить (AL Web Stop) |
| `0224` | Веб: назад (AL Web Back) |
| `0225` | Веб: вперед (AL Web Forward) |
| `022A` | Веб: избранное (AL Favorites) |
| `0221` | Веб: поиск (AL Web Search) |
| `0194` | Мой компьютер (AL My Computer) |
| `0192` | Калькулятор (AL Calculator) |
| `018A` | Электронная почта (AL Email Reader) |
**Известные ограничения:**
- Команда перезаписывает ВСЮ таблицу (вендорская утилита делает так же) — каждый раз шлются все 143 слота.
- Команда чтения текущих переназначений неизвестна (в дампах только запись).
- Установлены пять типов действий: мышь (`[01, 01, ...]`), шорткаты — редактор и горячие клавиши (`[02, маска, ...]`), мультимедиа (`[03, usage, ...]`) и макрос (`[06, ...]`).
Остальные типы (Fn-слой) не исследованы.
## Макросы (канал 04 19 + привязка через 04 11)
Снято по дампам `raw-data/katana-v1/macros-*.pcapng` и вендорскому XML (`test.xml`, `macros-create-test.xml`).
Содержимое макросов хранится в самой прошивке, а не на ПК:
в эксперименте макрос сыграл при отключённой вендорской утилите и другой раскладке («ФЫВА» — те же клавиши A, S, D, F).
### Запись содержимого — канал 04 19
Транзакция отличается от обычной:
```
SET 04 19 begin ACK: 04 19 00 01
SET 04 15 00*6 <число пакетов> data-begin ACK: 04 15 00 01 (эхо числа пакетов в байте [8])
SET <данные, пакетов × 64Б> данные (без ACK)
SET 04 02 commit ACK: 04 02 00 01 <LE16 sum>
```
- Вендор после коммита **не делает save** (04 f0) — в дампе macros-create его нет.
- В байте [8] data-begin — число 64-байтовых пакетов (как у 04 23/04 11, где 09 = 576/64).
- **Модель сохранения (проверена на железе): каждая область (bind-таблица, paint-таблица, режим, макросы) сохраняется во флеш только своим собственным save (04 f0).**
Чужой save чужую область ни сохраняет, ни затирает: remap-save не трогает макросы, macro-save не трогает bind.
Практическое правило: область переживает переподключение только если после её записи был её собственный save.
В apply каждая секция несёт свой save.
- **Запись макросов (04 19) сбивает bind-таблицу и режим подсветки В RAM** (во флеше они остаются).
После записи макросов bind и режим нужно перепослать (apply делает это автоматически).
- **Несколько макросов живут только при записи одним блобом**: каждый вызов 04 19 с блобом, содержащим один слот, обнуляет прочие слоты.
При обращении к ПУСТОМУ слоту прошивка делает fallback на слот 0 (проверено: macro1 при пустом слоте 1 исполнял содержимое macro0).
- Вендорская утилита после записи макросов других save-транзакций не делает — в одиночной операции этого достаточно.
**Формат блоба** (дамп macros-create, 14 пакетов = 896 байт):
- Заголовок 400 байт (0x190) — 100 u32-смещений: смещение данных макроса от начала блоба, 0 = слот пуст.
В дампе: слот 0 → 0x190 (400), слот 1 → 0x1d8 (472).
- На каждый макрос: u32 (число событий × 2), u32 0, затем события по 8 байт:
`[00 00][код][тип][задержка u16 LE][00][50]`.
- Хвост: нули до кратности 64 минус 2 байта + `aa55` (как у per-key блоба).
- Ёмкость по дампу — 896 байт: один макрос вмещает до 60 событий (при пустых остальных слотах).
**Типы событий** (байт [3]): `B0` — клавиша нажата, `30` — отпущена, `90` — кнопка мыши нажата, `10` — отпущена.
**Коды клавиш** (байт [2]):
- Буквы, цифры, F1–F12, модификаторы — совпадают с HID usage ID (сверено: A=04, S=16, Q=14, LCtrl=E0, LAlt=E2, LWin=E3).
- Стрелки — собственные коды прошивки, не HID: Left=`5C`, Right=`5E` (сверено); Up=`5D`, Down=`5F` — предположение по соседству, железом не проверены.
- Медиа — тоже коды прошивки: VolUp=`05`, VolDown=`06` (сверено по XML: VK 175→05, VK 174→06).
- Коды кнопок мыши — те же, что в таблице переназначений (`01` ЛКМ, `02` ПКМ, `04` СКМ).
**Коллизия `05`/`06`:** это одновременно VolUp/VolDown и HID-коды букв B/C.
Как прошивка различает их (и различает ли) — не установлено; вендорская утилита шлёт `05`/`06` именно для громкости.
**Задержки:** в двух событиях вендорского XML стоит `delay_time="0"`, а в блобе прошивки — 10.
Похоже на минимум вендора (2 наблюдения).
Семантика задержки (пауза до или после события) по дампу не определяется — значения просто переносятся как есть.
### Привязка макроса к клавише
Обычная транзакция канала 04 11 (см. «Переназначение клавиш»), запись `[06, индекс, режим, счётчик]`.
Вендор после привязки делает save (в отличие от записи содержимого).
### Вендорский XML
Утилита экспортирует макрос в XML (по файлу на макрос, имя — в `macroinfo`): `item type` `2`/`3` — клавиша вниз/вверх (`value` = VK-код Windows), `4`/`5` — кнопка мыши вниз/вверх (`value`: 1 = ЛКМ, 2 = СКМ, 3 = ПКМ), `delay_time` — мс.
Реализация: `parse_macro_xml()` / `macro_to_xml()` в katana.py, офлайн-тесты — `tests/test_macro.py`
(блоб, собранный из вендорских XML, побайтово совпадает с дампом с точностью до двух задержек «0 → 10»).
**Открытые вопросы:**
- Чтение содержимого макросов из прошивки не найдено (в дампах только запись).
- Смысл u32 «число событий × 2» (возможно, счётчик полусобытий).
- Реальный лимит макросов: заголовок рассчитан на 100 слотов, но ёмкости данных хватает лишь на пару десятков событий суммарно.
- Несохранённое (без 04 f0) содержимое стирается следующим save другой транзакции (проверено) — а вот переживает ли оно переподключение USB, не установлено.
- Коллизия кодов `05`/`06` (громкость против букв B/C).
- Коды Up/Down стрелок (`5D`/`5F` — предположение) и остальные медиа-клавиши.
### Особенность режима 7 (breathing): цвет из payload игнорируется
Проверено на железе серией экспериментов:
- Payload режима 7 побайтово совпадает с вендорским (`07 ff 00 00 … 01 0f 0a … aa 55`), но LED-кадр показывает зелёно-жёлто-красную гамму (R=0, G:B ≈ 5:1) при любом цвете в байтах [1..3]: красный, зелёный и синий дают одну и ту же картинку.
- Цвет честно работает в статике (mode 1: кадр `(238,0,0)` для красного) и в fade (mode 3: кадр красноватый).
- Speed, flag и paint-таблица (0x80) на это не влияют; после `default` поведение то же.
Вывод: у breathing цветовой цикл зашит в прошивку, байты цвета для него не используются (возможно, вендорская утилита показывает то же самое — в её дампе тот же payload). При выборе режима для моноцвета используйте static или fade.
## Карта клавиш (index → физическая клавиша)
Методика: красим 4–8 индексов контрастными цветами, пользователь называет клавиши.
Полная карта — в `keymap.py` (источник данных), ниже сводка.
| Индекс | Клавиша |
| ------------- | ----------------------- |
| `0x00` | (слепой) |
| `0x01` | Esc |
| `0x02` | F1 |
| `0x03` | F2 |
| `0x04` | F3 |
| `0x05` | F4 |
| `0x06` | F5 |
| `0x07` | F6 |
| `0x08` | F7 |
| `0x09` | F8 |
| `0x0a` | F9 |
| `0x0b` | F10 |
| `0x0c` | F11 |
| `0x0d` | F12 |
| `0x0e`–`0x12` | (слепые) |
| `0x13` | ~ (`` ` ``) |
| `0x14` | 1 |
| `0x15` | 2 |
| `0x16` | 3 |
| `0x17` | 4 |
| `0x18` | 5 |
| `0x19` | 6 |
| `0x1a` | 7 |
| `0x1b` | 8 |
| `0x1c` | 9 |
| `0x1d` | 0 |
| `0x1e` | - (_) |
| `0x1f` | = (+) |
| `0x20` | NumLock |
| `0x21` | Num/ |
| `0x22` | Num* |
| `0x23`–`0x24` | (слепые) |
| `0x25` | Tab |
| `0x26` | Q |
| `0x27` | W |
| `0x28` | E |
| `0x29` | R |
| `0x2a` | T |
| `0x2b` | Y |
| `0x2c` | U |
| `0x2d` | I |
| `0x2e` | O |
| `0x2f` | P |
| `0x30` | [ ({) |
| `0x31` | ] (}) |
| `0x32` | Num7 |
| `0x33` | Num8 |
| `0x34` | Num9 |
| `0x35`–`0x36` | (слепые) |
| `0x37` | Caps |
| `0x38` | A |
| `0x39` | S |
| `0x3a` | D |
| `0x3b` | F |
| `0x3c` | G |
| `0x3d` | H |
| `0x3e` | J |
| `0x3f` | K |
| `0x40` | L |
| `0x41` | ; |
| `0x42` | ' |
| `0x43` | \ |
| `0x44` | Num4 |
| `0x45` | Num5 |
| `0x46` | Num6 |
| `0x47`–`0x48` | (слепые) |
| `0x49` | LShift |
| `0x4a` | Z |
| `0x4b` | X |
| `0x4c` | C |
| `0x4d` | V |
| `0x4e` | B |
| `0x4f` | N |
| `0x50` | M |
| `0x51` | , |
| `0x52` | . |
| `0x53` | / |
| `0x54` | RShift |
| `0x55` | Enter |
| `0x56` | Num1 |
| `0x57` | Num2 |
| `0x58` | Num3 |
| `0x59`–`0x5a` | (слепые) |
| `0x5b` | LCtrl |
| `0x5c` | LWin |
| `0x5d` | LAlt |
| `0x5e` | Space |
| `0x5f` | RAlt |
| `0x60` | Fn |
| `0x61` | Menu |
| `0x62` | RCtrl |
| `0x63` | ArrowLeft |
| `0x64` | ArrowDown |
| `0x65` | ArrowUp |
| `0x66` | ArrowRight |
| `0x67` | Backspace |
| `0x68` | Num0 |
| `0x69` | Num. (Del) |
| `0x6a` | NumEnter |
| `0x6b`–`0x6f` | (слепые) |
| `0x70` | PrtSc |
| `0x71` | ScrLk |
| `0x72` | (слепой) |
| `0x73` | Pause |
| `0x74` | Insert |
| `0x75` | Home |
| `0x76` | PgUp |
| `0x77` | Delete |
| `0x78` | End |
| `0x79` | PgDown |
| `0x7a` | Num- |
| `0x7b` | Num+ |
| `0x7c`–`0x8e` | (слепые) |
Итого **104 клавиши**, все 143 слота проверены, все физические клавиши светятся при полной заливке (проверено: «горят ВСЕ клавиши»).
Нумпад полный: NumLock, /, *, -, 7/8/9, 4/5/6, 1/2/3, +, 0, ., Enter.
**Слепые слоты** (39 шт, не соответствуют ни одному LED): 0x00, 0x0e–0x12, 0x23, 0x24, 0x35, 0x36, 0x47, 0x48, 0x59, 0x5a, 0x6b–0x6f, 0x72, 0x7c–0x8e.
Хвост 0x80+ — резерв прошивки под большие раскладки/модели.
**Аномалия 0x80:** в раннем тесте светился синим сам по себе; в финальном тесте (жёлтый) не горел.
Считать слепым, но помнить про аномалию при чтении f5 (в потоке чтения 0x80 показывал 0000ff).
**Урок картирования:** тёмные/холодные цвета (синий) на дальних клавишах пользователь может не заметить — Num- (0x7a) был пропущен в синем тесте, но найден жёлтым при перепроверке слепых слотов группами.
При сомнениях перепроверять слепые слоты яркой заливкой группами.
## Статус утилиты katana.py
### `mode N --color RRGGBB [--brightness 1..15] [--speed 2..15] [--flag]`
Валидация диапазонов.
Для mode 1 speed форсируется 0x0A с warning.
### `off`
Payload нулей + aa55.
### `default`
Вендорский payload 0x80.
### `raw HEX`
Произвольный payload в транзакции.
### `scan --from --to`
Перебор режимов (1..19), каждый с save.
### `reset`
USB port reset (unwedge).
### `paint --all RRGGBB [--key IDX=RRGGBB ...] [--black] [--keep] ...`
Запись таблицы (143 клавиши) + показ mode 0x80.
`--wasd` = четыре `--key` для W/A/S/D.
`--numpad` = 17 `--key` для всего нумпада.
`--alpha` = 26 `--key` для букв A–Z.
`--punct` = 9 `--key` для знаков `[];',./\` и пробела.
`--digits` = 13 `--key` для ряда `` `1234567890-= ``.
`--arrows` = 4 `--key` для стрелок.
`--row1..--row6` = `--key` для горизонтальных рядов полной клавиатуры, включая нумпад (104 клавиши; высокие клавиши нумпада отнесены к ряду начала; состав — ROW_KEYS в katana.py).
`--keep` берёт базой текущий живой кадр (чтение f5), остальные клавиши сохраняют цвета.
В `--key` индексы задаются только в hex (`0x25`), голые цифры — имена клавиш цифрового ряда.
Клавиши со спецсимволами принимаются символом (`[`, `~`, `;`, ...), синонимом (`tilde`, `minus`, `lbracket`, `semicolon`, `backslash`, `numenter`, ... — таблица KEY_SYMBOL_ALIASES в keymap.py) и каноническим именем из keymap.py.
### `keys`
Чтение живого кадра (04 f5).
### `remap --key KEY=ACTION [--key ...] [--clear]`
Таблица переназначений (канал 04 11).
ACTION: мышь (`lmb`/`mouse1`, `rmb`/`mouse2`, `mmb`/`mouse3`, `back`/`mouseback`, `forward`/`mouseforward`), шорткаты редактора (`open`/`new`/`undo`/`save`/`copy`/`cut`/`paste`/`find`/`selectall` — Ctrl+O/N/Z/S/C/X/V/F/A), мультимедиа (`player`, `play`, `stop`, `prev`, `next`, `volup`, `voldown`, `mute`, `home`, `refresh`, `webstop`, `webback`, `webforward`, `favorites`, `websearch`, `mycomputer`, `calculator`, `email`), горячие клавиши (`MOD+КЛАВИША`: `ctrl`/`shift`/`alt`/`meta` + `a`..`z`, `0`..`9`, `f1`..`f12`, `esc`, `tab`, `enter`, `space`, `caps`, `backspace`, `num0`..`num9`, `fn` — например `ctrl+c`, `shift+b`, `meta+e`), `default`/`none`/`off`, макросы (`macro<N>` — однократно, `macro<N>:K` — K повторов, `macro<N>-toggle` — до останова).
`--clear` — вся таблица в default.
ВАЖНО: каждый вызов перезаписывает всю таблицу — не указанные `--key` сбрасываются в default; чтение текущих переназначений неизвестно.
### `macro set N [токены] [--xml FILE] [--save]`
Записать содержимое макроса N (канал 04 19, без save — как вендор; `--save` шлёт 04 f0).
Токены: `[+-]имя[@задержка_мс]` (`+` нажать, `-` отпустить, без знака — нажать и отпустить; имена — MACRO_KEYS/MOUSE_ACTIONS); перед токенами с `-` нужен `--`.
ВАЖНО: перезаписываются ВСЕ слоты макросов — не указанные стираются.
После записи привязать: `remap --key KEY=macro<N>`.
### `macro clear [--save]`
Стереть ВСЕ слоты макросов.
### `macro show FILE` / `macro export FILE [токены] [--name]`
Офлайн: показать события из вендорского XML / сгенерировать вендорский XML из токенов (без обращения к железу).
## Инструменты анализа
- `analyze_pcap.py <capture.pcapng> [out.txt]` — USBPcap → лог SET/GET control-трансферов (merged setup+data для OUT; GET по irpid).
- `raw-data/*.txt` — распарсенные логи всех 5 захватов (katana1 + 4×katana2).
- Захваты: `katana1.pcapng` (базовый реверс), `katana2-color-static` (цвета), `katana2-bright-static` (яркость), `katana2-speed` (скорость), `katana2-modes` (все режимы + off).
В режимах был случайный дубликат клика (mode 8 дважды).
- Сессии записи таблицы клавиш в katana1: события #11 и #36 (0x23, блок 09).
## Следующие шаги
1. ~~Докартировать карту~~ — ЗАВЕРШЕНО (104 клавиши, keymap.py).
2. ~~Починить чтение f5~~ — ЗАВЕРШЕНО (вендорская схема: SET f5 → 9 GET → commit → save; без begin/data-begin; ретраи с валидацией индексов).
3. Именованные пресеты (WASD, дыхание и т.п.) поверх paint — keymap.py готов.
4. ~~Переназначение клавиш: канал 04 20~~ — УТОЧНЕНО: канал `04 11` (не 04 20), формат и мышиные действия сняты и реализованы (см. «Переназначение клавиш»).
Шорткаты редактора (тип `02`) тоже сняты и реализованы (дамп keybind-editor).
Горячие клавиши (тип `02`, маска модификаторов в байте [1]) сняты по дампу keybind-hotkeys и реализованы (`HOTKEY_KEYS`/`HOTKEY_MODIFIERS`, `remap --key KEY=MOD+KEY`).
Осталось: другие типы действий (Fn-слой) и чтение таблицы.
5. ~~Демо-скрипты (радуга, волна) поверх per-key API + живое чтение f5~~ — ЗАВЕРШЕНО, методика описана и проверена ([extending.md](extending.md)): таблица обновляется живьём в режиме 0x80, ~1 кадр/с.
6. ~~Макросы: запись содержимого и привязка~~ — ЗАВЕРШЕНО (канал 04 19 + записи `[06, ...]` в 04 11, команда `macro`, см. «Макросы»).
Осталось: чтение содержимого, коллизия кодов 05/06, стрелки Up/Down, остальные медиа-коды.
## Важные грабли (не наступать повторно)
- pyusb + detach_kernel_driver = клавиатура перестаёт печатать.
Только hidraw.
- Сессия без f0 = клин прошивки.
Всегда save.
- После клина: reset может не помочь с первого раза — повторить с паузой 5–10 c.
- f5-чтение без begin+data-begin = мусор (указатель не с начала).
- begin ACK ff = «сессия уже открыта» — это ок, продолжать.
- Слепые слоты молча игнорируются прошивкой (ACK ok, но не горят).
- Пользователь различает цвета приблизительно: «розовый»=маджента, «жёлтый»=жёлтый/олива, «зелёный»=зелёный/тёмнозелёный.
Для картирования использовать максимально контрастные цвета и мало точек за раз.