greendeck/greendeck/lib/hidapi/device.py
2022-12-13 14:25:50 -06:00

171 lines
5.5 KiB
Python

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
)
]