fizzctl 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fizzctl-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.3
2
+ Name: fizzctl
3
+ Version: 0.2.0
4
+ Summary: Control a Redragon K617 Fizz keyboard on Linux: solid colors, 22 firmware-native RGB effects, per-key painting, live animations, and Cfg.ini keymap restore — no vendor software required
5
+ Keywords: redragon,k617,fizz,rgb,keyboard,backlight,led,hid,linux
6
+ Author: Ayoub Dya
7
+ Author-email: Ayoub Dya <ayoubdya@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
16
+ Classifier: Topic :: System :: Hardware
17
+ Requires-Dist: dpkt>=1.9.8
18
+ Requires-Dist: hidapi>=0.15.0
19
+ Requires-Python: >=3.14
20
+ Description-Content-Type: text/markdown
21
+
22
+ # fizzctl
23
+
24
+ Linux tool for controlling the Redragon K617 Fizz keyboard's RGB lighting
25
+ and restoring keymaps from `Cfg.ini` files.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ # from PyPI
31
+ pip install fizzctl
32
+
33
+ # from PyPI (uv)
34
+ uv tool install fizzctl
35
+
36
+ # from source
37
+ git clone https://github.com/you/fizzctl.git && cd fizzctl
38
+ uv build && uv tool install .
39
+ ```
40
+
41
+ ### Udev rules (needs one `sudo`)
42
+
43
+ ```bash
44
+ fizzctl setup-udev
45
+ ```
46
+
47
+ This installs `99-k617.rules` to `/etc/udev/rules.d/`, reloads udev and
48
+ re-triggers device events. The command elevates its own privileged steps
49
+ via `sudo` (you are prompted for your password), so you do **not** need to
50
+ wrap it as `sudo fizzctl ...` — that fails because the binary lives in a
51
+ user-local path like `~/.local/bin`. Unplug/replug the keyboard afterwards if
52
+ the trigger did not pick it up.
53
+
54
+ ## Commands
55
+
56
+ | Command | Description | Persists |
57
+ |---|---|---|
58
+ | `rgb` | Set the whole board to a solid color | yes |
59
+ | `effect` | Run one of 22 firmware-native effects | yes |
60
+ | `key` | Paint a single key | yes |
61
+ | `paint` | Paint multiple keys at once | yes |
62
+ | `keymap` | Write the keymap from a Cfg.ini | yes |
63
+ | `animate` | Host-streamed animation (volatile) | no |
64
+
65
+ ## Examples
66
+
67
+ All commands accept short flag aliases alongside their long forms:
68
+ | Long | Short | Applies to |
69
+ |---|---|---|
70
+ | `--color` | `-c` | `effect`, `animate` |
71
+ | `--speed` | `-s` | `effect`, `animate` |
72
+ | `--brightness` | `-b` | `effect`, `rgb` |
73
+ | `--fps` | `-f` | `animate` |
74
+
75
+ ### Write keymap (most important)
76
+
77
+ Apply the full keymap (bindings, lighting zones, function keys) from a
78
+ `Cfg.ini` — the file the official Redragon software exports:
79
+
80
+ ```bash
81
+ fizzctl keymap Cfg.ini
82
+ ```
83
+
84
+ ### Set the whole board to one color
85
+
86
+ ```bash
87
+ fizzctl rgb red
88
+ fizzctl rgb 00ff00 --brightness 4 # or: rgb 00ff00 -b 4
89
+ ```
90
+
91
+ ### Firmware effects
92
+
93
+ All 22 effects from the official Redragon software are supported, with full
94
+ speed and brightness control. `speed` and `brightness` are 5 levels (0-4,
95
+ matching the firmware); higher = faster / brighter. Run `fizzctl effect`
96
+ with no name to list every effect:
97
+
98
+ ```bash
99
+ fizzctl effect
100
+ fizzctl effect rainbow
101
+ fizzctl effect rainbow --speed 2 --brightness 4 # or: -s 2 -b 4
102
+ fizzctl effect fixed-on --color 00ff00 --brightness 3
103
+ fizzctl effect snake --color ff0000 --speed 4
104
+ fizzctl effect off
105
+ ```
106
+
107
+ Aliases are supported: `static` → `fixed-on`, `wheel` → `rainbow-wheel`,
108
+ `snake` → `retro-snake`, `waterfall` → `colorful-waterfall`.
109
+
110
+ ### Per-key painting
111
+
112
+ Keys are named by their top-left legend. Any key *not* listed turns off
113
+ because the canvas is absolute:
114
+
115
+ ```bash
116
+ fizzctl key W ff0000
117
+ fizzctl paint W=ff0000 A=00ff00 S=ffff00 D=ff00ff Space=ffffff
118
+ ```
119
+
120
+ ### Host-side animations
121
+
122
+ Volatile — lost when the keyboard reconnects or reboots. Run `fizzctl
123
+ animate` with no name to list the animations:
124
+
125
+ ```bash
126
+ fizzctl animate
127
+ fizzctl animate rainbow --fps 30 --duration 10 # or: -f 30
128
+ fizzctl animate chase --color ff0000 --speed 2 # or: -c yellow -s 2
129
+ fizzctl animate solid --color 0000ff
130
+ ```
131
+
132
+ ### Debug mode
133
+
134
+ ```bash
135
+ fizzctl --debug effect rainbow
136
+ ```
137
+
138
+ Shows the raw frames and handshake bytes sent over HID.
139
+
140
+ ## Dev tools
141
+
142
+ The `fizzctl-dev` binary exposes the full reverse-engineering toolkit
143
+ (capture inspect, diff, export, replay) in addition to all user commands.
144
+
145
+ ```bash
146
+ fizzctl-dev inspect captures/r3.json
147
+ fizzctl-dev diff captures/r2.json captures/r3.json
148
+ fizzctl-dev export captures/r2.json frames.json
149
+ fizzctl-dev replay frames.json
150
+ fizzctl-dev cfg Cfg.ini
151
+ fizzctl-dev list
152
+ ```
153
+
154
+ ## How the RGB paths work
155
+
156
+ **Flash writes** (`rgb`, `key`, `paint`, `effect`, `keymap`) send a 4-5 frame
157
+ burst through the vendor HID interface (`258a:0049`, interface 1, usage page
158
+ `0xFF00`). The sequence ends with a `5AA5` magic commit that writes to flash.
159
+ Colors persist across reboots. Keys not listed in a `key` or `paint` canvas
160
+ turn off.
161
+
162
+ **Animations** (`animate`) stream 382-byte per-key reports at the requested
163
+ frame rate with no flash commit. They are host-side only and lost on
164
+ reconnect.
165
+
166
+ ## Colors
167
+
168
+ Colors can be given as a name or `RRGGBB` hex (with or without `#`). Spaces,
169
+ dashes and underscores in names are ignored, so `light green`, `light-green`
170
+ and `lightgreen` are equivalent:
171
+
172
+ ```text
173
+ red green blue white black off
174
+ yellow cyan magenta orange pink purple lime teal violet indigo coral salmon
175
+ navy brown gold silver gray grey olive maroon
176
+ lightred lightgreen lightblue darkred darkgreen darkblue
177
+ ```
178
+
179
+ Both `-c yellow` and `--color ffaa00` work anywhere a color flag is accepted.
@@ -0,0 +1,158 @@
1
+ # fizzctl
2
+
3
+ Linux tool for controlling the Redragon K617 Fizz keyboard's RGB lighting
4
+ and restoring keymaps from `Cfg.ini` files.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ # from PyPI
10
+ pip install fizzctl
11
+
12
+ # from PyPI (uv)
13
+ uv tool install fizzctl
14
+
15
+ # from source
16
+ git clone https://github.com/you/fizzctl.git && cd fizzctl
17
+ uv build && uv tool install .
18
+ ```
19
+
20
+ ### Udev rules (needs one `sudo`)
21
+
22
+ ```bash
23
+ fizzctl setup-udev
24
+ ```
25
+
26
+ This installs `99-k617.rules` to `/etc/udev/rules.d/`, reloads udev and
27
+ re-triggers device events. The command elevates its own privileged steps
28
+ via `sudo` (you are prompted for your password), so you do **not** need to
29
+ wrap it as `sudo fizzctl ...` — that fails because the binary lives in a
30
+ user-local path like `~/.local/bin`. Unplug/replug the keyboard afterwards if
31
+ the trigger did not pick it up.
32
+
33
+ ## Commands
34
+
35
+ | Command | Description | Persists |
36
+ |---|---|---|
37
+ | `rgb` | Set the whole board to a solid color | yes |
38
+ | `effect` | Run one of 22 firmware-native effects | yes |
39
+ | `key` | Paint a single key | yes |
40
+ | `paint` | Paint multiple keys at once | yes |
41
+ | `keymap` | Write the keymap from a Cfg.ini | yes |
42
+ | `animate` | Host-streamed animation (volatile) | no |
43
+
44
+ ## Examples
45
+
46
+ All commands accept short flag aliases alongside their long forms:
47
+ | Long | Short | Applies to |
48
+ |---|---|---|
49
+ | `--color` | `-c` | `effect`, `animate` |
50
+ | `--speed` | `-s` | `effect`, `animate` |
51
+ | `--brightness` | `-b` | `effect`, `rgb` |
52
+ | `--fps` | `-f` | `animate` |
53
+
54
+ ### Write keymap (most important)
55
+
56
+ Apply the full keymap (bindings, lighting zones, function keys) from a
57
+ `Cfg.ini` — the file the official Redragon software exports:
58
+
59
+ ```bash
60
+ fizzctl keymap Cfg.ini
61
+ ```
62
+
63
+ ### Set the whole board to one color
64
+
65
+ ```bash
66
+ fizzctl rgb red
67
+ fizzctl rgb 00ff00 --brightness 4 # or: rgb 00ff00 -b 4
68
+ ```
69
+
70
+ ### Firmware effects
71
+
72
+ All 22 effects from the official Redragon software are supported, with full
73
+ speed and brightness control. `speed` and `brightness` are 5 levels (0-4,
74
+ matching the firmware); higher = faster / brighter. Run `fizzctl effect`
75
+ with no name to list every effect:
76
+
77
+ ```bash
78
+ fizzctl effect
79
+ fizzctl effect rainbow
80
+ fizzctl effect rainbow --speed 2 --brightness 4 # or: -s 2 -b 4
81
+ fizzctl effect fixed-on --color 00ff00 --brightness 3
82
+ fizzctl effect snake --color ff0000 --speed 4
83
+ fizzctl effect off
84
+ ```
85
+
86
+ Aliases are supported: `static` → `fixed-on`, `wheel` → `rainbow-wheel`,
87
+ `snake` → `retro-snake`, `waterfall` → `colorful-waterfall`.
88
+
89
+ ### Per-key painting
90
+
91
+ Keys are named by their top-left legend. Any key *not* listed turns off
92
+ because the canvas is absolute:
93
+
94
+ ```bash
95
+ fizzctl key W ff0000
96
+ fizzctl paint W=ff0000 A=00ff00 S=ffff00 D=ff00ff Space=ffffff
97
+ ```
98
+
99
+ ### Host-side animations
100
+
101
+ Volatile — lost when the keyboard reconnects or reboots. Run `fizzctl
102
+ animate` with no name to list the animations:
103
+
104
+ ```bash
105
+ fizzctl animate
106
+ fizzctl animate rainbow --fps 30 --duration 10 # or: -f 30
107
+ fizzctl animate chase --color ff0000 --speed 2 # or: -c yellow -s 2
108
+ fizzctl animate solid --color 0000ff
109
+ ```
110
+
111
+ ### Debug mode
112
+
113
+ ```bash
114
+ fizzctl --debug effect rainbow
115
+ ```
116
+
117
+ Shows the raw frames and handshake bytes sent over HID.
118
+
119
+ ## Dev tools
120
+
121
+ The `fizzctl-dev` binary exposes the full reverse-engineering toolkit
122
+ (capture inspect, diff, export, replay) in addition to all user commands.
123
+
124
+ ```bash
125
+ fizzctl-dev inspect captures/r3.json
126
+ fizzctl-dev diff captures/r2.json captures/r3.json
127
+ fizzctl-dev export captures/r2.json frames.json
128
+ fizzctl-dev replay frames.json
129
+ fizzctl-dev cfg Cfg.ini
130
+ fizzctl-dev list
131
+ ```
132
+
133
+ ## How the RGB paths work
134
+
135
+ **Flash writes** (`rgb`, `key`, `paint`, `effect`, `keymap`) send a 4-5 frame
136
+ burst through the vendor HID interface (`258a:0049`, interface 1, usage page
137
+ `0xFF00`). The sequence ends with a `5AA5` magic commit that writes to flash.
138
+ Colors persist across reboots. Keys not listed in a `key` or `paint` canvas
139
+ turn off.
140
+
141
+ **Animations** (`animate`) stream 382-byte per-key reports at the requested
142
+ frame rate with no flash commit. They are host-side only and lost on
143
+ reconnect.
144
+
145
+ ## Colors
146
+
147
+ Colors can be given as a name or `RRGGBB` hex (with or without `#`). Spaces,
148
+ dashes and underscores in names are ignored, so `light green`, `light-green`
149
+ and `lightgreen` are equivalent:
150
+
151
+ ```text
152
+ red green blue white black off
153
+ yellow cyan magenta orange pink purple lime teal violet indigo coral salmon
154
+ navy brown gold silver gray grey olive maroon
155
+ lightred lightgreen lightblue darkred darkgreen darkblue
156
+ ```
157
+
158
+ Both `-c yellow` and `--color ffaa00` work anywhere a color flag is accepted.
@@ -0,0 +1,49 @@
1
+ [project]
2
+ name = "fizzctl"
3
+ version = "0.2.0"
4
+ description = "Control a Redragon K617 Fizz keyboard on Linux: solid colors, 22 firmware-native RGB effects, per-key painting, live animations, and Cfg.ini keymap restore — no vendor software required"
5
+ readme = "README.md"
6
+ keywords = [
7
+ "redragon",
8
+ "k617",
9
+ "fizz",
10
+ "rgb",
11
+ "keyboard",
12
+ "backlight",
13
+ "led",
14
+ "hid",
15
+ "linux",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: End Users/Desktop",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: System :: Hardware :: Hardware Drivers",
25
+ "Topic :: System :: Hardware",
26
+ ]
27
+ requires-python = ">=3.14"
28
+ dependencies = [
29
+ "dpkt>=1.9.8",
30
+ "hidapi>=0.15.0",
31
+ ]
32
+
33
+ [project.license]
34
+ text = "MIT"
35
+
36
+ [[project.authors]]
37
+ name = "Ayoub Dya"
38
+ email = "ayoubdya@gmail.com"
39
+
40
+ [project.scripts]
41
+ fizzctl = "fizzctl:main"
42
+ fizzctl-dev = "fizzctl:main_dev"
43
+
44
+ [build-system]
45
+ requires = ["uv_build>=0.12.13,<0.13.0"]
46
+ build-backend = "uv_build"
47
+
48
+ [tool.uv]
49
+ package = true
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "fizzctl"
3
+ version = "0.2.0"
4
+ description = "Control a Redragon K617 Fizz keyboard on Linux: solid colors, 22 firmware-native RGB effects, per-key painting, live animations, and Cfg.ini keymap restore — no vendor software required"
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ authors = [
8
+ { name = "Ayoub Dya", email = "ayoubdya@gmail.com" }
9
+ ]
10
+ keywords = [
11
+ "redragon",
12
+ "k617",
13
+ "fizz",
14
+ "rgb",
15
+ "keyboard",
16
+ "backlight",
17
+ "led",
18
+ "hid",
19
+ "linux",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 4 - Beta",
23
+ "Environment :: Console",
24
+ "Intended Audience :: End Users/Desktop",
25
+ "Operating System :: POSIX :: Linux",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Topic :: System :: Hardware :: Hardware Drivers",
29
+ "Topic :: System :: Hardware",
30
+ ]
31
+ requires-python = ">=3.14"
32
+ dependencies = [
33
+ "dpkt>=1.9.8",
34
+ "hidapi>=0.15.0",
35
+ ]
36
+
37
+ [project.scripts]
38
+ fizzctl = "fizzctl:main"
39
+ fizzctl-dev = "fizzctl:main_dev"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.12.13,<0.13.0"]
43
+ build-backend = "uv_build"
44
+
45
+ [tool.uv]
46
+ package = true
@@ -0,0 +1,10 @@
1
+ """fizzctl — Linux tooling for the Redragon K617 Fizz.
2
+
3
+ User commands live in the :func:`main` entry point (rgb/effect/key/paint/
4
+ animate/keymap/setup-udev); the full reverse-engineering toolkit is exposed
5
+ by :func:`main_dev` via the ``fizzctl-dev`` console script.
6
+ """
7
+ from .cli import main, main_dev
8
+
9
+ __all__ = ["main", "main_dev"]
10
+ __version__ = "0.2.0"
@@ -0,0 +1,145 @@
1
+ """Host-side per-key animations streamed at N fps via the Sinodragon protocol.
2
+
3
+ These are NOT firmware effects — they render color maps on the host and push
4
+ one 382-byte per-key report per frame (volatile; stop with Ctrl+C). Only the
5
+ 8 firmware-native effects (`fizzctl effect`) survive a disconnect.
6
+
7
+ Animation helpers are pure ("render_frame(t, color, speed) -> dict") so they
8
+ can be unit-tested without hardware.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import math
13
+ import time
14
+
15
+ from .effects import PER_KEY_POS, encode_per_key_frame, parse_color
16
+
17
+ ANIMATIONS = ["solid", "blink", "pulse", "chase", "wave", "rainbow", "drop"]
18
+
19
+
20
+ def hsv(h: float, s: float, v: float) -> tuple[int, int, int]:
21
+ """HSV (h 0..360, s/v 0..1) -> (r,g,b) 0..255."""
22
+ c = v * s
23
+ x = c * (1 - abs((h / 60) % 2 - 1))
24
+ m = v - c
25
+ if h < 60:
26
+ r, g, b = c, x, 0
27
+ elif h < 120:
28
+ r, g, b = x, c, 0
29
+ elif h < 180:
30
+ r, g, b = 0, c, x
31
+ elif h < 240:
32
+ r, g, b = 0, x, c
33
+ elif h < 300:
34
+ r, g, b = x, 0, c
35
+ else:
36
+ r, g, b = c, 0, x
37
+ return int((r + m) * 255), int((g + m) * 255), int((b + m) * 255)
38
+
39
+
40
+ def render_frame(t: float, anim: str, color: tuple[int, int, int],
41
+ speed: float = 1.0) -> dict[str, tuple[int, int, int]]:
42
+ """Produce a color map for animation `anim` at time `t` (seconds).
43
+
44
+ speed scales the cycle period (higher = faster). Positions are the
45
+ verified col*6+row raster from effects.PER_KEY_POS.
46
+ """
47
+ r, g, b = color
48
+ out: dict[str, tuple[int, int, int]] = {}
49
+ period = max(0.1, 2.0 / speed)
50
+
51
+ if anim == "solid":
52
+ return {k: color for k in PER_KEY_POS}
53
+
54
+ if anim == "blink":
55
+ cycle = (t / period) % 1
56
+ return {k: color if cycle < 0.5 else (0, 0, 0) for k in PER_KEY_POS}
57
+
58
+ if anim == "pulse":
59
+ v = 0.5 + 0.5 * math.sin(2 * math.pi * t / period)
60
+ dim = tuple(int(c * v) for c in color)
61
+ return {k: dim for k in PER_KEY_POS}
62
+
63
+ if anim == "chase":
64
+ order = sorted(PER_KEY_POS, key=lambda k: PER_KEY_POS[k])
65
+ idx = int((t / period) * len(order)) % len(order)
66
+ for i, k in enumerate(order):
67
+ dist = (i - idx) % len(order)
68
+ frac = max(0.0, 1.0 - dist / 5)
69
+ out[k] = tuple(int(c * frac) for c in color) if dist < 5 else (0, 0, 0)
70
+ return out
71
+
72
+ if anim == "wave":
73
+ for k, pos in PER_KEY_POS.items():
74
+ col = pos // 6
75
+ phase = (t / period + col / 14) % 1
76
+ v = 0.5 + 0.5 * math.sin(2 * math.pi * phase)
77
+ out[k] = tuple(int(c * v) for c in color)
78
+ return out
79
+
80
+ if anim == "rainbow":
81
+ order = sorted(PER_KEY_POS, key=lambda k: PER_KEY_POS[k])
82
+ for i, k in enumerate(order):
83
+ hue = (360 * t / period + 360 * i / len(order)) % 360
84
+ out[k] = hsv(hue, 1.0, 1.0)
85
+ return out
86
+
87
+ if anim == "drop":
88
+ for k, pos in PER_KEY_POS.items():
89
+ col = pos // 6
90
+ row = pos % 6
91
+ phase = ((t * speed + col) % 6) - row
92
+ out[k] = color if 0 <= phase < 1 else (0, 0, 0)
93
+ return out
94
+
95
+ raise ValueError(f"unknown animation {anim!r}; choose from {', '.join(ANIMATIONS)}")
96
+
97
+
98
+ def run_animation(dev, anim: str, color: tuple[int, int, int],
99
+ fps: int = 30, speed: float = 1.0, duration: float | None = None) -> None:
100
+ """Stream `anim` until Ctrl+C (or `duration` seconds)."""
101
+ from .hid import send_per_key
102
+
103
+ period = 1.0 / fps
104
+ start = time.monotonic()
105
+ try:
106
+ while True:
107
+ if duration is not None and time.monotonic() - start >= duration:
108
+ break
109
+ frame = encode_per_key_frame(render_frame(time.monotonic() - start, anim, color, speed))
110
+ send_per_key(dev, frame)
111
+ time.sleep(period)
112
+ except KeyboardInterrupt:
113
+ pass
114
+
115
+
116
+ def cmd_animate(args):
117
+ """fizzctl animate <name> [--color HEX] [--speed N] [--fps N] [--duration S]"""
118
+ from .hid import NoDeviceError, open_device
119
+
120
+ anim = args.name
121
+ if anim is None:
122
+ print("Host-side animations (volatile — lost on disconnect). Run like:")
123
+ print(" fizzctl animate chase --color yellow --speed 2 --fps 30")
124
+ for name in ANIMATIONS:
125
+ print(f" {name}")
126
+ return 0
127
+ if anim not in ANIMATIONS:
128
+ print(f"unknown animation {anim!r}. Available: {', '.join(ANIMATIONS)}")
129
+ return 1
130
+ color = parse_color(args.color or "ff0000")
131
+ if color is None:
132
+ print(f"bad color {args.color!r}")
133
+ return 1
134
+ try:
135
+ dev = open_device(debug=getattr(args, 'debug', False))
136
+ except NoDeviceError:
137
+ return 1
138
+ if dev is None:
139
+ return 1
140
+ try:
141
+ print(f"streaming {anim} at {args.fps}fps (Ctrl+C to stop)...")
142
+ run_animation(dev, anim, color, fps=args.fps, speed=args.speed, duration=args.duration)
143
+ finally:
144
+ dev.close()
145
+ return 0