greendeck/greendeck/lib/elgato/streamdeck/gen2.py
2022-12-13 14:25:50 -06:00

184 lines
6.4 KiB
Python

import asyncio
import struct
from typing_extensions import Self
from greendeck.lib.elgato.streamdeck import StreamDeck
from greendeck.lib.hidapi.library import HIDAPIError
from greendeck.lib.util import task_done_callback
class StreamDeckGen2Base(StreamDeck):
MAX_PACKET_SIZE: int = 1024
async def _read(self: Self) -> None:
try:
while self.run_read_task:
data = await self.device.read(64)
if data is None:
continue
match data[0]:
case 0x01:
key_count = data[2]
states = [bool(s) for s in data[4 : 4 + key_count]]
t = asyncio.create_task(self.handleKeyReport(states))
t.add_done_callback(task_done_callback)
case 0x02:
# lcd input
# position1 = struct.unpack("<HH", data[5:9])
match data[3]:
case 0x01:
# short press
pass
case 0x02:
# long press
pass
case 0x03:
# swipe
# position2 = struct.unpack("<HH", data[9:12])
pass
case x:
print(f"unknown lcd input type {x}")
case 0x03:
# encoder
match data[3]:
case 0x00:
# encoder press/release
pass
case 0x01:
# rotate
pass
case x:
print(f"unknown input report type {x}")
except HIDAPIError:
print("HIDAPI Error")
self.run_read_thread = False
return
except asyncio.CancelledError:
print("cancelled")
self.run_read_thread = False
return
async def _reset_key_stream(self) -> None:
async with self.mutex:
payload = struct.pack(f"<B{self.MAX_PACKET_SIZE-1}x", 0x02)
await self.device.write(payload)
async def reset(self) -> None:
async with self.mutex:
payload = struct.pack("<BB30x", 0x03, 0x02)
# payload = b"\x03\x02" + (b"\x00" * 30)
await self.device.write_feature(payload)
async def set_brightness(self, percent: int | float):
"""
Sets the global screen brightness of the StreamDeck, across all the
physical buttons.
:param int/float percent: brightness percent, from [0-100] as an `int`,
or normalized to [0.0-1.0] as a `float`.
"""
async with self.mutex:
if isinstance(percent, float):
percent = int(round(100.0 * percent, 0))
percent = min(max(percent, 0), 100)
payload = struct.pack("<BBB29x", 0x03, 0x08, percent)
# payload = b"\x03\x08" + percent + (b"\x00" * 29)
await self.device.write_feature(payload)
async def get_serial_number(self) -> str:
"""
Gets the serial number of the attached StreamDeck.
:rtype: str
:return: String containing the serial number of the attached device.
"""
if self._serial_number is not None:
return self._serial_number
async with self.mutex:
if self._serial_number is None:
data = await self.device.read_feature(0x06, 32)
self._serial_number = self._extract_string(data[2:])
return self._serial_number
async def get_firmware_version(self) -> str:
"""
Gets the firmware version of the attached StreamDeck.
:rtype: str
:return: String containing the firmware version of the attached device.
"""
if self._firmware_version is not None:
return self._firmware_version
async with self.mutex:
if self._firmware_version is None:
data = await self.device.read_feature(0x05, 32)
self._firmware_version = self._extract_string(data[6:])
return self._firmware_version
async def set_key_image(self, key: int, image: bytes | None) -> None:
"""
Sets the image of a button on the StreamDeck to the given image. The
image being set should be in the correct format for the device, as an
enumerable collection of bytes.
.. seealso:: See :func:`~StreamDeck.get_key_image_format` method for
information on the image format accepted by the device.
:param int key: Index of the button whose image is to be updated.
:param enumerable image: Raw data of the image to set on the button.
If `None`, the key will be cleared to a black
color.
"""
IMAGE_REPORT_HEADER_LENGTH = struct.calcsize("<BBBBHH")
IMAGE_REPORT_PAYLOAD_LENGTH = self.MAX_PACKET_SIZE - IMAGE_REPORT_HEADER_LENGTH
async with self.mutex:
if min(max(key, 0), self.KEY_COUNT) != key:
raise IndexError("Invalid key index {}.".format(key))
image = bytes(image or self.BLANK_KEY_IMAGE)
page_number = 0
bytes_remaining = len(image)
while bytes_remaining > 0:
page_length = min(bytes_remaining, IMAGE_REPORT_PAYLOAD_LENGTH)
bytes_sent = page_number * IMAGE_REPORT_PAYLOAD_LENGTH
last_page = 1 if page_length == bytes_remaining else 0
header = struct.pack(
"<BBBBHH",
0x02,
0x07,
key,
last_page,
page_length,
page_number,
)
payload = header + image[bytes_sent : bytes_sent + page_length]
padding = b"\x00" * (self.MAX_PACKET_SIZE - len(payload))
await self.device.write(payload + padding)
bytes_remaining = bytes_remaining - page_length
page_number = page_number + 1