hidapi: dropped native, slight update to the python implementation

added an optional filter for driver name when enumerating devices
This commit is contained in:
Daniel Pavel 2013-04-28 14:05:33 +02:00
parent e7d19c9084
commit f5d2eba0c4
3 changed files with 53 additions and 425 deletions

View File

@ -2,6 +2,6 @@
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
__version__ = "0.5" __version__ = "0.6"
from hidapi.udev import * from hidapi.udev import *

View File

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

View File

@ -12,7 +12,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import os as _os import os as _os
import errno as _errno import errno as _errno
from select import select as _select 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' native_implementation = 'udev'
@ -57,7 +57,53 @@ def exit():
return True 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. """Enumerate the HID Devices.
List all the HID devices attached to the system, optionally filtering by 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'): for dev in _Context().list_devices(subsystem='hidraw'):
hid_dev = dev.find_parent('hid') hid_dev = dev.find_parent('hid')
if not hid_dev: if hid_dev:
continue dev_info = _match(dev, hid_dev, vendor_id, product_id, interface_number, driver)
if dev_info:
assert 'HID_ID' in hid_dev yield dev_info
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
def open(vendor_id, product_id, serial=None): def open(vendor_id, product_id, serial=None):