from greendeck.lib.hidapi.library import DeviceInfo from greendeck.lib.hidapi.library import Handle from greendeck.lib.hidapi.library import hid_close_device from greendeck.lib.hidapi.library import hid_enumerate from greendeck.lib.hidapi.library import hid_get_feature_report from greendeck.lib.hidapi.library import hid_open_device from greendeck.lib.hidapi.library import hid_read from greendeck.lib.hidapi.library import hid_send_feature_report from greendeck.lib.hidapi.library import hid_write __all__ = ["Device", "DeviceInfo", "enumerate_devices"] class Device: device_info: DeviceInfo device_handle: Handle | None def __init__(self, device_info: DeviceInfo): """ Creates a new HID device instance, used to send and receive HID reports from/to an attached USB HID device. :param dict() device_info: Device information dictionary describing a single unique attached USB HID device. """ self.device_info = device_info self.device_handle = None async def open(self) -> None: """ Opens the HID device for input/output. This must be called prior to sending or receiving any HID reports. .. seealso:: See :func:`~HID.Device.close` for the corresponding close method. """ if self.device_handle: return self.device_handle = await hid_open_device(self.device_info.path) async def close(self) -> None: """ Closes the HID device for input/output. .. seealso:: See :func:`~~HID.Device.open` for the corresponding open method. """ if self.device_handle: self.device_handle, device_handle = None, self.device_handle await hid_close_device(device_handle) async def is_open(self) -> bool: """ Indicates if the physical device object this instance is attached to has been opened by the host. :rtype: bool :return: `True` if the device is open, `False` otherwise. """ return self.device_handle is not None async def connected(self) -> bool: """ Indicates if the physical HID device this instance is attached to is still connected to the host. :rtype: bool :return: `True` if the device is still connected, `False` otherwise. """ return any([d.path == self.path for d in await hid_enumerate()]) @property def path(self) -> str: """ Retrieves the logical path of the attached HID device within the current system. This can be used to differentiate one HID device from another. :rtype: str :return: Logical device path for the attached device. """ return self.device_info.path @property def serial_number(self) -> int: return self.device_info.serial_number @property def vendor_id(self) -> int: return self.device_info.vendor_id @property def product_id(self) -> int: return self.device_info.product_id async def write_feature(self, payload: bytes) -> int: """ Sends a HID Feature report to the open HID device. :param enumerable() payload: Enumerate list of bytes to send to the device, as a feature report. The first byte of the report should be the Report ID of the report being sent. :rtype: int :return: Number of bytes successfully sent to the device. """ return await hid_send_feature_report(self.device_handle, payload) async def read_feature(self, report_id: int, length: int) -> bytes: """ Reads a HID Feature report from the open HID device. :param int report_id: Report ID of the report being read. :param int length: Maximum length of the Feature report to read.. :rtype: bytes :return: List of bytes containing the read Feature report. The first byte of the report will be the Report ID of the report that was read. """ return await hid_get_feature_report(self.device_handle, report_id, length) async def write(self, payload: bytes) -> int: """ Sends a HID Out report to the open HID device. :param bytes payload: bytes to send to the device, as an Out report. The first byte of the report should be the Report ID of the report being sent. :rtype: int :return: Number of bytes successfully sent to the device. """ return await hid_write(self.device_handle, payload) async def read(self, length: int) -> bytes | None: """ Performs a non-blocking read of a HID In report from the open HID device. :param int length: Maximum length of the In report to read. :rtype: bytes :return: bytes containing the read In report. The first byte of the report will be the Report ID of the report that was read. """ return await hid_read(self.device_handle, length) async def enumerate_devices( vendor_id: int | None = None, product_id: int | None = None ) -> list[Device]: return [ Device(device_info) for device_info in await hid_enumerate( vendor_id=vendor_id, product_id=product_id ) ]