Skip to content
rene-aguirre edited this page Dec 3, 2012 · 10 revisions

Introduction

This project aims to be a simple USB/HID user application space (hence no drivers needed) 100% python package.

The vision for this project is to be something similar to PySerial or PyParallel for USB HID class hardware enthusiasts.

Advantages

  • All python code, using ctypes

  • Top level handling of HID events (usage events calling hook handlers)

Limitations

Depending on your application you might find these limitations

  • Windows only (so far...)

Installation

Use the provided setup.py as any other Python package, this requires the setuptools or distribute package (distribute is required for Python 3 installation as it will use 2to3.py to convert the module).

Using the package

Examples

There are some scripts included in the .\examples folder, take a look:

  • hook_button.py, shows how to deal with input report usages, the basic idea is to use "event handlers".

  • simple_send.py, toggles (clicks) an hypothetical usage signal, two output reports are sent.

  • raw_data.py, this script shows a list of available HID devices, you can pick up one in order to setup a simple custom input reports raw data handler (it just shows the data). Actually is a good simple utility to test your device.

  • pnp_sample.py, shows how to use the Plug and Play helpers. The PnP helpers just require a window handler (hWnd) of your main frame. So, it does not depend on a particular window library (wxPython, pyQt, etc), but the sample uses wxPython as an example.

  • show_hids.py, run this to explore the capabilities of currently connected HID devices.

In detail

In general this is you'll usually follow this steps:

  • Import the pywinusb.hid subpackage:
from pywinusb import hid
  • Define a HidDeviceFilter() object to target a particular USB device. The most restrictions you add when initializing the object, the narrower your device search might be, examples:
    # any hid device
    filter = hid.HidDeviceFilter()

    # just by vendor id
    filter = hid.HidDeviceFilter(vendor_id = 0x1234)

    # using vendor id and product id
    filter = hid.HidDeviceFilter(vendor_id = 0x1234, product_id = 0x0001)

    # by getting a range of vendor id's (example 0x12??)
    filter = hid.HidDeviceFilter(vendor_id = 0x1234, product_id = 0x0001, vendor_id_mask = 0xff00)

    # or particular product_id range (0x001??)
    all_devices = hid.HidDeviceFilter(vendor_id = 0x1234, product_id = 0x0010, product_id_mask = 0x0010)
  • Then at some point you'd poll for device availability, the reason for this is that you could receive plug and play events when any HID device gets connected or disconnected
    # poll now
    all_devices = filter.get_devices()
    
    if all_devices:
       print "Found %d matching hid devices" % len(all_devices)
  • You´ve got to be carefull on the filter result, if your search is wide enough to match multiple devices, might end up with more than expected items, there are other reasons you might actually get more than expected items returned by HidDeviceFilter().get_devices()

    1. Obviously, multiple devices might be connected to different USB ports.

    2. You could have HID devices with multiple collections, the usages spread over different usage pages, or the way the HID descriptors are arranged causes Windows to report your devices as multiple hid devices (istead of a single HID instance), as if it were a composite device.

For both cases you might find out that the HidDeviceFilter().get_devices_by_parent() very useful, it will return a dictionary where the keys make reference to the actual hardware instance ID of the parent device, so, this will effectevely group the found hid devices by the top level parent device. The dictionary values will be a list containing the devices with the common parent hardware ID. HidDeviceFilter().get_devices_by_parent() support the same parameters get_devices() does.

    # example, handle the HidDeviceFilter().get_devices() result grouping items by parent ID
    all_hids = HidDeviceFilter().get_devices_by_parent()
    for parent, hid_items in al_hids.items():
        print "Hid devices having hardware (instance) parent id = %d:" % parent
        for item in hid_items:
            print "\t%s" % repr(item)
  • Once you decide you have something usefull to do with your HID items, you have to call HidDevice.open() to start a data reading thread and be ready to send output reports, if you find out that afte opening your device you get flooded with un-expected data, or just want to send output or feature reports, use the output_only = True parameter.
    # let's supposed we already have a HidDevice instance, open it and print the vendor_name string
    hid_device.open():
    if hid_device.vendor_id:
        # effectively we could open the device (and setup the reading thread)
        print "The vendor name of device %s is %s" % (repr(hid_device), hid_device.vendor_name)
        # don't forget to clean up!
        hid_device.close()
  • Always use HidDevice.close(), this will kill the reading thread and restore the system resources, use a try: finally block to make sure the related system resources are cleaned up in case of any failure during initialization

  • Sending reports. Check the ./examples/simple_send.py script, after opening your HID device, you need to get a target output or feature report, take a look at the HidDevice().find_output_reports() and find_feature_reports() functions, you could search for all reports (no parameters), or provide a target usage_page and usage_id to look for.

    # let´s find out all the HID usages in all the output reports
    for report in my_hid_device.find_output_reports():
        print "The report %s, has this usage items:" % repr(report)
        for item in report:
           print "\t%s" % repr(item)

    # how about looking for a particular usage in all output reports?
    target_usage = hid.get_full_usage_id(0x08, 0x36) #page_id = LED page, usage = Play LED
    # all user space HID devices
    all_devices = HidDeviceFilter().get_devices()

    for device in all_devices:
        # browse each one...
        try:
            device.open() #consider using open(output_only = True)
            for report in device.find_output_reports():
                if target_usage in report:
                    # hey, we found any one!
                    report[target_usage] = 1 # change value in report
                    report.send() #flush it!
        finally:
            device.close()
  • Finally, you could setup event handler functions to be informed when a particular usage changed, take a look at the hid.HID_EVT_XXXXX constants. So far, you could setup events that would call your event handler function for 0 values (hid.HID_EVT_CLEAR), 1 values (hid.HID_EVT_SET), changing to 0 (hid.HID_EVT_RELEASED), changing to 1 (hid.HID_EVT_PRESSED), just changed (hid.HID_EVT_CHANGED) or when any value is received (hid.HID_EVT_ALL), take a look at the .\examples\hook_button.py script

Utilities

  • The module pywinusb.hid.core contains a function to check HID class devices capabilities, it issues a text report, you can run it executing the .\examples\show_hids.py example script.

Feedback and Contributing

Feel free to contact me (rene <dot> f <dot> aguirre <at> gmail <dot> com)!, just tell what do you think about the project or bring me anything you think might be cool.

I´ve being thinking about how to get a entry level HID device, so I could add more examples on such hardware, I was thinking about the Wiimote, but saddly it's rather limited for this, it's not using a proper HID descriptor, so input reports are raw reports. Anyway, if you find (or want to send me, why not), a device that might fit this purpose, just let me know.

Any participation it's appreciated, if you are willing to contribute but don't have any ideas or time, feel free to donate

Clone this wiki locally