hidapi: dropped native, slight update to the python implementation
added an optional filter for driver name when enumerating devices
This commit is contained in:
parent
e7d19c9084
commit
f5d2eba0c4
|
@ -2,6 +2,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__version__ = "0.5"
|
||||
__version__ = "0.6"
|
||||
|
||||
from hidapi.udev import *
|
||||
|
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue