Recently I had to communicate with a USB device from Java. In this case it was my custom build 8x8x8 led cube (similar to this one) which uses the USB HID (human interface device) protocol. Since it has 512 leds it can be represented by 8×8 bytes, where each bit represents a led in the cube. The y and z dimensions are flattened into an array (
index = z *x + y). The x-dimension is represented within the byte: each bit represents an x-coordinate. This fits exactly with the HID protocol where you can send 64 bytes in a single block.
A straight forward representation in Java would be a
byte. To create this byte array you will have to do some bit manipulation. For example, to set the led on position (0,0,3) you would have to make sure the 3rd bit of the first byte is a 1. Basic bit manipulating can be done by using a bitwise-and or bitwise-or with a specific mask. If you want to turn a bit on, you’d create a mask with all 0’s and a single 1 on the position you need to turn on. If we
or the existing data with this, it would make sure that led is turned on in our representation. To create the mask we can use bit shifting, setting the x’th bit to one can be done with:
1 << x would create a mask with only the position enabled we want to manipulate. So basically we could do something like:
cube[z*8 + y] |= 1 << x;
And this will work in most cases. However, in Java a byte is signed whereas normally you would like to use an unsigned byte for this. And thus some operations might be very confusing. E.g., trying to assign ‘255’ to enable all leds at once will lead to a compiler error or overflow since the maximum is 127.
One common way of working around it is using an
int and only use the lower bits of it by applying a mask on it if it potentially overflowed, e.g.
int i = 0xFF & i;. But for doing i/o with bytes is inconvenient again, most API’s still expect
bytes. Most newer API’s accept
ints as well but that can be quite confusing. E.g., have a look at some of
write(int v): uses an int, writes a byte
write(byte b, int off, int len): uses bytes, writes bytes
writeByte(int v): uses an int, writes a byte
writeInt(int v): uses an int, writes a int
There is a more user friendly way: Java has a special class called
BitSet. Basically it allows you to create an arbritrary long set of bits. There are convenience functions for manipulating the bits and converting it to a byte array. It also saves you from writing the mostly unreadable bit manipulation code and simply use functions like
cube.set(z*8*8 + y*8 + x).
So using the
BitSet I could easily create a
byte representation of the cube. Now I only needed to send it over USB to the cube itself.
Luckily, the people from CodeMinders have created a JNI wrapper around a C library for USB HID support. While the documentation is a bit limited, it was easy enough to use:
BitSet cube = new BitSet();
HIDDevice dev = HIDManager.getInstance().openById(VENDOR_ID, PRODUCT_ID, null);
However, there are a few pitfalls which I learned the hard way. The first one was that if you, by accident, write too many bytes, it will crash your system. My Mac simply rebooted. The safe environment of Java is no longer there when using JNI.
Also, note that the HID protocol as provided by this library requires you to send first byte with a “report id” and then the 64 bytes of the HID protocol. This is not apparent in any way from the API but it is stated in the documentation.
Lastly, note that the
BitSet implementation in Java will only output the minimum of bytes needed. If you haven’t touched any bits at the far end of the array, those will not exist so you might need to fill up your array first.
I did the workarounds for these pitfalls the quick and dirty way, hiding these pitfalls in the communication part towards the USB:
byte block = Arrays.copyOf(cube.toByteArray(), 64);
byte blockWithReportId = new byte;
System.arraycopy(block, 0, blockWithReportId, 1, 64);
As a final note, the HID jar file ships with intel native libraries. If you want to run this on a Raspberry Pi, you might want to have a look at this page.