ajazz-rgb-cli 1.0.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.
- ajazz_rgb_cli-1.0.0/PKG-INFO +7 -0
- ajazz_rgb_cli-1.0.0/README.md +0 -0
- ajazz_rgb_cli-1.0.0/ajazz_cli/Device.py +150 -0
- ajazz_rgb_cli-1.0.0/ajazz_cli/Device_Mapper.py +6 -0
- ajazz_rgb_cli-1.0.0/ajazz_cli/__init__.py +0 -0
- ajazz_rgb_cli-1.0.0/ajazz_cli/main.py +80 -0
- ajazz_rgb_cli-1.0.0/ajazz_cli/utils.py +11 -0
- ajazz_rgb_cli-1.0.0/ajazz_rgb_cli.egg-info/PKG-INFO +7 -0
- ajazz_rgb_cli-1.0.0/ajazz_rgb_cli.egg-info/SOURCES.txt +13 -0
- ajazz_rgb_cli-1.0.0/ajazz_rgb_cli.egg-info/dependency_links.txt +1 -0
- ajazz_rgb_cli-1.0.0/ajazz_rgb_cli.egg-info/entry_points.txt +2 -0
- ajazz_rgb_cli-1.0.0/ajazz_rgb_cli.egg-info/requires.txt +1 -0
- ajazz_rgb_cli-1.0.0/ajazz_rgb_cli.egg-info/top_level.txt +1 -0
- ajazz_rgb_cli-1.0.0/pyproject.toml +16 -0
- ajazz_rgb_cli-1.0.0/setup.cfg +4 -0
|
File without changes
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import hid
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
CONTROL_INTERFACE = 3
|
|
5
|
+
PACKET_LENGTH = 64
|
|
6
|
+
|
|
7
|
+
# Command Codes (Preamble packet byte 1)
|
|
8
|
+
CMD_SAVE = 0x02
|
|
9
|
+
CMD_MODE = 0x13
|
|
10
|
+
|
|
11
|
+
MAX_BRIGHTNESS = 5
|
|
12
|
+
MAX_SPEED = 3
|
|
13
|
+
DEFAULT_DIRECTION = 0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
LIGHTING_MODES = {
|
|
17
|
+
'Off': 0x00,
|
|
18
|
+
'Static': 0x01,
|
|
19
|
+
'Single On': 0x02,
|
|
20
|
+
'Single Off': 0x03,
|
|
21
|
+
'Glittering': 0x04,
|
|
22
|
+
'Falling': 0x05,
|
|
23
|
+
'Colourful': 0x06,
|
|
24
|
+
'Breath': 0x07,
|
|
25
|
+
'Spectrum': 0x08,
|
|
26
|
+
'Outward': 0x09,
|
|
27
|
+
'Scrolling': 0x0a,
|
|
28
|
+
'Rolling': 0x0b,
|
|
29
|
+
'Rotating': 0x0c,
|
|
30
|
+
'Explode': 0x0d,
|
|
31
|
+
'Launch': 0x0e,
|
|
32
|
+
'Ripples': 0x0f,
|
|
33
|
+
'Flowing': 0x10,
|
|
34
|
+
'Pulsating': 0x11,
|
|
35
|
+
'Tilt': 0x12,
|
|
36
|
+
'Shuttle': 0x13,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
DIRECTIONS = {'Left': 0, 'Down': 1, 'Up': 2, 'Right': 3}
|
|
40
|
+
SLEEP_TIMERS = {'Never': 0, '1 minute': 1, '5 minutes': 2, '30 minutes': 3}
|
|
41
|
+
|
|
42
|
+
class Device:
|
|
43
|
+
def __init__(self, vendor_id, product_id):
|
|
44
|
+
self.vendor_id = vendor_id
|
|
45
|
+
self.product_id = product_id
|
|
46
|
+
self.device = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def print_device_info(self):
|
|
50
|
+
print("Manufacturer: %s" % self.device.get_manufacturer_string())
|
|
51
|
+
print("Product: %s" % self.device.get_product_string())
|
|
52
|
+
print("Serial No: %s" % self.device.get_serial_number_string())
|
|
53
|
+
|
|
54
|
+
def print_connected_interfaces(self):
|
|
55
|
+
devices = hid.enumerate(self.vendor_id, self.product_id)
|
|
56
|
+
for d in devices:
|
|
57
|
+
print(f"Path: {d['path']}")
|
|
58
|
+
print(f"Interface Number: {d['interface_number']}")
|
|
59
|
+
print(f"Usage Page: {hex(d['usage_page'])} | Usage: {hex(d['usage'])}")
|
|
60
|
+
print("-" * 35)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def connect(self):
|
|
64
|
+
try:
|
|
65
|
+
devices = hid.enumerate(self.vendor_id, self.product_id)
|
|
66
|
+
target_path = None
|
|
67
|
+
for d in devices:
|
|
68
|
+
if d['interface_number'] == CONTROL_INTERFACE:
|
|
69
|
+
target_path = d['path']
|
|
70
|
+
break
|
|
71
|
+
if target_path:
|
|
72
|
+
self.device = hid.device()
|
|
73
|
+
self.device.open_path(target_path)
|
|
74
|
+
# Set non-blocking to 0 so reads wait for the hardware handshake
|
|
75
|
+
self.device.set_nonblocking(0)
|
|
76
|
+
print("Connected to Ajazz AK820 Pro Control Interface!")
|
|
77
|
+
return True
|
|
78
|
+
print("Interface 3 not found.")
|
|
79
|
+
return False
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"Connection failed: {e}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def _handshake(self):
|
|
85
|
+
try:
|
|
86
|
+
# Read 64 bytes from the feature report stream (with a 500ms timeout)
|
|
87
|
+
response = self.device.get_feature_report(0x00, 65)
|
|
88
|
+
return response
|
|
89
|
+
except Exception:
|
|
90
|
+
# If it times out or skips, clear the buffer safely
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def set_static_color(self, r, g, b, mode=LIGHTING_MODES['Static'], rainbow=False, brightness=MAX_BRIGHTNESS, speed=MAX_SPEED, direction=DEFAULT_DIRECTION):
|
|
94
|
+
if not self.device:
|
|
95
|
+
print("Device offline.")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
# --- 1. RESET STATE PACKET ---
|
|
100
|
+
reset = [0x00] * 64
|
|
101
|
+
reset[0] = 0x04
|
|
102
|
+
reset[1] = 0x18
|
|
103
|
+
self.device.send_feature_report([0x00] + reset)
|
|
104
|
+
time.sleep(0.02) # Short pause for hardware processing
|
|
105
|
+
|
|
106
|
+
# --- 2. CONFIGURE LIGHTING PACKET ---
|
|
107
|
+
preamble = [0x00] * 64
|
|
108
|
+
preamble[0] = 0x04
|
|
109
|
+
preamble[1] = 0x13
|
|
110
|
+
preamble[8] = 0x01
|
|
111
|
+
self.device.send_feature_report([0x00] + preamble)
|
|
112
|
+
|
|
113
|
+
# --- 3. FIRST HANDSHAKE ---
|
|
114
|
+
self._handshake()
|
|
115
|
+
|
|
116
|
+
# --- 4. LIGHTING DATA PACKET ---
|
|
117
|
+
data = [0x00] * 64
|
|
118
|
+
data[0] = mode
|
|
119
|
+
data[1] = r
|
|
120
|
+
data[2] = g
|
|
121
|
+
data[3] = b
|
|
122
|
+
data[8] = rainbow
|
|
123
|
+
data[9] = brightness
|
|
124
|
+
data[10] = speed
|
|
125
|
+
data[11] = direction
|
|
126
|
+
data[14] = 0x55 # Magic validation footprint
|
|
127
|
+
data[15] = 0xAA # Magic validation footprint
|
|
128
|
+
self.device.send_feature_report([0x00] + data)
|
|
129
|
+
time.sleep(0.02)
|
|
130
|
+
|
|
131
|
+
# --- 5. SAVE PACKET ---
|
|
132
|
+
save = [0x00] * 64
|
|
133
|
+
save[0] = 0x04
|
|
134
|
+
save[1] = 0x02
|
|
135
|
+
self.device.send_feature_report([0x00] + save)
|
|
136
|
+
|
|
137
|
+
# --- 6. SECOND HANDSHAKE ---
|
|
138
|
+
self._handshake()
|
|
139
|
+
|
|
140
|
+
print(f"SetLightingMode sequence executed successfully! RGB({r}, {g}, {b})")
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"Protocol execution failed: {e}")
|
|
144
|
+
|
|
145
|
+
def close(self):
|
|
146
|
+
try:
|
|
147
|
+
self.device.close()
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(e)
|
|
150
|
+
print("Could not close device")
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from .Device_Mapper import device_mapper
|
|
7
|
+
from .Device import Device
|
|
8
|
+
from .utils import hex_to_rgb
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(description="Control Ajazz AK820 Pro RGB lighting.",formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
12
|
+
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
'-c', '--color',
|
|
15
|
+
type=hex_to_rgb,
|
|
16
|
+
default=(0, 255, 255),
|
|
17
|
+
help="Hex color code to apply (e.g., FF0000 for Red, 00FFFF for Cyan)"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
'-m', '--mode',
|
|
22
|
+
type=int,
|
|
23
|
+
default=1,
|
|
24
|
+
choices=range(0, 20),
|
|
25
|
+
help="Lighting mode ID (0: Off, 1: Static, 2: Single On, 6: Colourful, 7: Breath, 8: Spectrum, 9: Outward, 10: Scrolling, 11: Rolling, 12: Rotating, 13: Explode, 14: Launch, 15: Ripples, 16: Flowing, 17: Pulsating, 18: Tilt, 19: Shuttle)"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
'-b', '--brightness',
|
|
30
|
+
type=int,
|
|
31
|
+
default=5,
|
|
32
|
+
choices=range(0, 6),
|
|
33
|
+
help="LED Brightness level scale (0 to 5)"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'-s', '--speed',
|
|
38
|
+
type=int,
|
|
39
|
+
default=3,
|
|
40
|
+
choices=range(0, 6),
|
|
41
|
+
help="Animation effect speed (0 to 5)"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
'-d', '--direction',
|
|
46
|
+
type=int,
|
|
47
|
+
default=0,
|
|
48
|
+
choices=range(0, 4),
|
|
49
|
+
help="Animation effect direction (0 to 3)"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
'-r', '--rainbow',
|
|
54
|
+
action='store_true',
|
|
55
|
+
help="Enable rainbow effect"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
parser.add_argument('--vid', type=lambda x: int(x, 16), default=device_mapper["Microdia AK820"]["VID"], help="Vendor ID in Hex")
|
|
59
|
+
parser.add_argument('--pid', type=lambda x: int(x, 16), default=device_mapper["Microdia AK820"]["PID"], help="Product ID in Hex")
|
|
60
|
+
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
|
|
63
|
+
r, g, b = args.color
|
|
64
|
+
|
|
65
|
+
dev = Device(args.vid, args.pid)
|
|
66
|
+
|
|
67
|
+
if not dev.connect():
|
|
68
|
+
print("Error: Could not establish communication with the keyboard control endpoint.", file=sys.stderr)
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
dev.set_static_color(r, g, b, args.mode, args.rainbow, args.brightness, args.speed, args.direction)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
finally:
|
|
77
|
+
dev.close()
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
def hex_to_rgb(hex_str):
|
|
4
|
+
hex_str = hex_str.lstrip('#').strip()
|
|
5
|
+
if len(hex_str) != 6:
|
|
6
|
+
raise argparse.ArgumentTypeError("Hex color must be exactly 6 hex characters (e.g., FF00FF).")
|
|
7
|
+
try:
|
|
8
|
+
val = int(hex_str, 16)
|
|
9
|
+
return (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff
|
|
10
|
+
except ValueError:
|
|
11
|
+
raise argparse.ArgumentTypeError("Invalid hex characters.")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
ajazz_cli/Device.py
|
|
4
|
+
ajazz_cli/Device_Mapper.py
|
|
5
|
+
ajazz_cli/__init__.py
|
|
6
|
+
ajazz_cli/main.py
|
|
7
|
+
ajazz_cli/utils.py
|
|
8
|
+
ajazz_rgb_cli.egg-info/PKG-INFO
|
|
9
|
+
ajazz_rgb_cli.egg-info/SOURCES.txt
|
|
10
|
+
ajazz_rgb_cli.egg-info/dependency_links.txt
|
|
11
|
+
ajazz_rgb_cli.egg-info/entry_points.txt
|
|
12
|
+
ajazz_rgb_cli.egg-info/requires.txt
|
|
13
|
+
ajazz_rgb_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hidapi>=0.14.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ajazz_cli
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ajazz-rgb-cli"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "A terminal app to configure Ajazz AK820 Pro RGB on Linux"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"hidapi>=0.14.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
ajazz_cli = "ajazz_cli.main:main"
|