diff --git a/logitech/hidapi.py b/lib/hidapi.py similarity index 56% rename from logitech/hidapi.py rename to lib/hidapi.py index bd218727..c14a1eea 100644 --- a/logitech/hidapi.py +++ b/lib/hidapi.py @@ -15,54 +15,55 @@ USB driver from the kernel, and it may cause the device to become unresponsive. __version__ = '0.2-hidapi-0.7.0' -import os.path -from collections import namedtuple - -from ctypes import (cdll, create_string_buffer, create_unicode_buffer, - c_int, c_ushort, c_size_t, - c_char_p, c_wchar_p, c_void_p, - POINTER, Structure) +import ctypes as _C # # look for a native implementation in the same directory as this file # -_api = None -native_path = os.path.dirname(__file__) +"""The CDLL native library object.""" +_native = None + for native_implementation in ('hidraw', 'libusb'): try: - native_name = 'libhidapi-' + native_implementation + '.so' - native_libfile = os.path.join(native_path, native_name) - _api = cdll.LoadLibrary(native_libfile) + # first look in the default library path + native_implementation = 'libhidapi-' + native_implementation + '.so' + _native = _C.cdll.LoadLibrary(native_implementation) break except OSError: pass -del native_path, native_name, native_libfile, native_implementation -if _api is None: - raise ImportError(__file__, - 'failed to load any HID API native implementation') +del native_implementation + +if _native is None: + raise ImportError('hidapi: failed to load any HID API native implementation') -# internally used by native hidapi, no need to expose it -class _DeviceInfo(Structure): +# +# Structures used by this API. +# + + +# used by the native implementation when enumerating, no need to expose it +class _NativeDeviceInfo(_C.Structure): pass -_DeviceInfo._fields_ = [ - ('path', c_char_p), - ('vendor_id', c_ushort), - ('product_id', c_ushort), - ('serial', c_wchar_p), - ('release', c_ushort), - ('manufacturer', c_wchar_p), - ('product', c_wchar_p), - ('usage_page', c_ushort), - ('usage', c_ushort), - ('interface', c_int), - ('next_device', POINTER(_DeviceInfo)) +_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', @@ -72,78 +73,79 @@ DeviceInfo = namedtuple('DeviceInfo', [ 'manufacturer', 'product', 'interface']) +del namedtuple # create a DeviceInfo tuple from a hid_device object -def _DevInfoTuple(hid_device): +def _makeDeviceInfo(native_device_info): return DeviceInfo( - path=str(hid_device.path), - vendor_id=hex(hid_device.vendor_id)[2:], - product_id=hex(hid_device.product_id)[2:], - serial=str(hid_device.serial) if hid_device.serial else None, - release=hex(hid_device.release)[2:], - manufacturer=str(hid_device.manufacturer), - product=str(hid_device.product), - interface=hid_device.interface) + path=str(native_device_info.path), + vendor_id=hex(native_device_info.vendor_id)[2:], + product_id=hex(native_device_info.product_id)[2:], + serial=str(native_device_info.serial) if native_device_info.serial else None, + release=hex(native_device_info.release)[2:], + manufacturer=str(native_device_info.manufacturer), + product=str(native_device_info.product), + interface=native_device_info.interface) # # set-up arguments and return types for each hidapi function # -_api.hid_init.argtypes = None -_api.hid_init.restype = c_int +_native.hid_init.argtypes = None +_native.hid_init.restype = _C.c_int -_api.hid_exit.argtypes = None -_api.hid_exit.restype = c_int +_native.hid_exit.argtypes = None +_native.hid_exit.restype = _C.c_int -_api.hid_enumerate.argtypes = [c_ushort, c_ushort] -_api.hid_enumerate.restype = POINTER(_DeviceInfo) +_native.hid_enumerate.argtypes = [_C.c_ushort, _C.c_ushort] +_native.hid_enumerate.restype = _C.POINTER(_NativeDeviceInfo) -_api.hid_free_enumeration.argtypes = [POINTER(_DeviceInfo)] -_api.hid_free_enumeration.restype = None +_native.hid_free_enumeration.argtypes = [_C.POINTER(_NativeDeviceInfo)] +_native.hid_free_enumeration.restype = None -_api.hid_open.argtypes = [c_ushort, c_ushort, c_wchar_p] -_api.hid_open.restype = c_void_p +_native.hid_open.argtypes = [_C.c_ushort, _C.c_ushort, _C.c_wchar_p] +_native.hid_open.restype = _C.c_void_p -_api.hid_open_path.argtypes = [c_char_p] -_api.hid_open_path.restype = c_void_p +_native.hid_open_path.argtypes = [_C.c_char_p] +_native.hid_open_path.restype = _C.c_void_p -_api.hid_close.argtypes = [c_void_p] -_api.hid_close.restype = None +_native.hid_close.argtypes = [_C.c_void_p] +_native.hid_close.restype = None -_api.hid_write.argtypes = [c_void_p, c_char_p, c_size_t] -_api.hid_write.restype = c_int +_native.hid_write.argtypes = [_C.c_void_p, _C.c_char_p, _C.c_size_t] +_native.hid_write.restype = _C.c_int -_api.hid_read.argtypes = [c_void_p, c_char_p, c_size_t] -_api.hid_read.restype = 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 -_api.hid_read_timeout.argtypes = [c_void_p, c_char_p, c_size_t, c_int] -_api.hid_read_timeout.restype = 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 -_api.hid_set_nonblocking.argtypes = [c_void_p, c_int] -_api.hid_set_nonblocking.restype = c_int +_native.hid_set_nonblocking.argtypes = [_C.c_void_p, _C.c_int] +_native.hid_set_nonblocking.restype = _C.c_int -_api.hid_send_feature_report.argtypes = [c_void_p, c_char_p, c_size_t] -_api.hid_send_feature_report.restype = 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 -_api.hid_get_feature_report.argtypes = [c_void_p, c_char_p, c_size_t] -_api.hid_get_feature_report.restype = 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 -_api.hid_get_manufacturer_string.argtypes = [c_void_p, c_wchar_p, c_size_t] -_api.hid_get_manufacturer_string.restype = 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 -_api.hid_get_product_string.argtypes = [c_void_p, c_wchar_p, c_size_t] -_api.hid_get_product_string.restype = 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 -_api.hid_get_serial_number_string.argtypes = [c_void_p, c_wchar_p, c_size_t] -_api.hid_get_serial_number_string.restype = 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 -_api.hid_get_indexed_string.argtypes = [c_void_p, c_int, c_wchar_p, c_size_t] -_api.hid_get_indexed_string.restype = 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 -_api.hid_error.argtypes = [c_void_p] -_api.hid_error.restype = c_wchar_p +_native.hid_error.argtypes = [_C.c_void_p] +_native.hid_error.restype = _C.c_wchar_p # @@ -163,7 +165,7 @@ def init(): :returns: ``True`` if successful. """ - return _api.hid_init() == 0 + return _native.hid_init() == 0 def exit(): @@ -174,7 +176,7 @@ def exit(): :returns: ``True`` if successful. """ - return _api.hid_exit() == 0 + return _native.hid_exit() == 0 def enumerate(vendor_id=None, product_id=None, interface_number=None): @@ -187,29 +189,27 @@ def enumerate(vendor_id=None, product_id=None, interface_number=None): """ results = [] - devices = _api.hid_enumerate(vendor_id, product_id) + devices = _native.hid_enumerate(vendor_id, product_id) d = devices while d: if interface_number is None or interface_number == d.contents.interface: - results.append(_DevInfoTuple(d.contents)) + results.append(_makeDeviceInfo(d.contents)) d = d.contents.next_device if devices: - _api.hid_free_enumeration(devices) + _native.hid_free_enumeration(devices) return results def open(vendor_id, product_id, serial=None): - """Open a HID device using a Vendor ID, Product ID and optionally a serial - number. + """Open a HID device by its Vendor ID, Product ID and optional serial number. - If no serial_number is provided, the first device with the specified ids - is opened. + If no serial is provided, the first device with the specified IDs is opened. :returns: an opaque device handle, or ``None``. """ - return _api.hid_open(vendor_id, product_id, serial) or None + return _native.hid_open(vendor_id, product_id, serial) or None def open_path(device_path): @@ -220,7 +220,7 @@ def open_path(device_path): :returns: an opaque device handle, or ``None``. """ - return _api.hid_open_path(device_path) or None + return _native.hid_open_path(device_path) or None def close(device_handle): @@ -228,7 +228,7 @@ def close(device_handle): :param device_handle: a device handle returned by open() or open_path(). """ - _api.hid_close(device_handle) + _native.hid_close(device_handle) def write(device_handle, data): @@ -254,7 +254,7 @@ def write(device_handle, data): :returns: ``True`` if the write was successful. """ - bytes_written = _api.hid_write(device_handle, c_char_p(data), len(data)) + bytes_written = _native.hid_write(device_handle, _C.c_char_p(data), len(data)) return bytes_written > -1 @@ -273,8 +273,8 @@ def read(device_handle, bytes_count, timeout_ms=-1): :returns: the bytes read, or ``None`` if a timeout was reached. """ - out_buffer = create_string_buffer('\x00' * (bytes_count + 1)) - bytes_read = _api.hid_read_timeout(device_handle, out_buffer, bytes_count, timeout_ms) + 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 out_buffer[:bytes_read] @@ -303,7 +303,7 @@ def send_feature_report(device_handle, data, report_number=None): """ if report_number is not None: data = chr(report_number) + data - bytes_written = _api.hid_send_feature_report(device_handle, c_char_p(data), len(data)) + bytes_written = _native.hid_send_feature_report(device_handle, _C.c_char_p(data), len(data)) return bytes_written > -1 @@ -316,17 +316,17 @@ def get_feature_report(device_handle, bytes_count, report_number=None): :returns: the feature report data. """ - out_buffer = create_string_buffer('\x00' * (bytes_count + 2)) + out_buffer = _C.create_string_buffer('\x00' * (bytes_count + 2)) if report_number is not None: out_buffer[0] = chr(report_number) - bytes_read = _api.hid_get_feature_report(device_handle, out_buffer, bytes_count) + 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 = create_unicode_buffer('\x00' * _BUFFER_SIZE) + buf = _C.create_unicode_buffer('\x00' * _BUFFER_SIZE) if index is None: ok = func(device_handle, buf, _BUFFER_SIZE) else: @@ -340,7 +340,7 @@ def get_manufacturer(device_handle): :param device_handle: a device handle returned by open() or open_path(). """ - return _read_wchar(_api.hid_get_manufacturer_string, device_handle) + return _read_wchar(_native.hid_get_manufacturer_string, device_handle) def get_product(device_handle): @@ -348,7 +348,7 @@ def get_product(device_handle): :param device_handle: a device handle returned by open() or open_path(). """ - return _read_wchar(_api.hid_get_product_string, device_handle) + return _read_wchar(_native.hid_get_product_string, device_handle) def get_serial(device_handle): @@ -356,13 +356,28 @@ def get_serial(device_handle): :param device_handle: a device handle returned by open() or open_path(). """ - serial = _read_wchar(_api.hid_get_serial_number_string, device_handle) + 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): -# """ -# :param device_handle: a device handle returned by open() or open_path(). -# """ -# return _read_wchar(_api.hid_get_indexed_string, device_handle, index) +def get_indexed_string(device_handle, index): + """Get a string from a HID device, based on its string index. + + Note: may not be implemented by the underlying native library. + + :param device_handle: a device handle returned by open() or open_path(). + :param string_index: the index of the string to get. + """ + return _read_wchar(_native.hid_get_indexed_string, device_handle, index) + + +def last_error(device_handle): + """Get a string describing the last error which occurred. + + Note: currently not implemented by the underlying native library. + + :param device_handle: a device handle returned by open() or open_path(). + :returns: a string containing the last error which occurred, or None. + """ + return _native.hid_error(device_handle)