16 KiB
Расширение функционала: свои скрипты и анимации
Встроенных команд katana.py хватает для настройки подсветки, но настоящий интерес начинается, когда вы пишете собственные скрипты.
Модуль спроектирован как библиотека: класс Katana и вспомогательные функции импортируются в любой ваш скрипт одной строкой.
Документ описывает программный интерфейс, его ограничения и готовые приёмы. Справка по CLI-командам — в cli/README.md, низкоуровневые детали протокола — в protocol.md.
Что даёт per-key API
Прошивка хранит таблицу из 143 слотов [индекс, R, G, B] и умеет показывать её как отдельный режим 0x80.
Ключевое свойство, проверенное на железе: таблица обновляется живьём.
Если подсветка уже в режиме 0x80, каждая новая запись таблицы сразу меняет свечение клавиш — переключать режим повторно не нужно.
Именно это делает возможными анимации: цикл «изменил таблицу → записал → пауза» превращается в эффект.
Второе свойство — чтение живого кадра (04 f5): скрипт видит, что сейчас светится, с учётом яркости и активных анимаций.
На этом строятся скрипты, которые дорисовывают поверх текущей картинки, не затирая её.
Подключение библиотеки
Все примеры предполагают, что скрипт лежит рядом с katana.py (или путь добавлен в sys.path).
from light import Katana, KEYS_COUNT, resolve_key, build_payload
keyboard = Katana() # находит /dev/hidrawN и открывает его
try:
... # работа с клавиатурой
finally:
keyboard.close() # обязательно закрывать дескриптор
Импортируется именно light, а не дублируется транспорт: все проверки ACK, контрольных сумм и таймингов уже внутри.
Справочник API
| Объект | Сигнатура | Что делает |
|---|---|---|
Katana() |
конструктор | Находит hidraw-узел интерфейса 0 и открывает его. |
keyboard.close() |
— | Закрывает дескриптор. Вызывайте в finally. |
keyboard.lighting(payload, save=True) |
payload — 64 байта | Полная транзакция: begin → data-begin → payload → commit → save. Переключает режим подсветки. |
keyboard.write_key_table(colors, save=True) |
colors — 143 кортежей (r, g, b) |
Записывает per-key таблицу (9 пакетов). При активном режиме 0x80 свечение меняется сразу. |
keyboard.show_custom(brightness=15, save=True) |
brightness 1–15 | Переключает подсветку в режим 0x80 (показ таблицы). |
keyboard.read_frame() |
— | Читает живой кадр, возвращает список из 143 кортежей (r, g, b). |
keyboard.reset() |
— | USB port reset: снимает клин прошивки без переподключения. |
resolve_key(name) |
'tab', 'tilde', '0x25' |
Имя или символ клавиши → LED-индекс (None, если не найдено). |
build_payload(mode, rgb, ...) |
см. докстринг | Собирает 64-байтный payload для lighting(). |
KEYS_COUNT |
константа | Размер таблицы: 143 слота (0x00–0x8e). |
Имена слотов: индекс — это позиция в таблице, а не номер клавиши.
Карта «индекс → физическая клавиша» — в keymap.py (KEYMAP, BLIND), человекочитаемо: python3 keymap.py.
Ограничения, которые надо знать заранее
Скорость: ~1 кадр в секунду
Между любыми двумя USB-запросами выдерживается пауза DELAY = 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=True после каждой записи коммитит настройки во флеш-память, у которой ограниченный ресурс перезаписи.
Для анимаций это недопустимо расточительно, поэтому:
- в цикле анимации всегда используйте
save=False; - настройка, сохранённая последним
save, остаётся во флеше и восстанавливается при включении клавиатуры; - правило проекта: вызовы
./katana.py paintиз шелла — только с--no-save(см. AGENTS.md).
Несохранённая сессия не вредит: следующая команда откроет её заново (begin ответит ff = «сессия уже открыта», это штатно).
Но не оставляйте сессию открытой на часы — при зависании прошивки помогает katana.py reset.
Слепые слоты
39 слотов из 143 ни к какому LED не подключены (список BLIND в keymap.py).
Прошивка молча принимает в них любой цвет, но светиться ничего не будет — не ищите баг у себя.
Приём 1. Разовая раскраска скриптом
Скрипт-«пресет»: заливка по вашему алгоритму вместо перечисления --key в шелле.
#!/usr/bin/env python3
"""Двухцветная раскраска: буквенный блок — один цвет, остальное — другой."""
import time
from light import Katana, KEYS_COUNT, resolve_key
from keymap import KEYMAP
LETTERS = {resolve_key(l) for l in "qwertyuiopasdfghjklzxcvbnm"}
def make_table(base, accent):
"""143 кортежей (r, g, b): буквам — accent, остальным — base."""
return [accent if i in LETTERS else base for i 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) # режим 0x80 + один save
finally:
keyboard.close()
Обратите внимание: save выполняется один раз — в show_custom.
Таблица при этом остаётся несохранённой во флеше, но горит до выключения клавиатуры.
Приём 2. Анимация: бегущая волна
Каркас любой анимации: подготовить режим 0x80 один раз, затем в цикле менять таблицу.
#!/usr/bin/env python3
"""Синяя волна пробегает по цифровому ряду слева направо."""
import math
import time
from light import Katana, KEYS_COUNT, resolve_key
ROW = [resolve_key(k) for k in "`1234567890-="] # индексы 0x13..0x1f
FRAME_PAUSE = 0.2 # пауза между кадрами
keyboard = Katana()
try:
# один раз включаем режим 0x80 и гасим таблицу
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 pos, idx in enumerate(ROW):
# волна: яркость синего канала зависит от расстояния до фронта
wave = max(0.0, math.cos((pos - phase) * math.pi / 6))
table[idx] = (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()
Цикл бесконечный — останавливайте Ctrl+C, блок finally закроет дескриптор.
Полный кадр занимает ~0,8 с (запись + пауза), волна движется заметно плавно, потому что форма синусоиды «размазана» по 13 клавишам.
Советы по анимациям:
- меняйте за кадр всю таблицу целиком — частичные обновления всё равно требуют полной транзакции;
- не пишите
saveвнутри цикла никогда; - гасите таблицу в начале (
[(0, 0, 0)] * KEYS_COUNT), иначе волна наложится на старую раскраску; - для «дыхания» масштабируйте цвет всех клавиш одним коэффициентом — это дёшево и выглядит хорошо при 1 FPS.
Приём 3. Дорисовка поверх живого кадра
read_frame() возвращает текущую картинку, поэтому скрипт может добавлять акценты, не затирая остальное.
Это тот же механизм, что у команды paint --keep.
frame = keyboard.read_frame() # что светится сейчас
table = list(frame) # копия, чтобы не портить оригинал
table[resolve_key("esc")] = (255, 0, 0)
keyboard.write_key_table(table, save=False)
Так работают индикаторы: капс-лок красным, заряд беспроводного режима зелёным, напоминание о перерыве — подсветить ряд цифр после часа работы.
Данные для условия скрипт берёт откуда угодно: файл, сокет, D-Bus, os.stat.
Ограничение: в режиме 0x80 кадр — это сама таблица, поэтому «дорисовка поверх анимации прошивки» (например, поверх spectrum) даст статичную замену, а не наложение.
Смешивать своё и встроенные эффекты в одном кадре прошивка не умеет — это ограничение железа, а не API.
Приём 4. Переключение сценариев из шелла
Скриптам не нужен свой CLI, если хочется вызывать их как команды katana.py.
Оформите каждый сценарий функцией и выбирайте аргументом:
#!/usr/bin/env python3
"""./my_fx.py wave|breath|off — выбор сценария первым аргументом."""
import sys
def wave(kb): ...
def breath(kb): ...
SCENARIOS = {"wave": wave, "breath": breath}
if __name__ == "__main__":
from light import Katana
name = sys.argv[1] if len(sys.argv) > 1 else "wave"
kb = Katana()
try:
SCENARIOS[name](kb)
finally:
kb.close()
Привычные цвета принимайте через parse_color из light — так ваши скрипты поймут и ff8800, и darkorange.
Грабли
pyusbсdetach_kernel_driverломает печать. Транспорт — только hidraw-ioctl, как вkatana.py.- Сессию всегда доводите до
saveили закрывайте программой. Долгая висящая сессия клинит прошивку; лечение —katana.py reset. - Не укорачивайте
DELAY. Пауза 0,04 с снята с вендорских захватов; при ускорении прошивка начинает терять пакеты и ACK-и. read_frame()возвращает масштабированные яркостью цвета. При яркости 15/15 максимумee, а неff— не сравнивайте кадр с записанным цветом «в лоб».- Слот
0x6dвсегда читается как000000. Это null-слот прошивки, даже у вендорской утилиты.
Идеи для скриптов
- индикатор загрузки CPU или памяти — градиент по функциональному ряду;
- подсветка уведомлений: вспышка волной при новом сообщении;
- «помидор»: 25 минут работы — спокойный цвет, перерыв — зелёная заливка;
- плавный рассвет: медленное нарастание яркости и цвета по утрам;
- визуализация музыки через любой FFT-вход: спектр по рядам клавиатуры;
- световой «миникарт»: подсветка зоны WASD в играх, где это уместно.
Список открытых направлений самого протокола (переназначение клавиш, байты направлений) — в protocol.md → «Следующие шаги».