├── MANIFEST.in ├── setup.cfg ├── setup.py ├── LICENSE ├── README.md └── hid └── __init__.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | here = os.path.abspath(os.path.dirname(__file__)) 5 | README = open(os.path.join(here, 'README.md')).read() 6 | 7 | 8 | version = '1.0.8' 9 | 10 | setup( 11 | name='hid', 12 | version=version, 13 | description='ctypes bindings for hidapi', 14 | long_description=README, 15 | long_description_content_type='text/markdown', 16 | classifiers=[ 17 | 'Intended Audience :: Developers', 18 | 'License :: OSI Approved :: MIT License', 19 | 'Operating System :: OS Independent', 20 | 'Programming Language :: Python', 21 | 'Programming Language :: Python :: 3', 22 | ], 23 | keywords='', 24 | author='Austin Morton', 25 | author_email='amorton@juvsoft.com', 26 | url='https://github.com/apmorton/pyhidapi', 27 | license='MIT', 28 | packages=find_packages(), 29 | zip_safe=False, 30 | test_suite='nose.collector' 31 | ) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Austin Morton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Installing pyhidapi 2 | pyhidapi is available on [PyPI](https://pypi.org/project/hid/) and can be installed using pip. 3 | ``` 4 | pip install hid 5 | ``` 6 | 7 | pyhidapi is dependant upon the [hidapi library](https://github.com/libusb/hidapi), which must be installed separately. 8 | 9 | # Installing hidapi 10 | 11 | ## Linux 12 | Installation procedures vary depending on your distribution. 13 | 14 | ### Arch Linux 15 | Binary distributions are available in the community repository. 16 | 17 | 1. Enable the community repository in `/etc/pacman.conf` 18 | ``` 19 | [community] 20 | Include = /etc/pacman.d/mirrorlist 21 | ``` 22 | 2. Install hidapi 23 | ``` 24 | pacman -Sy hidapi 25 | ``` 26 | 27 | ### CentOS/RHEL 28 | Binary distributions are available through [EPEL](https://fedoraproject.org/wiki/EPEL). 29 | ``` 30 | yum install hidapi 31 | ``` 32 | 33 | ### Fedora 34 | Binary distributions are available. 35 | ``` 36 | dnf install hidapi 37 | ``` 38 | 39 | ### Ubuntu/Debian 40 | Binary distributions are available. 41 | 42 | ``` 43 | apt install libhidapi-hidraw0 44 | ``` 45 | or 46 | ``` 47 | apt install libhidapi-libusb0 48 | ``` 49 | 50 | ### Others 51 | Binary distributions may be available in your package repositories. If not, you can build from source as described [in the libusb/hidapi README](https://github.com/libusb/hidapi#build-instructions). 52 | 53 | ## Windows 54 | Installation procedure for Windows is described [in the libusb/hidapi README](https://github.com/libusb/hidapi#building-on-windows) 55 | 56 | Binary distributions are provided by [libusb/hidapi](https://github.com/libusb/hidapi/releases) 57 | 58 | ## OSX 59 | There are currently no official binary distributions for Mac, so you must build hidapi yourself. 60 | 61 | Installation instructions are described [in the libusb/hidapi README](https://github.com/libusb/hidapi#mac) 62 | 63 | You can also use brew: 64 | ``` 65 | brew install hidapi 66 | ``` 67 | 68 | ## FreeBSD 69 | Binary distributions are available. 70 | 71 | ``` 72 | pkg install -g 'py3*-hid' 73 | ``` 74 | 75 | # Sample usage code 76 | 77 | The details about a HID device can be printed with following code: 78 | 79 | ```python 80 | import hid 81 | 82 | vid = 0x046d # Change it for your device 83 | pid = 0xc534 # Change it for your device 84 | 85 | with hid.Device(vid, pid) as h: 86 | print(f'Device manufacturer: {h.manufacturer}') 87 | print(f'Product: {h.product}') 88 | print(f'Serial Number: {h.serial}') 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /hid/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ctypes 3 | import atexit 4 | import enum 5 | 6 | __all__ = ['HIDException', 'DeviceInfo', 'Device', 'enumerate', 'BusType'] 7 | 8 | 9 | hidapi = None 10 | library_paths = ( 11 | 'libhidapi-hidraw.so', 12 | 'libhidapi-hidraw.so.0', 13 | 'libhidapi-libusb.so', 14 | 'libhidapi-libusb.so.0', 15 | 'libhidapi-iohidmanager.so', 16 | 'libhidapi-iohidmanager.so.0', 17 | 'libhidapi.dylib', 18 | 'hidapi.dll', 19 | 'libhidapi-0.dll' 20 | ) 21 | 22 | for lib in library_paths: 23 | try: 24 | hidapi = ctypes.cdll.LoadLibrary(lib) 25 | break 26 | except OSError: 27 | pass 28 | else: 29 | error = "Unable to load any of the following libraries:{}"\ 30 | .format(' '.join(library_paths)) 31 | raise ImportError(error) 32 | 33 | 34 | hidapi.hid_init() 35 | atexit.register(hidapi.hid_exit) 36 | 37 | 38 | class HIDException(Exception): 39 | pass 40 | 41 | class APIVersion(ctypes.Structure): 42 | _fields_ = [ 43 | ('major', ctypes.c_int), 44 | ('minor', ctypes.c_int), 45 | ('patch', ctypes.c_int), 46 | ] 47 | 48 | try: 49 | hidapi.hid_version.argtypes = [] 50 | hidapi.hid_version.restype = ctypes.POINTER(APIVersion) 51 | 52 | version = hidapi.hid_version() 53 | version = ( 54 | version.contents.major, 55 | version.contents.minor, 56 | version.contents.patch, 57 | ) 58 | except AttributeError: 59 | # 60 | # hid_version API was added in 61 | # https://github.com/libusb/hidapi/commit/8f72236099290345928e646d2f2c48f0187ac4af 62 | # so if it is missing we are dealing with hidapi 0.8.0 or older 63 | # 64 | version = (0, 8, 0) 65 | 66 | if version >= (0, 13, 0): 67 | bus_type = [ 68 | ('bus_type', ctypes.c_int), 69 | ] 70 | else: 71 | bus_type = [] 72 | 73 | class BusType(enum.Enum): 74 | UNKNOWN = 0x00 75 | USB = 0x01 76 | BLUETOOTH = 0x02 77 | I2C = 0x03 78 | SPI = 0x04 79 | 80 | class DeviceInfo(ctypes.Structure): 81 | def as_dict(self): 82 | ret = {} 83 | for name, type in self._fields_: 84 | if name == 'next': 85 | continue 86 | ret[name] = getattr(self, name, None) 87 | 88 | if name == 'bus_type': 89 | ret[name] = BusType(ret[name]) 90 | 91 | return ret 92 | 93 | DeviceInfo._fields_ = [ 94 | ('path', ctypes.c_char_p), 95 | ('vendor_id', ctypes.c_ushort), 96 | ('product_id', ctypes.c_ushort), 97 | ('serial_number', ctypes.c_wchar_p), 98 | ('release_number', ctypes.c_ushort), 99 | ('manufacturer_string', ctypes.c_wchar_p), 100 | ('product_string', ctypes.c_wchar_p), 101 | ('usage_page', ctypes.c_ushort), 102 | ('usage', ctypes.c_ushort), 103 | ('interface_number', ctypes.c_int), 104 | ('next', ctypes.POINTER(DeviceInfo)), 105 | ] + bus_type 106 | 107 | hidapi.hid_init.argtypes = [] 108 | hidapi.hid_init.restype = ctypes.c_int 109 | hidapi.hid_exit.argtypes = [] 110 | hidapi.hid_exit.restype = ctypes.c_int 111 | hidapi.hid_enumerate.argtypes = [ctypes.c_ushort, ctypes.c_ushort] 112 | hidapi.hid_enumerate.restype = ctypes.POINTER(DeviceInfo) 113 | hidapi.hid_free_enumeration.argtypes = [ctypes.POINTER(DeviceInfo)] 114 | hidapi.hid_free_enumeration.restype = None 115 | hidapi.hid_open.argtypes = [ctypes.c_ushort, ctypes.c_ushort, ctypes.c_wchar_p] 116 | hidapi.hid_open.restype = ctypes.c_void_p 117 | hidapi.hid_open_path.argtypes = [ctypes.c_char_p] 118 | hidapi.hid_open_path.restype = ctypes.c_void_p 119 | hidapi.hid_write.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] 120 | hidapi.hid_write.restype = ctypes.c_int 121 | hidapi.hid_read_timeout.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_int] 122 | hidapi.hid_read_timeout.restype = ctypes.c_int 123 | hidapi.hid_read.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] 124 | hidapi.hid_read.restype = ctypes.c_int 125 | hidapi.hid_get_input_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] 126 | hidapi.hid_get_input_report.restype = ctypes.c_int 127 | hidapi.hid_set_nonblocking.argtypes = [ctypes.c_void_p, ctypes.c_int] 128 | hidapi.hid_set_nonblocking.restype = ctypes.c_int 129 | hidapi.hid_send_feature_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int] 130 | hidapi.hid_send_feature_report.restype = ctypes.c_int 131 | hidapi.hid_get_feature_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] 132 | hidapi.hid_get_feature_report.restype = ctypes.c_int 133 | if version >= (0, 14, 0): 134 | hidapi.hid_get_report_descriptor.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] 135 | hidapi.hid_get_report_descriptor.restype = ctypes.c_int 136 | hidapi.hid_close.argtypes = [ctypes.c_void_p] 137 | hidapi.hid_close.restype = None 138 | hidapi.hid_get_manufacturer_string.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_size_t] 139 | hidapi.hid_get_manufacturer_string.restype = ctypes.c_int 140 | hidapi.hid_get_product_string.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_size_t] 141 | hidapi.hid_get_product_string.restype = ctypes.c_int 142 | hidapi.hid_get_serial_number_string.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_size_t] 143 | hidapi.hid_get_serial_number_string.restype = ctypes.c_int 144 | hidapi.hid_get_indexed_string.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_size_t] 145 | hidapi.hid_get_indexed_string.restype = ctypes.c_int 146 | hidapi.hid_error.argtypes = [ctypes.c_void_p] 147 | hidapi.hid_error.restype = ctypes.c_wchar_p 148 | 149 | 150 | def enumerate(vid=0, pid=0): 151 | ret = [] 152 | info = hidapi.hid_enumerate(vid, pid) 153 | c = info 154 | 155 | while c: 156 | ret.append(c.contents.as_dict()) 157 | c = c.contents.next 158 | 159 | hidapi.hid_free_enumeration(info) 160 | 161 | return ret 162 | 163 | 164 | class Device(object): 165 | def __init__(self, vid=None, pid=None, serial=None, path=None): 166 | if path: 167 | self.__dev = hidapi.hid_open_path(path) 168 | elif serial: 169 | serial = ctypes.create_unicode_buffer(serial) 170 | self.__dev = hidapi.hid_open(vid, pid, serial) 171 | elif vid and pid is not None: 172 | self.__dev = hidapi.hid_open(vid, pid, None) 173 | else: 174 | raise ValueError('specify vid/pid or path') 175 | 176 | if not self.__dev: 177 | raise HIDException('unable to open device') 178 | 179 | def __enter__(self): 180 | return self 181 | 182 | def __exit__(self, exc_type, exc_value, exc_traceback): 183 | self.close() 184 | 185 | def __hidcall(self, function, *args, **kwargs): 186 | if not self.__dev: 187 | raise HIDException('device closed') 188 | 189 | ret = function(*args, **kwargs) 190 | 191 | if ret == -1: 192 | err = hidapi.hid_error(self.__dev) 193 | raise HIDException(err) 194 | return ret 195 | 196 | def __readstring(self, function, max_length=255): 197 | buf = ctypes.create_unicode_buffer(max_length) 198 | self.__hidcall(function, self.__dev, buf, max_length) 199 | return buf.value 200 | 201 | def write(self, data): 202 | return self.__hidcall(hidapi.hid_write, self.__dev, data, len(data)) 203 | 204 | def read(self, size, timeout=None): 205 | data = ctypes.create_string_buffer(size) 206 | 207 | if timeout is None: 208 | size = self.__hidcall(hidapi.hid_read, self.__dev, data, size) 209 | else: 210 | size = self.__hidcall( 211 | hidapi.hid_read_timeout, self.__dev, data, size, timeout) 212 | 213 | return data.raw[:size] 214 | 215 | def get_input_report(self, report_id, size): 216 | data = ctypes.create_string_buffer(size) 217 | 218 | # Pass the id of the report to be read. 219 | data[0] = bytearray((report_id,)) 220 | 221 | size = self.__hidcall( 222 | hidapi.hid_get_input_report, self.__dev, data, size) 223 | return data.raw[:size] 224 | 225 | def send_feature_report(self, data): 226 | return self.__hidcall(hidapi.hid_send_feature_report, 227 | self.__dev, data, len(data)) 228 | 229 | def get_feature_report(self, report_id, size): 230 | data = ctypes.create_string_buffer(size) 231 | 232 | # Pass the id of the report to be read. 233 | data[0] = bytearray((report_id,)) 234 | 235 | size = self.__hidcall( 236 | hidapi.hid_get_feature_report, self.__dev, data, size) 237 | return data.raw[:size] 238 | 239 | if version >= (0, 14, 0): 240 | def get_report_descriptor(self, size = 4096): 241 | data = ctypes.create_string_buffer(size) 242 | size = self.__hidcall( 243 | hidapi.hid_get_report_descriptor, self.__dev, data, size) 244 | return data.raw[:size] 245 | 246 | def close(self): 247 | if self.__dev: 248 | hidapi.hid_close(self.__dev) 249 | self.__dev = None 250 | 251 | @property 252 | def nonblocking(self): 253 | return getattr(self, '_nonblocking', 0) 254 | 255 | @nonblocking.setter 256 | def nonblocking(self, value): 257 | self.__hidcall(hidapi.hid_set_nonblocking, self.__dev, value) 258 | setattr(self, '_nonblocking', value) 259 | 260 | @property 261 | def manufacturer(self): 262 | return self.__readstring(hidapi.hid_get_manufacturer_string) 263 | 264 | @property 265 | def product(self): 266 | return self.__readstring(hidapi.hid_get_product_string) 267 | 268 | @property 269 | def serial(self): 270 | return self.__readstring(hidapi.hid_get_serial_number_string) 271 | 272 | def get_indexed_string(self, index, max_length=255): 273 | buf = ctypes.create_unicode_buffer(max_length) 274 | self.__hidcall(hidapi.hid_get_indexed_string, 275 | self.__dev, index, buf, max_length) 276 | return buf.value 277 | --------------------------------------------------------------------------------