Skip to Main Content

Embedded Technologies

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Brewing Java with the Raspberry Pi

Yolande Poirier-OracleSep 4 2015 — edited Oct 13 2015

Brewing Java with the Raspberry Pi

by Stephen Chin

Interact with a USB scale to accurately measure your coffee beans and brew the perfect cup.

Published May/June 2015

The Raspberry Pi comes preloaded with Java SE Embedded 8, so getting started with Java is as simple as typing the java command at the prompt. However, the ability to run Java on your Raspberry Pi wouldn’t be complete without an application to help perfect your coffee-brewing skills. This article takes a scientific approach to coffee brewing in order to obtain the perfect “cup of Joe.”

Communicating with a USB Scale

In order to precisely measure weight for coffee brewing, we are going to use a USB shipping scale. USB communication is based on a series of pipes (logical channels) that are connected to the device via endpoints. Furthermore, these endpoints are grouped into a set of interfaces. The protocol used by most shipping scales is simple one-way communication where the current scale value is repeatedly broadcast out over a single endpoint on the first interface.

To use the scale, simply plug it into one of the host ports on your Raspberry Pi and turn it on. It consumes only 16 mA, so it can be safely powered right off the Raspberry Pi USB bus.

The vendorId and productId show up when you plug in the scale and run the dmesg command. For the Dymo M10 scale they are 0x0922 and 0x8003, respectively. For its sister scale, the Dymo M25, they are 0x0922 and 0x8004, respectively. However, any compatible Stamps.com or DymoStamp scale should work fine as long as you change the vendorId and productId in the code.

For communication with the USB scale, we are going to use the usb4java library. This is an open source, JSR 80–compliant implementation of the standard JavaX-USB specification that has support for ARM Linux distributions such as the Raspberry Pi. You can download the latest version of usb4java from the project website.

Make sure to download both the core library as well as the javax extension. From the core distribution you need to grab the following JAR files:

  • usb4java-1.2.0.jar
  • libusb4java-1.2.0-linux-arm.jar
  • commons-lang3-3.2.1.jar

And you need these additional JAR files from the usb4java-javax distribution:

  • usb-api-1.0.2.jar
  • usb4java-javax-1.2.0.jar

Place all these files in a new folder called lib in your project directory, and add them to your project dependencies in your IDE of choice.

You also need to create a properties file in the root package that tells the JavaX-USB wrapper to use usb4java as the implementation. To accomplish this, create a file called javax.usb.properties in the root package (under src), and give it the contents shown in Listing 1.

|

javax.usb.services = org.usb4java.javax.Services

|

Listing 1

To test the data returned from the scale, we are going to use a single-class implementation of the USB scale protocol that returns 60 data points and prints out the results on the command line. This will be the basis for our coffee-brewing application.

|

public static void main(String[] args) throws UsbException {
UsbScaleTest scale = UsbScaleTest.findScale();
scale.open();
try {
for (int i = 0; i < 60; i++) {
scale.syncSubmit();
}
} finally {
scale.close();
}
}

|

Listing 2

The main method for our application is shown in Listing 2 and can be summarized as follows:

  • Find a connected USB scale.
  • Open a connection to the scale.
  • For 60 data points, submit a request to retrieve data.
  • Close the scale.

To ensure the scale is closed properly even in the case of an error or exception, we are using a try finally block.

Listing 3 shows the implementation of the first method, findScale, which discovers Dymo M10 or M25 scales using the JavaX-USB API. This calls the findDevice method, which contains code that traverses the USB device tree (see Listing 4).

|

public static UsbScaleTest findScale() throws UsbException {
UsbServices services = UsbHostManager.getUsbServices();
UsbHub rootHub = services.getRootUsbHub();
// Dymo M10 Scale:
UsbDevice device = findDevice(rootHub, (short) 0x0922,
(short) 0x8003);
// Dymo M25 Scale:
if (device == null) {
device = findDevice(rootHub, (short) 0x0922,
(short) 0x8004);
}
if (device == null) {
return null;
}
return new UsbScaleTest(device);
}

|

Listing 3

|

private static UsbDevice findDevice(UsbHub hub, short
vendorId, short productId) {
for (UsbDevice device :
(List) hub.getAttachedUsbDevices()) {
UsbDeviceDescriptor desc =
device.getUsbDeviceDescriptor();
if (desc.idVendor() == vendorId &&
desc.idProduct() == productId) {
return device;
}
if (device.isUsbHub()) {
device = findDevice((UsbHub) device,
vendorId, productId);
if (device != null) {
return device;
}
}
}
return null;
}

|

Listing 4

To read data from the scale, we need to connect to the correct interface and endpoint. Fortunately, the USB scale protocol is fairly simple, so you can simply grab the first interface and endpoint and then start listening to the data coming in from the scale directly, as shown in Listing 5.

|

private void open() throws UsbException {
UsbConfiguration configuration =
device.getActiveUsbConfiguration();
iface = configuration.getUsbInterface((byte) 0);
// this allows us to steal the lock from the kernel
iface.claim(usbInterface -> true);
final List endpoints = iface.getUsbEndpoints();
// there is only 1 endpoint
pipe = endpoints.get(0).getUsbPipe();
pipe.addUsbPipeListener(this);
pipe.open();
}

|

Listing 5

Notice that we had to use the claim method that accepts a UsbInterfacePolicy. This allows us to force the kernel to detach from the USB interface so we can claim it for our application.

The implementation of syncSubmit is trivial, calling the same-named method on the UsbPipe:

|

private void syncSubmit()
throws UsbException {
pipe.syncSubmit(data);
}

|

However, the real work happens in the callback. To receive the callback, our class needs to implement the UsbPipeListener class, which has two required methods. The first is dataEventOccurred, which will be called as a result of invoking syncSubmit and contain the data returned from the scale. The second is errorEventOccurred, which will be invoked when there is a problem interfacing with the scale.

The data sent by these shipping scales is a simple byte array that contains six values. The protocol is not well documented, but it has been reverse-engineered by the open source community. The data is as follows:

  • Byte 0—Unused
  • Byte 1—Special flags: empty=2, overweight=6, negative=5 (The conditions indicated by overweight and negative are described later.)
  • Byte 2—Unit of measure: grams=2, ounces=11
  • Byte 3—Weight scale
  • Byte 4—Base weight low- order byte
  • Byte 5—Base weight high- order byte

Listing 6 shows the implementation of dataEventOccurred, which takes apart the byte array returned and prints out a human-readable scale value to the command line.

|

@Override
public void dataEventOccurred(UsbPipeDataEvent upde) {
boolean empty = data[1] == 2;
boolean overweight = data[1] == 6;
boolean negative = data[1] == 5;
boolean grams = data[2] == 2;
int scalingFactor = data[3];
int weight = (data[4] & 0xFF) + (data[5] << 8);
if (empty) {
System.out.println("EMPTY");
} else if (overweight) {
System.out.println("OVERWEIGHT");
} else if (negative) {
System.out.println("NEGATIVE");
} else {
// Use String.format b/c printf causes problems on
// remote exec
System.out.println(String.format("Weight = %,.1f%s",
scaleWeight(weight, scalingFactor),
grams ? "g" : "oz"));
}
}

|

Listing 6

Besides doing a little bit of bit-shifting to get the weight magnitude, we also need to scale the weight by the scalingFactor returned in byte 3, as shown in Listing 7.

|

private double scaleWeight(int weight, int scalingFactor) {
return weight * Math.pow(10, scalingFactor);
}

|

Listing 7

In the case of an error, the best we can do is log the error and continue (see Listing 8).

|

@Override
public void errorEventOccurred(UsbPipeErrorEvent upee) {
Logger.getLogger(UsbScaleTest.class.getName()).log(
Level.SEVERE, "Scale Error", upee);
}

|

Listing 8

And the last method to finish the scale test is the close algorithm. This simply closes the pipe and interface cleanly so that the next application that tries to use the scale will be able to access it:

|

public void close() throws
UsbException {
pipe.close();
iface.release();
}

|

Read the full article at oracle.com/javamagazine The subscription to Java Magazine is free

About the Author

chin-headshot.jpg

Stephen Chin is the lead Java community manager at Oracle, author of the upcoming Raspberry Pi with Java (McGraw-Hill), and coauthor of Pro JavaFX 8 (Apress, 2014). He is also a JavaOne content co-chair. Chin has presented keynotes at numerous Java conferences including JavaOne, where he is a four-time Rock Star Award recipient.

Comments

715399
Answer
Hi,

It looks like incremental inference is indeed being used. Note that there is some overhead involved with doing incremental and non-incremental inference in general, so even with small models it might take a few seconds to finish. Some of the overhead is caused by the large number of rules in the OWLPRIME rulebase, so if you don't need all of them you can selectively disable some components using the GraphOracleSem.performInference(String components) procedure.

Also, it depends on your dataset. For instance, if you're adding only one triple, but that triple declares some heavily used property to be transitive, then that addition might trigger many additional inferences and updating the inferred graph will take more time.

Regarding OntModel APIs and incremental inference, depends on the loading method you use. Incremental inference works best with incremental loading. Please refer to Section 2.2.9 of the Semantic Technologies Developer's Guide for more details.

Cheers,
Vladimir
Marked as Answer by 696067 · Sep 27 2020
696067
That is very strange. I am basically inserting 'd' into a 5-level-deep tree of Transitive links. The tree has around 5300 nodes but 5000 of them are leaves.
It takes 5 seconds for the inference to be done whether I insert one leaf like 'd' or 100 of them.
By doing a performInference with only SCOH, SPOH, and TRANS it goes down to 3 seconds (further removing TRANS goes down to 2 seconds but I absolutely need to use TRANS).

How come it takes the same 3-5 seconds to infer 5 transitive links and 500 of them?
715399
Hi,

Can you let us know:
a) how large is the asserted dataset and how many triples are generated with inference (from scratch) ?
b) what is the performance target for the incremental inference calls?

Regarding b), if your inference performance target is on the order of milliseconds, you might want to try out PelletDB [1].

Cheers,
Vlad

[1] http://clarkparsia.com/pelletdb/
696067
Thanks Vlad, it looks like in-memory forward-chaining is more of what I am looking for in this regard.
As to your questions:
1) I basically have a 5 level deep tree where every node is transitive to the root, with 5000 leaves and nodes of 200,100,30,1 respectively which to my understanding is 15530 transitive links.
2) My performance target is indeed in the milliseconds but that's when it comes to adding 1-50 leaves.

Thank you for your answers.
Alexi
1 - 4

Post Details

Added on Sep 4 2015
5 comments
4,602 views