Temperature Sensor Example
As an example that puts all this together, Im going to demonstrate a program that talks to the simplest off-the-shelf USB device I could find, a Vernier Go!Temp thermometer. This device, shown in Figure 23-2, is a laboratory sensor used in primary and secondary schools. More complex devices use the same basic USB principles but have more complicated protocols to control them. This device has the advantage of being simple and relatively cheap. All you have to do is plug it in and then read the data it sends back over the interrupt pipe.
Figure 23-2. The Vernier Go!Temp next to a non-USB thermometer
When writing software to communicate with a specific device, you have to determine the vendor and product ID in order to find the device. Fortunately, Example 23-4, USBDeviceDescriber, lists exactly this information. After plugging in the probe and running Example 23-4, we find that the vendor ID for Vernier is 0x08F7 and the product ID is 0x2. Lets store these in named constants:
public final static int VERNIER_VENDOR_ID = 0x08F7; public final static int GOTEMP_PRODUCT_ID = 2;
These are the same for all devices of this type. Each probe also has a unique serial number that varies from one instrument to the next, but you don need it.
The first thing the program needs to do is search the bus for the device with this ID. How this is done is very similar to some of the earlier examples: recursively traverse the USB tree looking for a device with the right vendor and product ID, and when its found, return it, or return null if no such device is attached. Example 23-7 lists the program code.
Example 23-7. Locating a device
private static UsbDevice findProbe( ) throws UsbException { UsbServices services = UsbHostManager.getUsbServices( ); UsbHub root = services.getRootUsbHub( ); return searchDevices(root); } private static UsbDevice searchDevices(UsbHub hub) throws UsbException, IOException { List devices = hub.getAttachedUsbDevices( ); Iterator iterator = devices.iterator( ); while (iterator.hasNext( )) { UsbDevice device = (UsbDevice) iterator.next( ); UsbDeviceDescriptor descriptor = device.getUsbDeviceDescriptor( ); int manufacturerCode = descriptor.idVendor( ); int productCode = descriptor.idProduct( ); if (manufacturerCode == VERNIER_VENDOR_ID && productCode == GOTEMP_PRODUCT_ID) { return device; } else if (device.isUsbHub( )) { UsbDevice found = searchDevices((UsbHub) device); if (found != null) return found; } } return null; // didn find it } |
This code assumes theres only one such probe on the bus. It would be simple enough to extend it to handle multiple probes, but for now I kept it simple by choosing the first one found and ignoring any subsequent devices.
The Go!Temp has a single configuration, a single interface, and a single input pipe from the device to the host that sends the temperature data. The next step is to find the single interface and claim it. From the device, we get the active configuration. From the configuration, we get the single interface:
UsbConfiguration config = probe.getActiveUsbConfiguration( ); UsbInterface theInterface = (UsbInterface) config.getUsbInterfaces( ).get(0);
The interface number on this device should always be 0, so you could ask for it by number instead:
UsbInterface theInterface = config.getUsbInterface((byte) 0);
Now that we have the interface, we need to claim it. As usual, simple claiming does not work because the operating system has already grabbed hold of the device. We have to force the claim, like so:
theInterface.claim(new UsbInterfacePolicy( ) { public boolean forceClaim(UsbInterface usbInterface) { return true; } });
Once the interface is claimed, we can ask it for its endpoint. For this device, there should be only one:
UsbEndpoint endpoint = (UsbEndpoint) theInterface.getUsbEndpoints( ).get(0);
In general, at this point you would check whether the endpoint was an in or out endpoint. However, as the Go!Temp has only a single in endpoint, we can skip that check.
The endpoint has a single pipe:
UsbPipe pipe = endpoint.getUsbPipe( );
Normally here youd check whether you have a control, bulk, isochronous, or interrupt pipe. Again, the Go!Temp probe is so simple that you know what youve got without asking: an interrupt pipe.
Now its time to read from the pipe. First, create an IRP:
UsbIrp irp = pipe.createUsbIrp( );
You can use any implementation of the UsbIrp interface here, but its best to let the pipe object do it so it can optimize the IRP for the pipe.
The IRP should probably come with an appropriately sized data array. However, it doesn , so make one and stuff it. For this device I happen to know that eight bytes is the right size:
byte[] input = new byte[8]; irp.setData(input);
If you don know the size in advance, you can ask the UsbEndpointDescriptor for its wMaxPacketSize. If you send an incorrectly sized IRP, youll get a UsbBabbleException when you submit it.
Next, we open the pipe and submit the IRP to the pipe. For this program we might as well submit synchronously, since we don have anything else to do until the device responds:
pipe.open( ); pipe.syncSubmit(irp);
When this method returns, the array is filled with binary data. There are no particular rules for how this data is interpreted. Thats up to the device manufacturer. In this case, the Go!Temp stores the number of measurements its taken as an unsigned integer in the first byte, a rolling sequence counter in the second byte, and 2-byte little-endian shorts in the last six spaces in this array. These shorts are the temperature measurements in 1/128ths of a degree Celsius. That looks suspiciously like a number chosen for convenience in binary arithmetic. I doubt the instrument is accurate beyond a tenth of a degree or so. In any case, dividing these shorts by 128 gives the temperature in degrees Celsius. This code finds the first temperature measurement returned:
int result = UsbUtil.toShort(data[3], data[2]); double degreesCelsius = result / 128.0;
If the device has had time to make two measurements, the second temperature is in data[5] and data[4]. If its had time to make three, the third is in data[7] and data[6]. You have to check the first byte to see how many measurements it took. Subsequent measurements require additional IRPs to be submitted.
|
To finish up, we wrap this in a loop that continuously reads from the pipe and prints the results to System.out. The loop can reuse the same IRP as long as it calls setComplete(false) on each pass and is careful not to read vestigial data from previous runs if the IRP is not completely refilled each time. I also added a little code to check whether the probe was being operated outside its advertised temperature range (-10°C to 110°C). Example 23-8 demonstrates.
Example 23-8. Reading temperatures from a Go!Temp probe
import java.util.*; import javax.usb.*; import javax.usb.util.*; import java.io.*; public class Thermometer { public final static int VERNIER_VENDOR_ID = 0x8F7; public final static int GOTEMP_PRODUCT_ID = 2; public static void main(String[] args) throws UsbException, IOException { UsbDevice probe = findProbe( ); if (probe == null) { System.err.println("No Go!Temp probe attached."); return; } UsbConfiguration config = probe.getActiveUsbConfiguration( ); UsbInterface theInterface = config.getUsbInterface((byte) 0); theInterface.claim(new UsbInterfacePolicy( ) { public boolean forceClaim(UsbInterface usbInterface) { return true; } }); UsbEndpoint endpoint = (UsbEndpoint) theInterface.getUsbEndpoints( ).get(0); UsbPipe pipe = endpoint.getUsbPipe( ); // set up the IRP UsbIrp irp = pipe.createUsbIrp( ); byte[] data = new byte[8]; irp.setData(data); pipe.open( ); outer: while (true) { pipe.syncSubmit(irp); int numberOfMeasurements = data[0]; for (int i = 0; i < numberOfMeasurements; i++) { int result = UsbUtil.toShort(data[2*i+3], data[2*i+2]); int sequenceNumber = UsbUtil.unsignedInt(data[1]); double temperature = result / 128.0; if (temperature > 110.0) { System.err.println("Maximum accurate temperature exceeded."); break outer; } else if (temperature < -10) { System.err.println("Minimum accurate temperature exceeded."); break outer; } System.out.println("Measurement " + sequenceNumber + ": " + temperature + "°C"); } // get ready to reuse IRP irp.setComplete(false); } pipe.abortAllSubmissions( ); pipe.close( ); theInterface.release( ); } private static UsbDevice findProbe( ) throws UsbException, IOException { UsbServices services = UsbHostManager.getUsbServices( ); UsbHub root = services.getRootUsbHub( ); return searchDevices(root); } private static UsbDevice searchDevices(UsbHub hub) throws UsbException, IOException { List devices = hub.getAttachedUsbDevices( ); Iterator iterator = devices.iterator( ); while (iterator.hasNext( )) { UsbDevice device = (UsbDevice) iterator.next( ); UsbDeviceDescriptor descriptor = device.getUsbDeviceDescriptor( ); int manufacturerCode = descriptor.idVendor( ); int productCode = descriptor.idProduct( ); if (manufacturerCode == VERNIER_VENDOR_ID && productCode == GOTEMP_PRODUCT_ID) { return device; } else if (device.isUsbHub( )) { UsbDevice found = searchDevices((UsbHub) device); if (found != null) return found; } } return null; // didn find it } }
|
Heres some output from when I ran it. The probe started collecting data just sitting on my desk, but then I dunked it in ice water. You can see it started with three measurements in one packet at room temperature. However, because the program was running faster than the probe could retrieve the temperature, each subsequent packet contained only a single measurement. Initially, the temperature was slowly rising, but as soon as I dunked it in the ice water it began dropping rapidly, stabilizing at around 6°C. If I let the ice melt and left the program running for an hour or so, it would heat back up to room temperature.
# java -classpath jsr80_ri-1.0.0.jar:jsr80-1.0.0.jar:.:jsr80_linux-1.0.0.jar Probe Measurement 247: 18.9375 °C Measurement 248: 18.9375 °C Measurement 249: 18.9375 °C Measurement 250: 18.9375 °C Measurement 251: 18.9375 °C Measurement 252: 18.9375 °C Measurement 253: 18.9375 °C Measurement 254: 18.9375 °C Measurement 255: 19.0 °C Measurement 0: 19.0625 °C Measurement 1: 16.0625 °C Measurement 2: 13.6875 °C Measurement 3: 12.1875 °C Measurement 4: 11.1875 °C Measurement 5: 10.5 °C Measurement 6: 10.0 °C Measurement 7: 9.625 °C Measurement 8: 9.25 °C Measurement 9: 8.9375 °C Measurement 10: 8.625 °C Measurement 11: 8.375 °C Measurement 12: 8.1875 °C Measurement 13: 8.0 °C Measurement 14: 7.8125 °C Measurement 15: 7.6875 °C Measurement 16: 7.5 °C Measurement 17: 7.375 °C Measurement 18: 7.25 °C Measurement 19: 7.125 °C Measurement 20: 7.0625 °C Measurement 21: 6.9375 °C Measurement 22: 6.875 °C Measurement 23: 6.75 °C Measurement 24: 6.6875 °C Measurement 25: 6.625 °C Measurement 26: 6.5625 °C Measurement 27: 6.5 °C Measurement 28: 6.5 °C Measurement 29: 6.4375 °C Measurement 30: 6.375 °C Measurement 31: 6.375 °C Measurement 32: 6.3125 °C Measurement 33: 6.3125 °C Measurement 34: 6.25 °C Measurement 35: 6.25 °C Measurement 36: 6.25 °C Measurement 37: 6.1875 °C Measurement 38: 6.1875 °C
Example 23-8 reads continuously. It should provide the user with a way to quit the program. When it does so, the program should release the device.
Категории |