PyHID Documentation

07 December 2006
python c++ free library pyhid

Overview

This is a library for accessing USB Human Interface Devices (HIDs) on Mac OS X, from within C++ or Python. Its philosophy is somewhat inspired by the SWT widget libraries for Java, in that it provides only very simple, low-level native APIs in C, getting as quickly as possible into a high-level language to provide clean, friendly APIs there.

At the lowest level, a C++ wrapper provides a simpler way to work with HIDs than the HIDManager Carbon API. This is contained in HID.cpp/HID.h. It's useful in its own right if you're writing C++ code, but not really the focus of the exercise.

Next, a Python C Extension Module, PyHID.lowlevel, exposes a plain procedural interface to HID.cpp/HID.h, while PyHID.consts exposes the #defines from IOHIDUsageTables.h to Python.

The high level interfaces are in PyHID.devices, PyHID.usage and, optionally, PyHID.gaming. These provide happy object-oriented interfaces to the HID drivers.

As a bonus, the specific subpackage contains modules that provide interfaces to specific bits of hardware that I've used. These generally don't do anything you can't do with the rest of the PyHID API, they're just helpful. Right now, the only device in there is the k8055 with which you can read inputs and control outputs on the Velleman K8055 USB experiment board.

For convenience, the PyHID package imports some key functions from the submodules.

We'll start at the top, since this is what most people using PyHID will be interested in, and then work down into the messy depths for those who need to access the drivers at a lower level.

PyHID

Call PyHID.initialise() before doing anything else with the library.

Call PyHID.finalise() when you're completely done with the library.

PyHID.scan_devices() will rescan the system for connected USB HIDs and return a list containing a PyHID.devices.Device instance for each device found.

PyHID.devices.Device

The Device class has the following member variables of interest:

There are also three functions, which we will come back to later:

PyHID.devices.Element

HID Elements are the meat of the module. These allow you to read or write the value of the elements, obviously, but also let you check the range of values they can return, and their intended usage, which can be invaluable for figuring out sensible default control mappings.

HID Element class members of interest:

Methods:

Event queues

When event monitoring is enabled on an element, then every time the element's value changes, the new value is stored in a queue, along with a timestamp (a float, measured in seconds, relative to when PyHID.initialise() was called). This can get very "spammy" for analogue joystick axes and the like, as "jitter" in the device's hardware causes the values to continuously change by small amounts. However, for digital buttons, event queues can be invaluable as they allow you to register button-presses successfully even when your application is running slowly and would otherwise have missed them.

Events are stored in a single queue per device, from which you can pull them using device.poll(). The result is None if no events are pending, or a tuple (element, raw_value, timestamp).

Note that the queue is of limited size and old events will be thrown away when the queue fills up. You can set the size of the queue when you call device.enable_monitoring(). The default is 8 events.

Transformations

When you read an element by calling its read() method, the value you get back is always the raw value as returned by the HID Manager directly. However, the values returned by different brands or products vary wildly. One might return values from 0 to 255, another might range from -32767 to 32768. This gets old pretty quickly. It's more useful to translate all values into a particular range; typically -1.0 to 1.0 for steering/movement axes, or 0.0 to 1.0 for magnitude axes such as audio volume or acceleration.

PyHID can take care of this for you. Calling an element as a function returns a normalised, floating-point value, from 0.0 to 1.0 by default. This range can be changed with set_transform(), eg steering_wheel.set_transform(-1.0, 1.0)

You can also supply a callable which is automatically applied to the values before they are returned. This might be used for creating a "dead zone", or making axes non-linear, and so on. When writing such a transformation, it's helpful to work in certain ranges — for example, most functions are easier to write when the range is centred around zero. Take this crude dead-zone example (I don't recommend using this, by the way — use the smooth_deadzone provided in the gaming module):

python

cheap-ass deadzone function:

def deadzone(v): if abs(v)<0.1: return 0 return v

However, both the device's raw values, and the range requested by set_transform() are completely arbitrary. What to do? Again, PyHID helps, by automatically transforming device values into a suitable range for the transformation function, and then transforming the output from the function into the requested range.

If the callable passed to set_transform() has a member range, which is a tuple of two floats (min, max), this is assumed to be the domain of the function, and the raw values are transformed into this range before passing to the callable. Its output is assumed to be in the same range, and this is then transformed to the range specified in set_transform() before being returned to the application. Thus you simply need to add the following after the previous example:

python

example (ctd)

deadzone.range = -1.0, 1.0

If you don't supply a range, the function is assumed to operate in the range 0.0 to 1.0.

PyHID.gaming

This is a convenience module for game developers (and anyone else interested in using similar controllers for non-gaming purposes). PyHID.gaming provides subclasses of Device: GameDevice and GamePad. Pass a normal Device instance to their constructors.

GameDevice is identical to Device except that it filters down the list of elements to just those which it deems relevant to traditional game input devices (you can always subclass it yourself if you need more esoteric support). A HID device/elements list is often very large as it may include LEDs, force-feedback controllers, telephone handsets and ringers — the USB HID spec even defines element usages for fire alarms, climate control, voltage regulation, battery charging, and all sorts of other unlikely stuff, so starting off with a filtered list can be useful.

GamePad subclasses GameDevice, and looks at each element that GameDevice accepted as relevant, and puts a reference to it into one of three lists: buttons, axes and pads. These store digital buttons, analogue axes and direction pads/hat switches respectively.

There are also some helpful global functions in the module:

C++ Interface

The core of the C++ interface is the HIDManager class. Constructing and destructing this corresponds to the initialise()/finalise() calls in the Python interface. Although HIDManager is not designed as a Singleton (I don't really believe in them), you should not create more than one instance.

HIDManager members:

GetDevice() returns a pointer to an IHIDevice. This interface has these members:

IHIDElements, returned from GetElement(), have the following interface:

Error Handling

If an error occurs in the C++ portion of the library, a HIDException is raised. This includes a GetReason() method that returns a human-readable STL std::string explaining what went wrong. The Python translation layer, PyHID.cpp, catches these and raises a Python SystemError exception with the same message.

Known Issues

Licensing

PyHID is licensed under similar terms to Python. Details are in the license.txt file.

© Wooji Juice 2006