Working with the Mobile Sensor API

by Vikram Goyal

This article explains how the Mobile Sensor API (JSR 256 for Java Platform, Micro Edition) enables Java MIDlets to talk to the physical and virtual sensors available on a device.

Published September 2010


Downloads:
Download Java ME

The Mobile Sensor API (JSR 256 for Java Platform, Micro Edition [Java ME]) provides the ability for Java MIDlets to talk to the physical and virtual sensors available on the device. This is a particularly useful API, because it can provide the MIDlet the means to gauge the health not only of the device but also of the outside physical environment, which makes some very interesting applications possible. Of course, as with all APIs, the implementation of this API by the device manufacturer determines the actual sensors that are available.

In this article, I provide an introduction to this API and some simple code to determine the available sensors on a device. I then create an example MIDlet to figure out the current tilt of a device using the accelerometer. The tilt orientation (axisX, axisY, and axisZ) can help Java devices figure out which way a user is holding the device in order to manipulate the user interface accordingly.

Introduction to the Mobile Sensor API

The Mobile Sensor API grew out of JSR 256. The API provides ways to fetch and monitor data from physical and virtual sensors uniformly. The simplest example of a sensor is a battery monitor, which tells the MIDlet the level of battery charge. Using the API, you can make informed decisions in your MIDlet about what the battery sensor is telling you, if you first create a connection to the sensor and then register to listen to events originating from this sensor.

A sensor might or might not allow the user to control the sensor via programmatic interface. For example, if a physical sensor connected to a device allows the user to measure the current temperature, it might also provide controls to physically start, stop, or calibrate the thermometer. To this end, the Mobile Sensor API is divided into two parts:

  • The sensor part, covered by the package javax.microedition.sensor
  • The control part, covered by the package javax.microedition.sensor.control

The sensor part is solely responsible for getting information out of the sensor (physical or virtual). The control part provides the interfaces for the controls.

Defining Sensors

Sensors, within the context of the Mobile Sensor API, are defined using two parameters:

  • Quantity
  • Context

Think of quantity as the actual item that is being measured, for example, the battery charge. The context (or to use the correct term, context type) is the context within which that quantity is being measured or monitored, for example, the device. So, if a sensor were to provide you the battery charge of a device, the quantity that you are monitoring or measuring is the battery charge, and the context is the device.

If, for argument sake, you were interested in the battery charge of an electric vehicle (through a sensor connected to your device), you would be using the sensor defined with the quantity of battery charge and the context of vehicle. Continuing on with our silly analogy, if a user has a measurable battery charge sensor, the quantity would be battery charge, and the context would be user. There is one last context type, called ambient, which is used to define something being measured within the surrounding environment. (I could say sensing the battery charge of the surrounding environment, but that would be too silly.)

So there are four context types that are used to define a sensor:

  • Ambient
  • User
  • Device
  • Vehicle

If the context types define the "WHERE" of sensors, the quantity defines the "WHAT." The sensor will measure (what?) the battery charge (where?) on the device.

As you might expect, there can be hundreds of quantities, many of which are standardized by the API and defined in the javax.microedition.sensor.SensorInfo interface. Companies can introduce their own quantities, as long as they can keep them separate from existing ones on the device.

The API specifies that the information about a sensor be encapsulated in the SensorInfo interface. This interface, provides information about a sensor, and must provide the quantity being measured, the context type, the type of connection to the sensor (embedded, wired, short-range wireless, or remote), the sensor model, the URL to the sensor, the sensor's description, and, finally, meta information about the sensor in terms of channels (unit, scale, ranges, and so on). A lot of information is required of a SensorInfo object. All of it is mandatory, per the API, and must not be null or empty. (Unfortunately, as with most API definitions, this requirement is contradicted within the API itself at several places; it seems that the only properties that are really required are the quantity and context type.) Besides these, the API also defines some optional parameters.

The final piece of the puzzle for how a sensor is defined is solved by the SensorConnection interface. The implementation of this interface provides the means to connect to the actual sensor and collect data from it, which can be done either synchronously or asynchronously. As expected, asynchronous notifications require you to implement a DataListener interface in your receiver class. Two getData methods within this interface provide the mechanism to get the actual data from the underlying sensor.

Finding Sensors

To identify and work with sensors, the API provides a manager class called SensorManager, which has two methods to find sensors within a device:

  • findSensors(String quantity, String contextType) returns an array of SensorInfo objects that match the given quantity and context type.
  • findSensors(String url) requires you to know the URL of an existing sensor. URLs are defined similarly to HTTP URLs, with sensor replacing HTTP. Therefore, sensor:battery_charge;context_type=device is a valid URL. This method returns an array of possible SensorInfo objects.

Finding All Sensors in a Device

To find all sensors available in a device, all you need to do is to call the findSensors(String quantity, String contextType) method with null values for both parameters. This should return all available sensors, in theory. The following code shows a MIDlet that prints out the available sensors on a device.

public class MobileSensorFinder extends MIDlet implements CommandListener {

  // the main display
  Display display;

  // the canvas on which we will show the sensor information
  Canvas canvas;

  // the command
  Command exitCommand;

  public MobileSensorFinder() {

    // get the current display
    display = Display.getDisplay(this);
  }

  public void startApp() {

    // first, find out the sensor version supported by the device
    // if no version information can be found, then sensors are probably not
    // supported
    String version = System.getProperty("microedition.sensor.version");
    if(version != null) {

      // create the sensor - which initiates looking for sensors
      canvas = new SensorCanvas();

      // create the exit command and attach to Canvas
      exitCommand = new Command("Exit", Command.EXIT, 1);

      canvas.addCommand(exitCommand);
      canvas.setCommandListener(this);

      display.setCurrent(canvas);

    } else {
      Alert alert =
        new Alert("Error",
                  "No version info found. Sensors are probably not supported!",
                  null, AlertType.ERROR);
      display.setCurrent(alert);
    }
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }

  public void commandAction(Command c, Displayable d) {
    // handle the exit command
    if (c == exitCommand) {
      destroyApp(true);
      notifyDestroyed();
      return;
    }
  }
}

class SensorCanvas extends Canvas {

  // the array of SensorInfo objects
  SensorInfo foundSensors[] = null;

  public SensorCanvas() {
    searchSensors(); // look for them
  }

  protected void paint(Graphics g) {

    // clear the screen
    g.setGrayScale(255);
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(0x000000);

    // let's get the version of the sensors
    g.drawString("Sensor Version: " +
      System.getProperty("microedition.sensor.version"), 0, 0, 0);

    if (foundSensors == null || foundSensors.length == 0) {
      g.drawString("No Sensors found", 0, 2, 0); // nothing found?
    } else {
      int noOfSensors = foundSensors.length; // how many?
      int ydraw = 15; // to help with drawing
      for (int i = 0; i < noOfSensors; i++) {
        g.drawString(
          "Sensor [" + (i + 1) + "]:\r\n " +
          foundSensors[i].getDescription(), 0, ydraw , 0);
        ydraw = ydraw + 30;
      }
    }

  }

  private void searchSensors() {
    // simple enough, find all the sensors
    foundSensors = SensorManager.findSensors(null, null);
  }
}

SensorCanvas searches for the sensors in the searchSensors method and uses the paint method to draw information about the sensors on the screen. Before creating SensorCanvas, the MIDlet checks to see whether the current device supports the Mobile Sensor API by trying to find the microedition.sensor.version system property. The MIDlet does not proceed further if this property is null.

Handling Sensor Information

Once you find a sensor you are interested in, how do you handle the data that it produces? As mentioned in an earlier section, you use the SensorInfo implementation for that sensor and after getting a SensorConnection to the sensor, you use this connection to get the data, either directly or through a listener.

The actual data is encapsulated in the Data interface. Since the data sent by a sensor might not be a single unit of measurement, the data comes in channels. For example, although the data sent by a thermometer sensor would be a single unit (temperature), the data sent by a sensor measuring the tilt of the device will have at least three channels, (X, Y, and Z) that together make up the data sent by the sensor. Therefore, the data that is retrieved is in an array of Data objects, each with its own channel. The ChannelInfo interface provides methods to query information about the channel, such as the name, data type, accuracy, and so on.

The Data interface provides three methods to retrieve the value of the data:

  • getIntValues
  • getDoubleValues
  • getObjectValues

You can use the ChannelInfo.getDataType method to get the type of the data that is expected.

Using the information that we have so far, it is easy to create a simple MIDlet that echoes the current tilt of the device. The following code snippet shows the TiltCanvas class that captures and shows this information.

class TiltCanvas extends Canvas implements DataListener {

  // the sensor
  SensorInfo tiltSensor = null;

  // the connection to it
  SensorConnection connection = null;

  // parameters used to find current tilt
  double axisX, axisY, axisZ;

  public TiltCanvas() {

    // let's find this particular sensor
    SensorInfo sensors[] =
      SensorManager.findSensors(
      "acceleration", SensorInfo.CONTEXT_TYPE_DEVICE);

    if (sensors == null || sensors.length == 0) {
      System.err.println("Nothing found!");
    } else if (sensors.length > 1) {
      System.err.println("Too many sensors found!");
    } else {
      tiltSensor = sensors[0];
      try {
        connection = (SensorConnection) Connector.open(tiltSensor.getUrl());
        connection.setDataListener(this, 10);
      } catch (IOException ex) {
        ex.printStackTrace();
      }
    }

  }

  protected void paint(Graphics g) {

    // clear the screen
    g.setGrayScale(255);
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor(0x000000);

    if (tiltSensor == null) { // sensor not found
      g.drawString("No valid Tilt Sensor found!", 0, 0, 0);
      return;
    } else if (connection == null) { // sensor found, but could not connect
      g.drawString("Can't connect to the Sensor!", 0, 0, 0);
      return;
    } else { // sensor found, and active connection
      g.drawString("Connected and getting data", 0, 0, 0);
      g.drawString("Axis X: " + axisX, 0, 30, 0);
      g.drawString("Axis Y: " + axisY, 0, 60, 0);
      g.drawString("Axis Z: " + axisZ, 0, 90, 0);

    }
  }

  public void dataReceived(SensorConnection conn, Data[] data, boolean lost) {
    axisX = data[0].getDoubleValues()[0];
    axisY = data[1].getDoubleValues()[0];
    axisZ = data[2].getDoubleValues()[0];
    repaint();
  }
}

As you can see, the tilt canvas needs to look for the tilt sensor using the keyword acceleration for the quantity and SensorInfo.CONTEXT_TYPE_DEVICE for the context type. This is the only combination that works for the Java ME SDK 3.0, and you will need to find the correct values to use using the previous SensorFinder MIDlet if you run this code on other devices or emulation platforms. The canvas implements the dataReceived method to capture the data events sent by the acceleration sensor attached to the emulator. Since we know which channel is for what axis (we can find this out by trial and error, as I did, or from the documentation that comes with the emulator), we can assign the channel data to the correct axis. In this case, data[0] is axis X, data[1] is axis Y, and data[2] is axis Z.

Figure 1 shows this MIDlet running in the emulator.


Figure 1: MIDlet Captures External Sensor Event to Provide Device's Current Tilt

Conclusion

This article introduced you to the Mobile Sensor API (JSR 256), which has been around for quite some time and is starting to gain a lot of traction in applications for the Java ME platform. We looked at the details of this API and its interfaces, and we learned how to find all the sensors within a device or emulator. Finally, we learned how to use the information returned by our sensors using a simple example that retrieves data from an accelerator to figure out the current tilt of a device.

See Also

About the Author

Vikram Goyal is the author of Pro Java ME MMAPI: Mobile Media API for Java Micro Edition, published by Apress. This book explains how to add multimedia capabilities to Java technology-enabled phones. Vikram is also the author of the Jakarta Commons Online Bookshelf, and he helps manage a free craft projects Web site.

Left Curve
Java SDKs and Tools
Right Curve
Left Curve
Java Resources
Right Curve
JavaOne Banner
Java 8 banner (182)