Saturday, September 24, 2011

Windows Phone 7: Using the Compass

Want to add a compass to your Windows Phone 7 app? Want to get that AR app started? Most of us are in the same boat--we want to use the compass, motion, gyro, etc. but we can't. We've installed Mango but, without manufacturer-specific drivers, we're out of luck. If you're really anxious to get started anyway, let me share what I did and the lessons learned.

My first attempt was to create a set of "watchers".  I created a CompassVirtualWatcher and a CompassSensorWatcher both derived from an abstract base class, CompassWatcher.  The Sensor watcher implemented the base class functions using the actual Mango Compass API.  The Virtual watcher used a set of values that moved relatively randomly to simulate the compass function.

Having dealt with WP7 sensor APIs before, I knew the events would likely be coming in on a different thread.  Since I implemented the watchers using Observable (Microsoft.Phone.Reactive), it was easy to leverage ObserverOnDispatcher (or so I thought) to interact with the UI during updates.

The idea proved useful.  I was able to add a compass to my "Jack of Tools" app, play with it and see it seem to function.  I even simulated variant accuracy and the need for calibration so my calibration UI would pop up.

Once convinced the app worked, I eyed the CompassSensorWatcher for probably an hour just trying to catch any issues I might not see with the virtual watcher.  This included considering the potential threading issues once again but eventually I was convinced I had done my best.

Of course, the moment it hit real hardware with a compass, it locked up.  Now what?  I knew it must be something about working with the actual hardware but just could not figure out how to test that.  I posted in several places begging for drivers, emulators, ideas, etc. to no benefit.  Hence the reason for making this post.

Finally, an epiphany:  I didn't necessarily need compass hardware to recreate my bug.  Any hardware might be enough.  That is where the FauxCompass came in (my Flux Capacitor caliber idea of the year).  I would create an object derived from SensorBase<T> that implemented compass like functionality using the Accelerometer.

Of course, this was not so simple:

  1. SensorBase has only an internal constructor so there would be no deriving from that.
  2. Compass is sealed so there would be no deriving from it either.
  3. CompassReading has a public constructor but no public property setters so there'd be no implementing anything that returned CompassReadings.
This wasn't a show stopper but it made the implementation of the FauxCompass a little less elegant.  In the end, the signature is just like the Compass but using FauxCompassReading instead.  So, to switch between the regular Compass and the Faux Compass requires changing the code in four spots:  the member declaration, the constructor call, the observable declaration, and observable target's signature.

Once in place, I ran my app, switched to the Compass view, and it locked up.  "Damn it!  Now what?!" I thought.  You're probably already there but it took me a few seconds to really get it--that's exactly what I wanted.  My FauxCompass had repeated the bug that the real compass was producing and in short time.  I used it to fix the Compass bug in minutes.

So what did the FauxCompass do?  It consumed Accelerometer readings and converted them to FauxCompassReadings.  Turning the phone on one axis, produced headings from 0 to 359; turning on another axis, varied the accuracy resulting in a CalibrationNeeded event when it hit 20.

I hope this helps someone else waste less time with their compass app.  Please let me know if it does, if it doesn't, or if you have another solution.