Свои скрипты и анимации¶
katana.py спроектирован как библиотека: класс Katana и вспомогательные функции импортируются в любой скрипт.
Все проверки подтверждений, контрольных сумм и таймингов уже внутри.
Что даёт per-key API¶
Прошивка хранит таблицу из 143 слотов [индекс, R, G, B] и показывает её режимом 0x80.
Таблица обновляется живьём: если подсветка уже в этом режиме, каждая новая запись сразу меняет свечение клавиш.
Второе свойство — чтение живого кадра: скрипт видит, что светится сейчас, с учётом яркости и активных анимаций.
Подключение¶
from katana import Katana, KEYS_COUNT, resolve_key, build_payload
keyboard = Katana()
try:
...
finally:
keyboard.close()
Дескриптор обязательно закрывайте в finally.
На macOS вместо Katana() используйте фабрику open_katana(): она сама выберет транспорт.
Справочник API¶
| Объект | Что делает |
|---|---|
open_katana() |
Открывает устройство нужным транспортом: hidraw на Linux, IOKit на macOS. |
Katana(), KatanaMacOS() |
Транспорты напрямую; обычно достаточно open_katana(). |
keyboard.close() |
Закрывает дескриптор. |
keyboard.lighting(payload, save=True) |
Полная транзакция с payload: переключает режим подсветки. |
keyboard.write_key_table(colors, save=True) |
Записывает таблицу раскраски из 143 кортежей (r, g, b). |
keyboard.write_bind_table(records, save=True) |
Записывает таблицу переназначений, 143 записи по 4 байта. |
keyboard.write_macro_table(macros, save=False) |
Записывает содержимое макросов, все слоты одним блобом. |
keyboard.show_custom(brightness=15, save=True) |
Переключает подсветку в режим 0x80. |
keyboard.read_frame() |
Читает живой кадр и возвращает 143 кортежа (r, g, b). |
keyboard.reset() |
USB port reset без переподключения. |
resolve_key(name) |
Имя или символ клавиши в LED-индекс; None, если клавиша не найдена. |
build_payload(mode, rgb, ...) |
Собирает 64-байтный payload для lighting(). |
parse_color(text) |
Hex или имя цвета в кортеж (r, g, b). |
KEYS_COUNT |
Размер таблицы: 143 слота. |
Индекс — это позиция в таблице, а не номер клавиши.
Карта «индекс → физическая клавиша» лежит в misc/keymap.py.
Для скриптов на Node.js есть та же библиотека — см. раздел TypeScript API.
Ограничения¶
Скорость¶
Между любыми двумя USB-запросами выдерживается пауза 0,04 секунды.
Один кадр записи таблицы — это 17 запросов.
| Операция | Время |
|---|---|
write_key_table(save=False) |
~0,6 с |
write_key_table(save=True) |
~0,7 с |
read_frame() |
~0,6 с |
lighting() (смена режима) |
~0,4 с |
Практический потолок плавной анимации записью — 1–1,5 кадра в секунду.
Чтение кадра без save идёт быстрее, около 15 кадров в секунду, но в katana.py такой метод пока не реализован: read_frame() всегда делает save.
В приложении для этого есть readLiveFrame(), а перенос метода в Python-версию записан в открытые задачи.
Вендорская утилита анимирует отдельные клавиши через отдельный канал стриминга 04 20: без save, около 15 кадров в секунду.
В katana.py этот канал пока не реализован.
Эффекты с быстрым движением (бегущая строка) будут дёргаными; хорошо смотрятся медленные волны, дыхание, плавные переливы.
Флеш-память¶
save=True записывает настройку во флеш, у которого ограниченный ресурс перезаписи.
Правила:
- в цикле анимации всегда используйте
save=False; - последняя сохранённая настройка восстанавливается при включении клавиатуры;
- для экспериментов из оболочки используйте
--no-save.
Несохранённая сессия не вредит: следующая команда откроет её заново, а begin ответит ff.
Не пишите во флеш каждый кадр
Ресурс перезаписи флеш-памяти ограничен.
Анимация с save=True в цикле быстро его израсходует.
Слепые слоты¶
39 слотов из 143 не подключены ни к одному светодиоду. Прошивка молча принимает в них цвет, но свечения не будет.
Приём: разовая раскраска¶
#!/usr/bin/env python3
"""Двухцветная раскраска: буквам один цвет, остальным другой."""
from katana import Katana, KEYS_COUNT, resolve_key
LETTERS = {resolve_key(letter) for letter in "qwertyuiopasdfghjklzxcvbnm"}
def make_table(base, accent):
"""143 кортежа (r, g, b): буквам accent, остальным base."""
return [accent if index in LETTERS else base for index in range(KEYS_COUNT)]
keyboard = Katana()
try:
keyboard.write_key_table(make_table((30, 0, 40), (255, 120, 0)), save=False)
keyboard.show_custom(brightness=15, save=True)
finally:
keyboard.close()
save выполняется один раз, в show_custom.
Таблица остаётся несохранённой, но горит до выключения клавиатуры.
Приём: анимация¶
#!/usr/bin/env python3
"""Синяя волна пробегает по цифровому ряду слева направо."""
import math
import time
from katana import Katana, KEYS_COUNT, resolve_key
ROW = [resolve_key(key) for key in "`1234567890-="]
FRAME_PAUSE = 0.2
keyboard = Katana()
try:
keyboard.write_key_table([(0, 0, 0)] * KEYS_COUNT, save=False)
keyboard.show_custom(brightness=15, save=True)
phase = 0
while True:
table = [(0, 0, 0)] * KEYS_COUNT
for position, index in enumerate(ROW):
wave = max(0.0, math.cos((position - phase) * math.pi / 6))
table[index] = (0, 0, int(255 * wave))
keyboard.write_key_table(table, save=False)
phase = (phase + 1) % (len(ROW) + 6)
time.sleep(FRAME_PAUSE)
finally:
keyboard.close()
Советы:
- меняйте за кадр всю таблицу целиком;
- не пишите
saveвнутри цикла; - гасите таблицу в начале, иначе волна наложится на старую раскраску;
- для дыхания масштабируйте цвет всех клавиш одним коэффициентом.
Приём: дорисовка поверх кадра¶
frame = keyboard.read_frame()
table = list(frame)
table[resolve_key("esc")] = (255, 0, 0)
keyboard.write_key_table(table, save=False)
Так работают индикаторы: Caps Lock красным, заряд беспроводного режима зелёным, напоминание о перерыве.
Ограничение: в режиме 0x80 кадр — это сама таблица, поэтому наложить своё поверх встроенного эффекта прошивки нельзя.
Приём: сценарии из оболочки¶
#!/usr/bin/env python3
"""./my_fx.py wave|breath — выбор сценария первым аргументом."""
import sys
def wave(keyboard):
...
def breath(keyboard):
...
SCENARIOS = {"wave": wave, "breath": breath}
if __name__ == "__main__":
from katana import Katana
name = sys.argv[1] if len(sys.argv) > 1 else "wave"
keyboard = Katana()
try:
SCENARIOS[name](keyboard)
finally:
keyboard.close()
Цвета принимайте через parse_color из katana: так скрипт поймёт и ff8800, и darkorange.
Грабли¶
pyusbсdetach_kernel_driverломает печать: используйте hidraw.- Долгая висящая сессия клинит прошивку; лечение —
reset. - Не сокращайте паузу
0,04секунды. read_frame()возвращает цвета, умноженные на яркость: максимумee, а неff.- Слот
0x6dвсегда читается как000000.
Идеи¶
- индикатор загрузки процессора или памяти;
- подсветка уведомлений;
- таймер работы и перерыва;
- плавный рассвет;
- визуализация музыки;
- подсветка зоны WASD в играх.
Открытые направления протокола перечислены в разделе Реверс-инжиниринг.