2012/08/08

How to use Android sensors?

This article teaches how to use Android sensors.

The Android sensors are hardware devices. Android provides a manager to control the devices.User should register the devices you want to use by the manager. And implements the callback functions for the sensor event alert.

Step1:We create a new class for the sensors. This is a reusable class.

public class SensorController implements SensorEventListener 
{
 public SensorController(Context context)
 {
 }
 @Override
 public void onAccuracyChanged(Sensor arg0, int arg1) 
 {
  // TODO Auto-generated method stub
 }
 @Override
 public void onSensorChanged(SensorEvent arg0) 
 {
  // TODO Auto-generated method stub
 }
}
Step2:The class implements two interface for the sensor events.In the class constructor we need the Context to get the sensor manager.
 private SensorManager mSensorManager = null;

 public SensorController(Context context)
 {
   mSensorManager = (SensorManager)context.getSystemService(context.SENSOR_SERVICE);
   registerSensor(); 
 }
Step3:Create the registerSensor() function to register the sensors.At this example i register the magnetic and accelerometer. These two sensor can combined to create a compass on Android.
 private Boolean sensorRegisteredFlag = false;
 private void registerSensor()
 {
  if(mSensorManager != null)
  {
   List sensors = mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
   if(sensors.size() > 0)
   {
    Sensor sensor = sensors.get(0);
    //if(!mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL))
    if(!mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME))//faster than SENSOR_DELAY_NORMAL
    {
     return;
    }
   }
   sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
   if(sensors.size() > 0)
   {
    Sensor sensor = sensors.get(0);
    //if(!mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL))
    if(!mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME))//faster than SENSOR_DELAY_NORMAL
    {
     return;
    }
   }
   sensorRegisteredFlag = true;
  }
 }
Step4:The onAccuracyChanged() method is called when the sensor accuracy has changed. The first parameter is the sensor of the registered. And the other parameter is the new accuracy of the sensor.There are four value of the accuracy.At this example we does not care the changes.
SensorManager.SENSOR_STATUS_ACCURACY_HIGH 
    // This sensor is reporting data with maximum accuracy

SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM 
    // This sensor is reporting data with an average level of accuracy, 
    // calibration with the environment may improve the readings

SensorManager.SENSOR_STATUS_ACCURACY_LOW 
    // This sensor is reporting data with low accuracy, 
    // calibration with the environment is needed

SensorManager.SENSOR_STATUS_UNRELIABLE 
    // The values returned by this sensor cannot be trusted, 
    // calibration is needed or the environment doesn't allow readings
Step5:The onSensorChanged() method is whole thing we want.We collect the magnetic and accelerometer values and convert to the human readable values.
 private final double RADIANS_TO_DEGREES = 180/Math.PI;
 private final float[] sZVector = { 0, 0, 1, 1 };
 private float R[] = new float[16];
 private float remapR[] = new float[16];
 private float remapR_inv[] = new float[16];
 private float AccelerometerValues_last[] = new float[3];
 private float MagneticFieldValues_last[] = new float[3];
 private float orientationValues[] = new float[3];
 private float orientationVector[] = new float[4];
 private float azimuthVector[] = new float[4];
 boolean bHaveAccelerometer = false;
 boolean bHaveMagneticField = false;
 private float orientation;//up direction
 private float azimuth;//aim to north
 private float pitch;

 public void onSensorChanged(SensorEvent event) 
 {
  if(event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
  {
   MagneticFieldValues_last[0] = event.values[0];
   MagneticFieldValues_last[1] = event.values[1];
   MagneticFieldValues_last[2] = event.values[2];
   
   bHaveMagneticField = true;
  }
  if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
  {
   AccelerometerValues_last[0] = event.values[0];
   AccelerometerValues_last[1] = event.values[1];
   AccelerometerValues_last[2] = event.values[2];
   
   bHaveAccelerometer = true;
  }
  if(bHaveMagneticField && bHaveAccelerometer)
  {
   if(SensorManager.getRotationMatrix(R, null, AccelerometerValues_last, MagneticFieldValues_last))
  {
   SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, remapR);
   SensorManager.getOrientation(remapR, orientationValues);
    
   Matrix.multiplyMV(orientationVector, 0, remapR, 0, sZVector, 0);
   pitch = (float) (-Math.atan2(orientationVector[1], orientationVector[2]) * RADIANS_TO_DEGREES);
    
   Matrix.multiplyMV(orientationVector, 0, remapR, 0, sZVector, 0);
   orientation = (float) (-Math.atan2(orientationVector[0], orientationVector[1]) * RADIANS_TO_DEGREES);
    
   Matrix.invertM(remapR_inv, 0, remapR, 0);
   Matrix.multiplyMV(azimuthVector, 0, remapR_inv, 0, sZVector, 0);
   azimuth = (float) (180 + Math.atan2(azimuthVector[0], azimuthVector[1]) * RADIANS_TO_DEGREES);
   }
  }
 }
Step6:Three important values we want are ready. Provide get method for the other class.
 public boolean getNowOrientation(float [] retValues) 
 {
  retValues[0] = pitch;
  retValues[1] = orientation;
  retValues[2] = azimuth;
  return true;
 } 
Step7:Implement the on action methods.
 public void onResume()
 {
  registerSensor();
 }
 
 public void onPause()
 {
  if(mSensorManager != null && sensorRegisteredFlag)
  {
   mSensorManager.unregisterListener(this);
  }
 }
Step8:Back to the main activity class.Create a thread to get the values and refresh the values periodically.
public class MySensors extends Activity 
{
 private SensorController MySensors = null;
 private Handler mHandler = new Handler(Looper.getMainLooper());
 private TextView NowText = null;
 
 @Override
 public void onCreate(Bundle savedInstanceState) 
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
        
  MySensors = new SensorController(this);
        
  NowText = (TextView)findViewById(R.id.TextView01);
        
  mHandler.postDelayed(ReadSensorValues, 300);
 }
    
 private Runnable ReadSensorValues = new Runnable() 
 {
  float orientationValues[] = new float[3];
  public void run() 
  {
   MySensors.getNowOrientation(orientationValues);
         
   NowText.setText("pitch:" + orientationValues[0] + "\n" +
                   "orientation:" + orientationValues[1] + "\n" +
                   "azimuth:" + orientationValues[2]);
         
   mHandler.postDelayed(ReadSensorValues, 100);
  }        
 };
 protected void onResume()
 {
  super.onResume();
  MySensors.onResume();
 }   
   
 protected void onPause()
 {
  super.onPause();
  MySensors.onPause();
 }    
}
Step9:Run the application, you should see the values beat in the TextView.

3 comments:

  1. great article ! Thumbs up !
    Helped me understand how to compute those angles.

    ReplyDelete
  2. Awesome document, did you have a really noisy azimuth

    ReplyDelete
    Replies
    1. Yes, maybe you can try average the vales.
      azimuthAverage = azimuthAverage * 0.8f + azimuth * 0.2f;

      Delete