OCZ NIA hacking, now with Python!

May 25, 2009

Disclaimer the first: I don't know a whole lot about USB or device drivers. Those of you who do will no doubt point and laugh.

Disclaimer the second: Where applicable, I've given credit for and linked to the work of others. I've independently discovered a few things that others have already figured out, so one or two things may not be attributed. In that case, please let me know and I'll put a reference where applicable.

Over the past few weeks I've been playing with my OCZ NIA on and off. My first attempt at getting anything out of it involved recompiling Windbringer's kernel with HID debugging and /dev/hidraw device support to maximize my chances of having a device node to play with. As far as I can tell these are probably unnecessary because when you plug in the NIA device nodes under /dev appear and some of them will emit binary when you read from them. While you can `cat` from the device node you won't get much from it. After running niasnoop (which defaults to maximum debugging output) it automatically found my NIA at device node /dev/bus/usb/005/002 and started pulling data from it, just as it's supposed to do.

Using niasnoop I captured a couple of megabytes of data in a text file and stared at it for a while in an attempt to gain more insight into the format of the data stream, only to no avail. I then got the bright idea to check out the OCZ forums again, and there discovered that someone called dr-mephisto had written a Python module that hooks into libusb and makes it possible to write apps in Python that read data from the NIA, thus beating me to the punch by a few weeks (realistically speaking, probably a few months). PyNIA has a few dependencies that have to be met before you can use it - the aforementioned libusb, Pyglet, pyusb, and numpy to provide the signal analysis algorithms. These dependencies are pretty easy to satisfy; you can probably pull what you need from your distribution's package repository (unless you're running Python on Windows or Mac OSX, in which case you're on your own). PyNIA seems pretty straightforward in how it works - it walks the USB buses and enumerates all connected devices to locate a NIA (which has to be plugged in before you try to do anything with it). The module implements an NIA_Interface() object which knows what it's looking for and can configure and calibrate itself. It also exposes the usual methods, like open(), close(), and read(), but these are actually for the benefit for the other half of the module. The class NIA_Data() is the one that you actually interact with when you write your own code. It takes the output from the NIA, runs a Fourier analysis against it to extract the six channels of signal data, and spawns threads which read one second of data each to make available for use while continuing to read in the background for near-realtime operation. PyNIA also generates waveform data suitable for use with OpenGL, incidentally.

The key to the object is the method NIA_Data.fingers(), which returns a list of six signed floating point values that represent the six channels of signal data picked up from the person wearing the headband. Everything can be done in the context of a simple loop:

array = my_nia.fingers()
for channel in range(len(array)):

(Note: The HTML tag doesn't seem to display indentation the way it's supposed to. It's Python, so hit the space bar a few times and you should be okay.)

If you poke around on the forums there is some proof of concept code written in Python floating around which implements a simple EEG/EMG using wxPython and a bunch of slider controls for visualization. With only a little work you can do pretty much whatever you want with the values returned from the NIA_Data.fingers() method.

To see if I could do it (and to have an excuse to play around with Python) I wrote a simple utility that prints the six channels of data to standard out as long as the NIA's plugged in and transmitting data. I've found that once in a while the utility will throw an exception and error out, and I think it's due to a synchronization glitch with the NIA: if PyNIA tries to read a packet of data from the USB subsystem but gets only a partial packet because it reads in the middle of a burst rather than at the beginning, it can't make sense of the data and falls over. Just try running the utility again and everything should be fine. I figured out how to do it by reading through dr-mephisto's hellonia.py application from the forums six or seven times; I've made it as simple as I possibly could and have it still have it do something useful. Between that and dr-mephisto's hellonia.py, you should be able to figure out how to write code for the NIA.

nia_number_dumper.zip - download it!