385 lines
12 KiB
Python
385 lines
12 KiB
Python
"""Generic Human Interface Device API.
|
|
|
|
It is little more than a thin ctypes layer over a native hidapi implementation.
|
|
The docstrings are mostly copied from the hidapi API header, with changes where
|
|
necessary.
|
|
|
|
The native HID API implemenation is available at
|
|
https://github.com/signal11/hidapi.
|
|
|
|
The native implementation comes in two flavors, hidraw (``libhidapi-hidraw.so``)
|
|
and libusb (``libhidapi-libusb.so``). For this API to work, at least one of them
|
|
must be in ``LD_LIBRARY_PATH``; otherwise an ImportError will be raised.
|
|
|
|
Using the native hidraw implementation is recommended.
|
|
Currently the native libusb implementation (temporarily) detaches the device's
|
|
USB driver from the kernel, and it may cause the device to become unresponsive.
|
|
"""
|
|
|
|
#
|
|
# LEGACY, no longer supported
|
|
#
|
|
|
|
__version__ = '0.3-hidapi-0.7.0'
|
|
|
|
|
|
import ctypes as _C
|
|
from struct import pack as _pack
|
|
|
|
|
|
#
|
|
# look for a native implementation in the same directory as this file
|
|
#
|
|
|
|
# The CDLL native library object.
|
|
_native = None
|
|
|
|
for native_implementation in ('hidraw', 'libusb'):
|
|
try:
|
|
_native = _C.cdll.LoadLibrary('libhidapi-' + native_implementation + '.so')
|
|
break
|
|
except OSError:
|
|
pass
|
|
|
|
if _native is None:
|
|
raise ImportError('hidapi: failed to load any HID API native implementation')
|
|
|
|
|
|
#
|
|
# Structures used by this API.
|
|
#
|
|
|
|
|
|
# used by the native implementation when enumerating, no need to expose it
|
|
class _NativeDeviceInfo(_C.Structure):
|
|
pass
|
|
_NativeDeviceInfo._fields_ = [
|
|
('path', _C.c_char_p),
|
|
('vendor_id', _C.c_ushort),
|
|
('product_id', _C.c_ushort),
|
|
('serial', _C.c_wchar_p),
|
|
('release', _C.c_ushort),
|
|
('manufacturer', _C.c_wchar_p),
|
|
('product', _C.c_wchar_p),
|
|
('usage_page', _C.c_ushort),
|
|
('usage', _C.c_ushort),
|
|
('interface', _C.c_int),
|
|
('next_device', _C.POINTER(_NativeDeviceInfo))
|
|
]
|
|
|
|
|
|
# the tuple object we'll expose when enumerating devices
|
|
from collections import namedtuple
|
|
DeviceInfo = namedtuple('DeviceInfo', [
|
|
'path',
|
|
'vendor_id',
|
|
'product_id',
|
|
'serial',
|
|
'release',
|
|
'manufacturer',
|
|
'product',
|
|
'interface',
|
|
'driver',
|
|
])
|
|
del namedtuple
|
|
|
|
|
|
# create a DeviceInfo tuple from a hid_device object
|
|
def _makeDeviceInfo(native_device_info):
|
|
return DeviceInfo(
|
|
path=native_device_info.path.decode('ascii'),
|
|
vendor_id=hex(native_device_info.vendor_id)[2:].zfill(4),
|
|
product_id=hex(native_device_info.product_id)[2:].zfill(4),
|
|
serial=native_device_info.serial if native_device_info.serial else None,
|
|
release=hex(native_device_info.release)[2:],
|
|
manufacturer=native_device_info.manufacturer,
|
|
product=native_device_info.product,
|
|
interface=native_device_info.interface,
|
|
driver=None)
|
|
|
|
|
|
#
|
|
# set-up arguments and return types for each hidapi function
|
|
#
|
|
|
|
_native.hid_init.argtypes = None
|
|
_native.hid_init.restype = _C.c_int
|
|
|
|
_native.hid_exit.argtypes = None
|
|
_native.hid_exit.restype = _C.c_int
|
|
|
|
_native.hid_enumerate.argtypes = [_C.c_ushort, _C.c_ushort]
|
|
_native.hid_enumerate.restype = _C.POINTER(_NativeDeviceInfo)
|
|
|
|
_native.hid_free_enumeration.argtypes = [_C.POINTER(_NativeDeviceInfo)]
|
|
_native.hid_free_enumeration.restype = None
|
|
|
|
_native.hid_open.argtypes = [_C.c_ushort, _C.c_ushort, _C.c_wchar_p]
|
|
_native.hid_open.restype = _C.c_void_p
|
|
|
|
_native.hid_open_path.argtypes = [_C.c_char_p]
|
|
_native.hid_open_path.restype = _C.c_void_p
|
|
|
|
_native.hid_close.argtypes = [_C.c_void_p]
|
|
_native.hid_close.restype = None
|
|
|
|
_native.hid_write.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t]
|
|
_native.hid_write.restype = _C.c_int
|
|
|
|
_native.hid_read.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t]
|
|
_native.hid_read.restype = _C.c_int
|
|
|
|
_native.hid_read_timeout.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t, _C.c_int]
|
|
_native.hid_read_timeout.restype = _C.c_int
|
|
|
|
_native.hid_set_nonblocking.argtypes = [_C.c_void_p, _C.c_int]
|
|
_native.hid_set_nonblocking.restype = _C.c_int
|
|
|
|
_native.hid_send_feature_report.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t]
|
|
_native.hid_send_feature_report.restype = _C.c_int
|
|
|
|
_native.hid_get_feature_report.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t]
|
|
_native.hid_get_feature_report.restype = _C.c_int
|
|
|
|
_native.hid_get_manufacturer_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t]
|
|
_native.hid_get_manufacturer_string.restype = _C.c_int
|
|
|
|
_native.hid_get_product_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t]
|
|
_native.hid_get_product_string.restype = _C.c_int
|
|
|
|
_native.hid_get_serial_number_string.argtypes = [_C.c_void_p, _C.c_wchar_p, _C.c_size_t]
|
|
_native.hid_get_serial_number_string.restype = _C.c_int
|
|
|
|
_native.hid_get_indexed_string.argtypes = [_C.c_void_p, _C.c_int, _C.c_wchar_p, _C.c_size_t]
|
|
_native.hid_get_indexed_string.restype = _C.c_int
|
|
|
|
_native.hid_error.argtypes = [_C.c_void_p]
|
|
_native.hid_error.restype = _C.c_wchar_p
|
|
|
|
|
|
#
|
|
# exposed API
|
|
# docstrings mostly copied from hidapi.h
|
|
#
|
|
|
|
|
|
def init():
|
|
"""Initialize the HIDAPI library.
|
|
|
|
This function initializes the HIDAPI library. Calling it is not strictly
|
|
necessary, as it will be called automatically by enumerate() and any of the
|
|
open_*() functions if it is needed. This function should be called at the
|
|
beginning of execution however, if there is a chance of HIDAPI handles
|
|
being opened by different threads simultaneously.
|
|
|
|
:returns: ``True`` if successful.
|
|
"""
|
|
return _native.hid_init() == 0
|
|
|
|
|
|
def exit():
|
|
"""Finalize the HIDAPI library.
|
|
|
|
This function frees all of the static data associated with HIDAPI. It should
|
|
be called at the end of execution to avoid memory leaks.
|
|
|
|
:returns: ``True`` if successful.
|
|
"""
|
|
return _native.hid_exit() == 0
|
|
|
|
|
|
def enumerate(vendor_id=None, product_id=None, interface_number=None):
|
|
"""Enumerate the HID Devices.
|
|
|
|
List all the HID devices attached to the system, optionally filtering by
|
|
vendor_id, product_id, and/or interface_number.
|
|
|
|
:returns: an iterable of matching ``DeviceInfo`` tuples.
|
|
"""
|
|
|
|
devices = _native.hid_enumerate(vendor_id, product_id)
|
|
d = devices
|
|
while d:
|
|
if interface_number is None or interface_number == d.contents.interface:
|
|
yield _makeDeviceInfo(d.contents)
|
|
d = d.contents.next_device
|
|
|
|
if devices:
|
|
_native.hid_free_enumeration(devices)
|
|
|
|
|
|
def open(vendor_id, product_id, serial=None):
|
|
"""Open a HID device by its Vendor ID, Product ID and optional serial number.
|
|
|
|
If no serial is provided, the first device with the specified IDs is opened.
|
|
|
|
:returns: an opaque device handle, or ``None``.
|
|
"""
|
|
return _native.hid_open(vendor_id, product_id, serial) or None
|
|
|
|
|
|
def open_path(device_path):
|
|
"""Open a HID device by its path name.
|
|
|
|
:param device_path: the path of a ``DeviceInfo`` tuple returned by
|
|
enumerate().
|
|
|
|
:returns: an opaque device handle, or ``None``.
|
|
"""
|
|
if type(device_path) == str:
|
|
device_path = device_path.encode('ascii')
|
|
return _native.hid_open_path(device_path) or None
|
|
|
|
|
|
def close(device_handle):
|
|
"""Close a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
"""
|
|
_native.hid_close(device_handle)
|
|
|
|
|
|
def write(device_handle, data):
|
|
"""Write an Output report to a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
:param data: the data bytes to send including the report number as the
|
|
first byte.
|
|
|
|
The first byte of data[] must contain the Report ID. For
|
|
devices which only support a single report, this must be set
|
|
to 0x0. The remaining bytes contain the report data. Since
|
|
the Report ID is mandatory, calls to hid_write() will always
|
|
contain one more byte than the report contains. For example,
|
|
if a hid report is 16 bytes long, 17 bytes must be passed to
|
|
hid_write(), the Report ID (or 0x0, for devices with a
|
|
single report), followed by the report data (16 bytes). In
|
|
this example, the length passed in would be 17.
|
|
|
|
write() will send the data on the first OUT endpoint, if
|
|
one exists. If it does not, it will send the data through
|
|
the Control Endpoint (Endpoint 0).
|
|
|
|
:returns: ``True`` if the write was successful.
|
|
"""
|
|
bytes_written = _native.hid_write(device_handle, _C.c_char_p(data), len(data))
|
|
return bytes_written > -1
|
|
|
|
|
|
def read(device_handle, bytes_count, timeout_ms=-1):
|
|
"""Read an Input report from a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
:param bytes_count: maximum number of bytes to read.
|
|
:param timeout_ms: can be -1 (default) to wait for data indefinitely, 0 to
|
|
read whatever is in the device's input buffer, or a positive integer to
|
|
wait that many milliseconds.
|
|
|
|
Input reports are returned to the host through the INTERRUPT IN endpoint.
|
|
The first byte will contain the Report number if the device uses numbered
|
|
reports.
|
|
|
|
:returns: the data packet read, an empty bytes string if a timeout was
|
|
reached, or None if there was an error while reading.
|
|
"""
|
|
out_buffer = _C.create_string_buffer(b'\x00' * (bytes_count + 1))
|
|
bytes_read = _native.hid_read_timeout(device_handle, out_buffer, bytes_count, timeout_ms)
|
|
if bytes_read == -1:
|
|
return None
|
|
if bytes_read == 0:
|
|
return b''
|
|
return out_buffer[:bytes_read]
|
|
|
|
|
|
def send_feature_report(device_handle, data, report_number=None):
|
|
"""Send a Feature report to the device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
:param data: the data bytes to send including the report number as the
|
|
first byte.
|
|
:param report_number: if set, it is sent as the first byte with the data.
|
|
|
|
Feature reports are sent over the Control endpoint as a
|
|
Set_Report transfer. The first byte of data[] must
|
|
contain the Report ID. For devices which only support a
|
|
single report, this must be set to 0x0. The remaining bytes
|
|
contain the report data. Since the Report ID is mandatory,
|
|
calls to send_feature_report() will always contain one
|
|
more byte than the report contains. For example, if a hid
|
|
report is 16 bytes long, 17 bytes must be passed to
|
|
send_feature_report(): the Report ID (or 0x0, for
|
|
devices which do not use numbered reports), followed by the
|
|
report data (16 bytes).
|
|
|
|
:returns: ``True`` if the report was successfully written to the device.
|
|
"""
|
|
if report_number is not None:
|
|
data = _pack(b'!B', report_number) + data
|
|
bytes_written = _native.hid_send_feature_report(device_handle, _C.c_char_p(data), len(data))
|
|
return bytes_written > -1
|
|
|
|
|
|
def get_feature_report(device_handle, bytes_count, report_number=None):
|
|
"""Get a feature report from a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
:param bytes_count: how many bytes to read.
|
|
:param report_number: if set, it is sent as the report number.
|
|
|
|
:returns: the feature report data.
|
|
"""
|
|
out_buffer = _C.create_string_buffer('\x00' * (bytes_count + 2))
|
|
if report_number is not None:
|
|
out_buffer[0] = _pack(b'!B', report_number)
|
|
bytes_read = _native.hid_get_feature_report(device_handle, out_buffer, bytes_count)
|
|
if bytes_read > -1:
|
|
return out_buffer[:bytes_read]
|
|
|
|
|
|
def _read_wchar(func, device_handle, index=None):
|
|
_BUFFER_SIZE = 64
|
|
buf = _C.create_unicode_buffer('\x00' * _BUFFER_SIZE)
|
|
if index is None:
|
|
ok = func(device_handle, buf, _BUFFER_SIZE)
|
|
else:
|
|
ok = func(device_handle, index, buf, _BUFFER_SIZE)
|
|
if ok == 0:
|
|
return buf.value
|
|
|
|
|
|
def get_manufacturer(device_handle):
|
|
"""Get the Manufacturer String from a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
"""
|
|
return _read_wchar(_native.hid_get_manufacturer_string, device_handle)
|
|
|
|
|
|
def get_product(device_handle):
|
|
"""Get the Product String from a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
"""
|
|
return _read_wchar(_native.hid_get_product_string, device_handle)
|
|
|
|
|
|
def get_serial(device_handle):
|
|
"""Get the serial number from a HID device.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
"""
|
|
serial = _read_wchar(_native.hid_get_serial_number_string, device_handle)
|
|
if serial is not None:
|
|
return ''.join(hex(ord(c)) for c in serial)
|
|
|
|
|
|
def get_indexed_string(device_handle, index):
|
|
"""Get a string from a HID device, based on its string index.
|
|
|
|
Note: currently not working in the ``hidraw`` native implementation.
|
|
|
|
:param device_handle: a device handle returned by open() or open_path().
|
|
:param index: the index of the string to get.
|
|
"""
|
|
return _read_wchar(_native.hid_get_indexed_string, device_handle, index)
|