As promised in our recent blog post about a medieval compass, we’ve put together a few notes about generating compass bearings on the Moverio BT-200 in case this is of use to other developers.

Magnetometer and accelerometer information for the Moverio is accessed using the same Android API as on Android smart phones. Compass direction can be computed using the recommended approach of the getRotationMatrix method followed by getOrientation as documented here, but there is a catch!

To behave as a compass, we require a bearing as the projection of the earth’s magnetic field onto the plane of the displayed compass disc. Compass applications on a smart phone usually show the compass disc in the plane of the phone display screen, so that compass compass bearings are taken with the display roughly parallel to the ground. Clearly we can use the stereo display of the Moverio to show a compass disc in a similar plane, extending away from the eyes of the viewer, so that if the viewer is looking straight ahead the compass plane will be roughly parallel to the ground.

According to Epson’s document BT200_TIW1405CE.pdf document (Technical Information for Application Developer) which registered Epson developers will have access to, we see that accelerometer and magnetometer sensors are oriented such that our desired compass disc is in their XZ plane. This differs from the convention for smart phones in which the display screen is aligned with sensor XY plane. One might think that computing compass bearing for display on the Moverio is simply a matter of taking a different component of rotation produced by Android’s getOrientation method. The problem with this approach is that getOrientation outputs its result as Euler angles. The way that the Android SDK is implemented, the first of the three Euler angles corresponds to a projection angle in the XY plane and the others are not projection angles at all. So effectively the Android API only supports computation of compass bearings when sensor data is expressed in a coordinate system whose XY plane is aligned with the desired plane of display of the compass disc. Therefore to mimic a compass in a horizontal plane extending in the forward-looking direction of the user we must first transform all input to such a coordinate system before using the Android getRotationMatrix and getOrientation methods. It’s easy to do once you know about it! The code is as follows:

/** Copyright (C) Ogglebox Sensory Computing Ltd.
 *  This example Java code is free to use.
 *  All that we ask in return is that you write and tell us about your application
 *  - we'd be delighted to hear from you.
 *
 *  Disclaimer
 *  ----------
 *  No warranties are given whether express, implied, statutory, or other.
 *  To the extent lawfully possible we have no liability whatsoever arising from
 *  this material, irrespective of how it may be used.
 */

import android.app.Activity;
import android.os.Bundle;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class MainViewActivity extends Activity implements SensorEventListener {

	// Helper function to transform measurements taken using 
	// XZ coordinate system for compass disc on smart glasses
	// to XY coordinate system as used on smart phones.
	static void toSmartPhoneCoords(float[] smartphone, float[] moverio) {
		smartphone[0] =  moverio[0];
		smartphone[1] = -moverio[2];
		smartphone[2] =  moverio[1];
	}

	// Compass bearing to be updated from sensor measurements
	float bearing;

	// Main view instance as defined in our application (extends GLSurfaceView)
	MainView mainView;

	// Manager for compass sensors
	SensorManager sensorManager;

	// Intermediate variables required for update
	float[] sensorMagnetic = new float[3];
	float[] sensorAccel = new float[3];  
	float[] matrixR = new float[9];  
	float[] matrixI = new float[9];
	float[] sensorResult = new float[3];

	@Override
	protected void onCreate(Bundle icicle) {    	
      
		// ... usual creation code here ...

		// obtain manager for compass sensors
		this.sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
	}
	
	@Override
	protected void onPause() {

		super.onPause();
		mainView.onPause();

		// pause reception of compass measurement events
		sensorManager.unregisterListener(this);
	}

	@Override
	protected void onResume() {

		super.onResume();
		mainView.onResume();

		// begin reception of sensor measurements required for compass (magnetometer and accelerometer)
		sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),  SensorManager.SENSOR_DELAY_GAME);
		sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),  SensorManager.SENSOR_DELAY_GAME);
	}
		
	// Handle incoming measurements from magnetometer and accelerometer		
	@Override
	public void onSensorChanged(SensorEvent event) {
		
		if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {

			// convert coord system and store magnetic field measurements
			toSmartPhoneCoords(sensorMagnetic, event.values);
			
		}
		else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
				
			// convert coord system and store accelerometer measurements
			toSmartPhoneCoords(sensorAccel, event.values);
				
			// compute rotation matrix
			if (SensorManager.getRotationMatrix(
				matrixR, matrixI, sensorAccel, sensorMagnetic)) {
				
				// extract euler angles
				SensorManager.getOrientation(matrixR, sensorResult);

				// use first euler angle as projection angle
				// to get bearing on compass disc
				bearing = (float)Math.toDegrees(sensorResult[0]);

				// ... code here to low-pass filter and update display ...
			} 
		}
	}

	@Override
	public void onAccuracyChanged(Sensor s, int value) {
	}
}

If you’re a developer and found this useful, please do write and tell us - we’d be delighted to hear about your work.