Factor Mystic

6May/122

Using Reactive Extensions to smooth compass data in Windows Phone

Getting Started

The challenge is to take the raw compass heading data, and smooth it out over time so the UI element this data is bound to (such as an arrow) won't be jittery. You can see this jitter in the SDK example for the Compass: theĀ CurrentValueChanged event handler is fired every 20ms and jitters within the error margin for the hardware. Our task is to smooth out these data readings for a more pleasant user interface. And to make it fun, we'll be using Reactive Extensions.

It's quite easy to calculate a periodic average of a data stream with Reactive Extensions, and we'll tackle it in three stages:

  1. Create the Observable
  2. Segment the compass readings into chunks, and get an average
  3. Capture the result and make something happen

Step 1: Create the Observable

Starting with the Compass's CurrentValueChanging event, we need to convert it into an Observable. After creating a new Compass object (hereafter simply called "compass"), we can write this with any overload of Observable.FromEvent, either by manually providing delegates to add/remove event subscriptions (as commonly listed on RxWiki), or by providing the event name as a string:

https://gist.github.com/2607208

For succinctness, I'll use the latter in this post.

If we stop right here for a moment, we can confirm that our compass is working and that we can get readings by subscribing to the Observable's stream of SensorReadingEventArgs events by tapping in with the Subscribe method and printing out the raw heading readings:

https://gist.github.com/2607213

Running this sample will spit out an endless series of raw readings in the debug output window:

So far, so good. But, we could've done this all with a standard event handler, so let's kick it up an notch...

Step 2: Break into chunks and average

We want to avoid jitter in our compass readings, and a simple way to do that is to average all the individual readings over a small time period, of say, two seconds. Without Rx, you'd need to declare a period reset timer, and then keep track of a running average over the two second period, then clear it when the timer fires and so forth.

With Rx, it's chillingly simple; we can do it by chaining just two methods: BufferWithTime followed by a standard Linq SelectMany (well, and a Select). And it's all in a single statement.

https://gist.github.com/2608813

Running this will spit out readings just like before, except now, they're clumped every two seconds, with a pause in between.

Let's break it down:

Picking up after creating the Observable, we chain a call to BufferWithTime. For the specified period (two seconds, passed in with TimeSpan.FromSeconds(2), BufferWithTime takes events and stuffs them into an IList of events. Then, we use Select to project that list of events into a list of compass headings (pulled out of the event args, e.EventArgs.SensorReading.TrueHeading). The Subscribe method, as before, allows us to debug print the compass headings to confirm.

Here's the full application so far:

https://gist.github.com/2608127

Our raw compass readings are broken into two-second lists, but they're still individually jittery. However, we can instead take each list and aggregate it into a single heading value. By replacing the SelectMany, which gave us a list of headings, with a Select, we can do the averaging right inline:

https://gist.github.com/2609744

And this gives us one average compass heading every two seconds, minus the jitter, just like we wanted:

Complete program listing so far:

https://gist.github.com/2609858

Step 3: Making something happen

We now have de-jittered compass readings coming in every two seconds. Let's hook this data up to the UI and build an actual application.

Taking this empty Windows Phone project, I've added the following Image into the default content Grid:

https://gist.github.com/2613124

There's nothing special here, it's just an image with a default RenderTransform that sets the rotational center of the image element to the middle.

Pasting in the Rx code we developed above, we can swap out the debug call and instead update the RenderTransform's angle property to the compass heading (actually, 360 minus the heading... we want the arrow image to rotate opposite the phone device. If we didn't do this subtraction, it'd rotate the header angle, but in the same direction we turn the phone.)

https://gist.github.com/2613466

(Oh, and there's one other change: adding in a call to ObserveOnDispatcher allows us to execute the Subscribe delegate (eg, update the arrow rotation) on the UI thread. We didn't need to worry about that before because we were just debug printing, and not accessing any resources created on the UI thread.)

Running the whole program now shows the compass arrow updating once every two seconds, correctly pointing to the device's compass heading. Feel free to adjust the period from two seconds to something more useful... I found 0.5 seconds to be a good balance between update frequency and heading jitter.

I've posted the complete example application on github, so feel free to check it out:
https://github.com/factormystic/rx-compass-smoothing