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:
- Create the
- Segment the compass readings into chunks, and get an average
- Capture the result and make something happen
Step 1: Create the
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:
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:
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.
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
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,
Subscribe method, as before, allows us to debug print the compass headings to confirm.
Here's the full application so far:
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:
And this gives us one average compass heading every two seconds, minus the jitter, just like we wanted:
Complete program listing so far:
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
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.)
(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: