Image by Rob Bulmahn
Technical

Getting Started with Android Sensors

Sensors are the lifeblood of the modern connected hardware revolution, the Internet of Things. We collect data about the world around us, push it up to the cloud, and do interesting and useful things with it. In the home it’s Birdi and Sentri, in your local neighborhood dumpster it’s Compology, in space it’s Spire — the list goes on and on. And as sensors, connectivity, and vibrant displays make our hardware products more complex, the operating system running them gets more complex as well. More and more connected hardware is running embedded Linux, or even Android. So today we’re going to spend some time learning about talking to our sensors from Android. 

If you’re anywhere on the spectrum of fledgling hardware hacker to full-blown product development engineer, you’ve probably spent some time gathering sensor data from a microcontroller. The code to do that probably looked something like this: 

void loop() {
 int sensorValue = analogRead(A0); // read the input on analog pin 0: 
 float voltage = sensorValue * (5.0 / 1023.0); // Convert the analog reading to a voltage 
 Serial.println(voltage); // print out the value you read:
} 

from the Arduino website, slightly modified

The steps are simple – set up the pin correctly, read a value. It might be digital, might be analog. If the latter, you might then convert that
voltage based on what kind of sensor you’re working with – into a temperature, or a lux reading, a pressure measurement, who knows.

But when you’re accessing sensor values from an Android app, there are a few more layers of complexity. And by a few, I mean a lot.

ape_fwk_sensors

from Android’s source documentation

Luckily, you shouldn’t have to worry about most of this from the application level. In fact, there are just a few easy steps to get you going…

0// Set up your Android App.
Google’s Building Your First App page will have you covered here. Keep going until you have a MainActivity.java class that you can run, and then feel free to dip out.

1// Check for sensors.
Your Android device won’t necessarily have every sensor under the sun working and ready. To determine what sensors are at your disposal, add this code to the onCreate method in your Android app’s MainActivity class.

private SensorManager yourSensorManager;
yourSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
for(Sensor sensor : yourSensorManager.getSensorList(Sensor.TYPE_ALL))
{
  String sensorType;
  Log.d(TAG, "Found sensor: %s, Type: %s", sensor.getName(), sensor.getType());
} 

A few things are happening here. The first two lines declare and initialize a SensorManager object, which is used to handle administrative tasks around sensors. The for-each loop then iterates through a list of all the sensors we have available in your system (using the SensorManager’s getSensorList method), and prints out information about each sensor (using a few sensor methods and Android’s Log functionality).

2// Get the sensor you want.
Now let’s get a persistent reference to the sensor we’re interested in today. Let’s say that’s an Ambient Light Sensor. We can easily add this functionality to our previous code snippet:

private SensorManager yourSensorManager;
yourSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor ambientLightSensor;

for(Sensor sensor : yourSensorManager.getSensorList(Sensor.TYPE_ALL))
{
     String sensorType;
     Log.d(TAG, "Found sensor: %s, Type: %s", sensor.getName(), sensor.getType());    

       if(sensor.getType() == Sensor.TYPE_LIGHT)
       {
           ambientLightSensor = sensor;
       }
}

Good job! Keep in mind this gets somewhat trickier if you have multiple sensors of the same type. The getSensorList documentation touches on this.

3// Create a listener.
At this point, things get just a little more complicated. In order to get sensor data, we have to create a listener. A listener is an object from a class that implements the SensorEventListener interface. This interface specifies two methods: onSensorChanged and onAccuracyChanged. We’re most concerned with the onSensorChanged method; it will be called whenever your sensor reports a new value.

We could accomplish this with a separate class– or we could utilize the class we already have on-hand, MainActivity. Change your MainActivity class declaration to look like this:

public class MainActivity implements SensorEventListener {

and add two methods:

public final void onSensorChanged(SensorEvent event)
{
   double lightIn = event.values[0];
   ALog.d("Light amount: %02f", lightIn);
}

public final void onAccuracyChanged(Sensor sensor, int x)
{
   //do nothing
} 

You’ll want to customize your onSensorChanged to match your sensor and what data you’re interested in. The SensorEvent’s values array is populated differently for every kind of sensor; check this documentation for what you can expect.

4// Register the listener.
Finally, we’ll need to register our sensor event listener. This is the glue between the listener definition and the sensor we’re interested in. This step is just one line; we’ll add it to our if-statement from step 2.

if(sensor.getType() == Sensor.TYPE_LIGHT)
{
         ambientLightSensor = sensor;
         mSensorManager.registerListener(this, ambientLightSensor,  SensorManager.SENSOR_DELAY_NORMAL);
}

The registerListener method takes three inputs – our listener object, the sensor we’re interested in, and the rate of checking the sensor. Because we have made our parent class the listener, we simply use Java’s this for the first argument.

That’s the gist of it right there. We’re now “listening” for new sensor information. Whenever the sensor reports a new value, our onSensorChanged method will be called. In our case, that will print the value from the sensor to the console. Good job! You’re now up and running with Android sensors.

One final note…
Before I leave you to it, remember one thing. Many of the details provided in the Android Sensor documentation are highly dependent upon the driver provided by the hardware manufacturer! This extends from the simple side of the spectrum, like what event.values provides, to more complex concepts, like how to specify a particular FIFO batch size. A good driver will be sure to follow the Android documentation to the letter. A not-so-good driver will be… full of surprises. If you have access to the driver code, it may be helpful to look through it at times. If not, you might try to get in touch with a support engineer at the hardware manufacturer. Keep this in mind and you may be able to avoid a few headaches!