This article is Day #4 in a series called the 31 Days of Mango.
Today, I’m going to discuss a sensor that is inside of every Windows Phone device today: the Compass. In the original phones, every device had a compass, but developers didn’t have an API to access it. With the release of Windows Phone 7.5, it became an optional piece of equipment, but with a rich API similar to the Accelerometer.
In addition, if you’d like to try this application out on your device right now, it’s available in the Windows Phone Marketplace!
Before I get into the “how” of the Compass API, however, let’s talk a little about what the Compass really is.
What is a Compass?
In traditional terms, a compass is used to determine the direction of the Earth’s magnetic north pole. When you hear the word compass, you probably imagine one of these:
There’s certainly not one of these taking up space in your device. Instead, mobile phones opt to use something more accurately referred to as a magnetometer. In our case, a magnetometer can still determine the direction of magnetic north, but it can also determine rotation of the device relative to magnetic north. In addition, it is also capable of detecting magnetic fields around the device (which often interfere with those other calculations). To best demonstrate this, I’ve created a quick video where I use a magnet with my Windows Phone device.
The X, Y, and Z values that you see on the screen are actually measured in microteslas, which are a unit of magnetic field strength. The white values that were shown above X, Y, and Z are the magnetic and true headings, measured in degrees. The MagneticHeading uses the magnetic north pole as its reference, while the TrueHeading uses the geographic north pole. So now that we’ve dived into the science of the Compass sensor, let’s take a look at how we build the application shown in the video.
Using the Windows Phone Compass
First, let’s build out the user interface. In case you didn’t watch the video above, we’re going to be creating this:
Once you build this sample app, I’m certain you’re also going to grab a magnet and give my little video trick a try. Here’s the XAML to get everything started in your interface:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="31 DAYS OF MANGO" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="compass" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Height="30" HorizontalAlignment="Left" Margin="20,73,0,0" Text="MAGNETIC" VerticalAlignment="Top" Foreground="White" FontSize="28" FontWeight="Bold"/>
<TextBlock Height="30" HorizontalAlignment="Right" Margin="0,74,47,0" Text="TRUE" VerticalAlignment="Top" Foreground="Gray" FontSize="28" FontWeight="Bold"/>
<TextBlock Height="30" HorizontalAlignment="Left" Margin="20,100,0,0" Name="magneticValue" Text="1.0" VerticalAlignment="Top" Foreground="White" FontSize="28" FontWeight="Bold" Width="147" TextAlignment="Center" />
<TextBlock Height="30" HorizontalAlignment="Right" Margin="0,100,20,0" Name="trueValue" Text="1.0" VerticalAlignment="Top" Foreground="Gray" FontSize="28" FontWeight="Bold" Width="123" TextAlignment="Center" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="20,140,0,0" Name="xBlock" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red" FontSize="28" FontWeight="Bold"/>
<TextBlock Height="30" HorizontalAlignment="Center" Margin="0,140,0,0" Name="yBlock" Text="Y: 1.0" VerticalAlignment="Top" Foreground="Green" FontSize="28" FontWeight="Bold"/>
<TextBlock Height="30" HorizontalAlignment="Right" Margin="0,140,20,0" Name="zBlock" Text="Z: 1.0" VerticalAlignment="Top" Foreground="Blue" FontSize="28" FontWeight="Bold"/>
<Line x:Name="magneticLine" X1="240" Y1="350" X2="240" Y2="270" Stroke="White" StrokeThickness="4"></Line>
</Grid>
</Grid>
You can see that we really just have a bunch of TextBlock values, and one Line. This line will be used to act like a traditional compass, pointing north. However, it actually points at the strongest magnetic field, which is why it moves so much when we introduced the magnet.
Most of our effort will be in our code-behind file, including some slightly advanced math to make our line move appropriately. Let’s start with initializing and detecting our Compass on the device.
using Microsoft.Phone.Controls;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;
namespace Day4_Compass
{
public partial class MainPage : PhoneApplicationPage
{
Compass compass;
public MainPage()
{
InitializeComponent();
if (Compass.IsSupported)
{
//DO SOMETHING
}
}
}
}
You can see that we had to add a reference to the Microsoft.Devices.Sensors assembly (we also added a reference to Microsoft.Xna.Framework, but that’s for later). This gives us the ability to communicate with the Compass sensor, and also check to see if the user’s device supports a Compass. Where I left the “DO SOMETHING” comment is where we will initialize our Compass and create an event handler to handle the data it produces.
using Microsoft.Phone.Controls;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;
namespace Day4_Compass
{
public partial class MainPage : PhoneApplicationPage
{
Compass compass;
public MainPage()
{
InitializeComponent();
if (Compass.IsSupported)
{
compass = new Compass();
compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(1);
compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged);
compass.Start();
}
}
void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
{
//MAKE THIS THREAD-SAFE
}
}
}
In the above code example, we added a TimeBetweenUpdates property, which limits how often we get new data from the sensor. I’ve set it to give me updates as often as possible, but this is also a battery consideration. I don’t recommend reading from any of the sensors any more often than you need to. Taking slower readings will also give you the ability to provide a smoother “needle” movement. You should notice that in my video earlier, the needle of the compass jumps very erratically. This is a result of my frequent TimeBetweenUpdates.
I also created a CurrentValueChanged event handler, which will give us the new Compass values each time a change is detected. For something as sensitive as the Compass (or tomorrow’s subject, the Gyroscope), you’re likely to get a steady feed of data, so handle this in a way that makes sense for your application.
Finally, we call the Start() method, which tells the Compass to start paying attention to data. This data is then passed to our event handler method, compass_CurrentValueChanged, as a CompassReading object. In order to read this data, we actually need to move off of the primary UI thread of the processor. If we try to read this data directly in this event handler method, we will get an UnauthorizedAccessException, which prevents invalid cross-thread access. So, where our “MAKE THIS THREAD SAFE” comment lives, we are going to pass our CompassReading object to a method on a separate thread, which gives us our final code below:
using Microsoft.Phone.Controls;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;
namespace Day4_Compass
{
public partial class MainPage : PhoneApplicationPage
{
Compass compass;
public MainPage()
{
InitializeComponent();
if (Compass.IsSupported)
{
compass = new Compass();
compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(1);
compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged);
compass.Start();
}
}
void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
{
Dispatcher.BeginInvoke(() => UpdateUI(e.SensorReading));
}
private void UpdateUI(CompassReading compassReading)
{
magneticValue.Text = compassReading.MagneticHeading.ToString("0.00");
trueValue.Text = compassReading.TrueHeading.ToString("0.00");
magneticLine.X2 = magneticLine.X1 – (200 * Math.Sin(MathHelper.ToRadians((float)compassReading.MagneticHeading)));
magneticLine.Y2 = magneticLine.Y1 – (200 * Math.Cos(MathHelper.ToRadians((float)compassReading.MagneticHeading)));
xBlock.Text = "X: " + compassReading.MagnetometerReading.X.ToString("0.00");
yBlock.Text = "Y: " + compassReading.MagnetometerReading.Y.ToString("0.00");
zBlock.Text = "Z: " + compassReading.MagnetometerReading.Z.ToString("0.00");
}
}
}
Using the Dispatcher.BeginInvoke method allows us to pass our data to a separate thread, where we can work with the data. Inside our UpdateUI method is where we can start to work on updating our UI with the information from our Compass.
We first gather our MagneticHeading and TrueHeading, and assign those to our TextBlocks. At the bottom of the UpdateUI method, we also assign the raw X, Y, and Z values to our UI elements. For determining our line’s direction, however, we need some advanced math, and we actually pull in the MathHelper class from the Microsoft.Xna.Framework assembly.
What I am doing in those two values for magneticLine is determining the X and Y coordinates of the end of the Line control we’re using. Because it always anchored to the center of the screen, we calculate our math based on that position. This is why I start with the magneticLine.X1 and magneticLine.Y1 values. To explain the rest of the math, the value “200” is actually the length of the line. The line is always 200 pixels long. The rest of the calculation uses a conversion of Radians from the MagneticHeading value to determine where on the curve (using Sin and Cos functions) the end of the line should reside.
By using the XAML above and the C# that we just walked through, you can easily build a compelling Compass data reader.
Summary
The Compass is a powerful sensor in our Windows Phones that gives us the ability to determine which direction the device is pointing, in addition to a measurement of magnetic fields around the device. Over the next few days, we are going to look at another sensor, the Gyroscope, and then we’ll look at the new Motion class that actually combines all of our sensors into one API that gives us a much richer look at the orientation of the user’s device.
If you would like to download a working solution that accesses a Windows Phone compass sensor, click the Download Code button below.
Tomorrow, I’m going to write about another cool sensor: the Gyroscope. It’s an amazing tool for understanding the orientation of your user’s device, and one of those topics that you should definitely spend some time on. See you then!
Leave a Reply