From f5d2eba0c4778ee05ffdb89989acd7c54a38a3ec Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Sun, 28 Apr 2013 14:05:33 +0200 Subject: [PATCH] hidapi: dropped native, slight update to the python implementation added an optional filter for driver name when enumerating devices --- lib/hidapi/__init__.py | 2 +- lib/hidapi/native.py | 384 ----------------------------------------- lib/hidapi/udev.py | 92 +++++----- 3 files changed, 53 insertions(+), 425 deletions(-) delete mode 100644 lib/hidapi/native.py diff --git a/lib/hidapi/__init__.py b/lib/hidapi/__init__.py index 72a2628c..b8bcdbc3 100644 --- a/lib/hidapi/__init__.py +++ b/lib/hidapi/__init__.py @@ -2,6 +2,6 @@ from __future__ import absolute_import, division, print_function, unicode_literals -__version__ = "0.5" +__version__ = "0.6" from hidapi.udev import * diff --git a/lib/hidapi/native.py b/lib/hidapi/native.py deleted file mode 100644 index c2e04be8..00000000 --- a/lib/hidapi/native.py +++ /dev/null @@ -1,384 +0,0 @@ -# """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) diff --git a/lib/hidapi/udev.py b/lib/hidapi/udev.py index acdf0045..0c5c730b 100644 --- a/lib/hidapi/udev.py +++ b/lib/hidapi/udev.py @@ -12,7 +12,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera import os as _os import errno as _errno from select import select as _select -from pyudev import Context as _Context, Device as _Device +from pyudev import Context as _Context, Monitor as _Monitor, Device as _Device native_implementation = 'udev' @@ -57,7 +57,53 @@ def exit(): return True -def enumerate(vendor_id=None, product_id=None, interface_number=None): +def _match(device, hid_dev, vendor_id=None, product_id=None, interface_number=None, driver=None): + assert 'HID_ID' in hid_dev + bus, vid, pid = hid_dev['HID_ID'].split(':') + driver_name = hid_dev['DRIVER'] + + if ((vendor_id is None or vendor_id == int(vid, 16)) and + (product_id is None or product_id == int(pid, 16)) and + (driver is None or driver == driver_name)): + + if bus == '0003': # USB + intf_dev = device.find_parent('usb', 'usb_interface') + if intf_dev: + interface = intf_dev.attributes.asint('bInterfaceNumber') + if interface_number is None or interface_number == interface: + usb_dev = device.find_parent('usb', 'usb_device') + assert usb_dev + attrs = usb_dev.attributes + d_info = DeviceInfo(path=device.device_node, + vendor_id=vid[-4:], + product_id=pid[-4:], + serial=hid_dev.get('HID_UNIQ'), + release=attrs['bcdDevice'], + manufacturer=attrs['manufacturer'], + product=attrs['product'], + interface=interface, + driver=driver_name) + return d_info + + elif bus == '0005': # BLUETOOTH + # TODO + pass + + +def monitor(callback, *device_filters): + m = _Monitor.from_netlink(_Context()) + m.filter_by(subsystem='hidraw') + for action, device in m: + hid_dev = device.find_parent('hid') + if hid_dev: + for filter in device_filters: + dev_info = _match(device, hid_dev, *filter) + if dev_info: + callback(action, dev_info) + break + + +def enumerate(vendor_id=None, product_id=None, interface_number=None, driver=None): """Enumerate the HID Devices. List all the HID devices attached to the system, optionally filtering by @@ -67,44 +113,10 @@ def enumerate(vendor_id=None, product_id=None, interface_number=None): """ for dev in _Context().list_devices(subsystem='hidraw'): hid_dev = dev.find_parent('hid') - if not hid_dev: - continue - - assert 'HID_ID' in hid_dev - bus, vid, pid = hid_dev['HID_ID'].split(':') - if vendor_id is not None and vendor_id != int(vid, 16): - continue - if product_id is not None and product_id != int(pid, 16): - continue - - if bus == '0003': # USB - intf_dev = dev.find_parent('usb', 'usb_interface') - if not intf_dev: - continue - - interface = intf_dev.attributes.asint('bInterfaceNumber') - if interface_number is not None and interface_number != interface: - continue - - serial = hid_dev['HID_UNIQ'] if 'HID_UNIQ' in hid_dev else None - - usb_dev = dev.find_parent('usb', 'usb_device') - assert usb_dev - attrs = usb_dev.attributes - d_info = DeviceInfo(path=dev.device_node, - vendor_id=vid[-4:], - product_id=pid[-4:], - serial=serial, - release=attrs['bcdDevice'], - manufacturer=attrs['manufacturer'], - product=attrs['product'], - interface=interface, - driver=hid_dev['DRIVER']) - yield d_info - - elif bus == '0005': # BLUETOOTH - # TODO - pass + if hid_dev: + dev_info = _match(dev, hid_dev, vendor_id, product_id, interface_number, driver) + if dev_info: + yield dev_info def open(vendor_id, product_id, serial=None):