Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kraken230 firmware 2 image support #692

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions docs/developer/protocol/nzxt_x3-z3-2023.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
- Byte 13: data type. Possible values are:
- `0x01`: GIF data
- `0x02`: Static RGBA data
- `0x06`: Q565 Image data (only kraken 2023 with firmware >= v2.0.0)
- `0x08`: Q565 Image data (only kraken 2023 elit with firmware > v2.0.0)
- Transfer 2:
- Data: The binary data of the image you want to send to the device
2 changes: 2 additions & 0 deletions docs/kraken-x3-z3-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ _New in git._<br>

Adds support for NZXT Kraken 2023 Standard, Elite

*For firmware version 2 gif screen mode is no longer supported*
sophipl marked this conversation as resolved.
Show resolved Hide resolved

_New in 1.11.0._<br>

The LCD screen can be configured in a few different modes.
Expand Down
85 changes: 73 additions & 12 deletions liquidctl/driver/kraken3.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ class KrakenZ3(KrakenX3):
(
0x1E71,
0x300E,
"NZXT Kraken 2023 (broken)",
"NZXT Kraken 2023",
{
"speed_channels": _SPEED_CHANNELS_KRAKEN2023,
"color_channels": _COLOR_CHANNELS_KRAKEN2023,
Expand Down Expand Up @@ -781,16 +781,12 @@ def parse_lcd_info(msg):
self.brightness = msg[0x18]
self.orientation = msg[0x1A]

# Firmware 2.0.0 and onwards broke the implemented image setting mechanism for Kraken 2023
# (non-elite). In those cases, show an error until issue #631 is resolved.
def check_unsupported_fw_version():
def _is_2023_fw_version2():
device_product_id = self.device.product_id
if device_product_id == 0x300E:
self._get_fw_version()
if self._fw[0] == 2:
raise NotSupportedByDriver(
"setting images is not supported on firmware 2.X.Y, please see issue #631"
)
return self._fw[0] == 2
return False

self._read_until({b"\x31\x01": parse_lcd_info})

Expand All @@ -807,12 +803,26 @@ def check_unsupported_fw_version():
self._write([0x30, 0x02, 0x01, self.brightness, 0x0, 0x0, 0x1, int(value_int / 90)])
return
elif mode == "static":
check_unsupported_fw_version()
data = self._prepare_static_file(value, self.orientation)
self._send_data(data, [0x02, 0x0, 0x0, 0x0] + list(len(data).to_bytes(4, "little")))
if _is_2023_fw_version2():
data = self._prepare_static_file_rgb16(value, self.orientation)
self._send_2023_data_fw2(
data, [0x06, 0x0, 0x0, 0x0] + list(len(data).to_bytes(4, "little"))
)
# sending it twice is only required once after initialization
# the same behaviour is observed in manufacturer at init
# some soft of framebuffer swapping?
self._send_2023_data_fw2(
sophipl marked this conversation as resolved.
Show resolved Hide resolved
data, [0x06, 0x0, 0x0, 0x0] + list(len(data).to_bytes(4, "little"))
)
else:
data = self._prepare_static_file(value, self.orientation)
self._send_data(data, [0x02, 0x0, 0x0, 0x0] + list(len(data).to_bytes(4, "little")))
return
elif mode == "gif":
check_unsupported_fw_version()
if _is_2023_fw_version2():
raise NotSupportedByDriver(
"gif images are not supported on firmware 2.X.Y, please see issue #631"
)
data = self._prepare_gif_file(value, self.orientation)
assert (
len(data) / 1000 < _LCD_TOTAL_MEMORY
Expand Down Expand Up @@ -853,6 +863,28 @@ def _prepare_static_file(self, path, rotation):
result.append(0)
return result

def _prepare_static_file_rgb16(self, path, rotation):
"""
path is the path to any image file
Rotation is expected as 0 = no rotation, 1 = 90 degrees, 2 = 180 degrees, 3 = 270 degrees
"""
data = (
Image.open(path)
.resize(self.lcd_resolution)
.rotate(rotation * -90)
.convert("RGB")
.getdata()
)
result = []
pixelDataIndex = 0
for pixelDataIndex in range(0, len(data)):
dr = data[pixelDataIndex][0] >> 3
dg = data[pixelDataIndex][1] >> 2
db = data[pixelDataIndex][2] >> 3
result.append((dr << 3) + (dg >> 3))
result.append(((dg & 0x7) << 5) + db)
return result

def _prepare_gif_file(self, path, rotation):
"""
path is the path of the gif file
Expand Down Expand Up @@ -886,6 +918,35 @@ def prepare_frames(frames):

return result_bytes.getvalue()

def _send_2023_data_fw2(self, data, bulkInfo):
"""
This method is intended for Kraken 2023 firmware version 2.X.Y
sends image or gif to device
data is an array of bytes to write
bulk info contains info about the transfer
"""
self._write_then_read([0x36, 0x01, 0x00, 0x01, 0x06]) # start data transfer
header = [
0x12,
0xFA,
0x01,
0xE8,
0xAB,
0xCD,
0xEF,
0x98,
0x76,
0x54,
0x32,
0x10,
] + bulkInfo
self._bulk_write(header)

for i in range(0, len(data), self.bulk_buffer_size): # start sending data in chunks
self._bulk_write(list(data[i : i + self.bulk_buffer_size]))

self._write_then_read([0x36, 0x02]) # end data transfer

def _send_data(self, data, bulkInfo):
"""
sends image or gif to device
Expand Down