Hi Everyone!
I've been looking for a way to communicate with the DigiSpark from Java. Java being preferred as it is much easier to deploy than the Python/Ruby based alternatives.
In short, I have been able to successfully send data between the DigiSpark (running the Echo example) and a Java application. However this did require some minor modifications to the DigiUSB library.
I struggled to find a suitable Java USB library, I found many long dead, or poorly documented options. I settled on javahidapi (
javahidapi).
javahidapi allows feature reports to be sent/received using sendFeatureReport(byte[]) and getFeatureReport(byte[]). It requires that the first byte in the given array corresponds to the ReportID. This causes problems with the unmodified version of DigiUSB, as it 'misuses' the ReportID value to transfer data. javahidapi would not permit sending anything other than '0' for this value, as the device doesn't provide any ReportIDs and defaults to '0'.
To resolve this I implemented the optional functions usbFunctionWrite and usbFunctionRead in DigiUSB.cpp. Implementing these functions requires USB_CFG_IMPLEMENT_FN_WRITE and USB_CFG_IMPLEMENT_FN_READ to be changed to '1' in usbconfig.h. The changed parts are below:
uchar bytesRemaining;
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (usbRequest_t*)((void *)data);
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){ /* HID class request */
if(rq->bRequest == USBRQ_HID_GET_REPORT){ /* wValue: ReportType (highbyte), ReportID (lowbyte) */
/* since we have only one report type, we can ignore the report-ID */
return USB_NO_MSG;//we want to use usbFunctionRead()
}else if(rq->bRequest == USBRQ_HID_SET_REPORT){
/* since we have only one report type, we can ignore the report-ID */
// TODO: Check race issues?
bytesRemaining = rq->wLength.bytes[0];//limited to 254 anyway
return USB_NO_MSG;//we want to use usbFunctionWrite()
}
}else{
/* ignore vendor type requests, we don't use any */
}
return 0;
}
uchar usbFunctionWrite(uchar *data, uchar len) {
for (uchar i=0; i<len; i++) {
store_char(data[i], &rx_buffer);
}
bytesRemaining -= len;
return bytesRemaining==0;//1 if complete
}
uchar usbFunctionRead(uchar *data, uchar len) {
uchar numBytes = tx_available();
if (len < numBytes) {
numBytes = len;
}
for (uchar i=0; i<numBytes; i++) {
data[i] = tx_read();
}
return numBytes;
}
From the java side, my simple test class consists of the following:
import java.io.IOException;
import java.io.InputStreamReader;
import com.codeminders.hidapi.ClassPathLibraryLoader;
import com.codeminders.hidapi.HIDDevice;
import com.codeminders.hidapi.HIDDeviceInfo;
import com.codeminders.hidapi.HIDManager;
public class SimpleUsbTest {
private static final int DIGISPARK_PRODUCT_ID = 0x05DF;
private static final int DIGISPARK_VENDOR_ID = 0x16C0;
public static void main(String[] args) throws IOException, InterruptedException {
ClassPathLibraryLoader.loadNativeHIDLibrary();
for (HIDDeviceInfo info : HIDManager.getInstance().listDevices()) {
System.out.println("Product : " + info.getProduct_string());//XXX
System.out.println("Manufacturer: " + info.getManufacturer_string());//XXX
System.out.println("Vendor ID : " + Integer.toHexString(info.getVendor_id()));//XXX
System.out.println("Product ID : " + Integer.toHexString(info.getProduct_id()));//XXX
}
final HIDDevice digispark = HIDManager.getInstance().openById(DIGISPARK_VENDOR_ID, DIGISPARK_PRODUCT_ID, null);
new Thread(new Runnable() {
public void run() {
byte[] data = new byte[2];
data[0] = 0;//First byte is ReportID
while (true) {
try {
int read = digispark.getFeatureReport(data);
if (read>0) {
System.out.print((char)data[1]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
InputStreamReader in = new InputStreamReader(System.in);
byte[] data = new byte[2];
data[0] = 0;//First byte is ReportID
while (true) {
byte c = (byte)in.read();
data[1] = c;
digispark.sendFeatureReport(data);
}
}
}
The first part simply enumerates the connected devices using listDevices(). For the DigiSpark, it produces the expected:
Product : DigiUSB
Manufacturer: digistump.com
Vendor ID : 16c0
Product ID : 5df
The following part opens a device, using the known Vendor/Product IDs. A thread is created which continually reads from the DigiSpark, printing any data it receives.
The final part reads from stdin and sends that data to the DigiSpark.
The samples above currently only send a single byte at a time. To increase this all that is needed on the DigiSpark side is setting the REPORT_COUNT value in the USB report descriptor. The java side just needs the buffers increased correspondingly.
I hope that helps anybody in my situation, the lack of available examples is very frustrating. I'm happy to try and answer any questions. I am working on more fully featured code which makes working with the DigiSpark from within java easier, but I will go into that in another post.
- Andrew