Random geekery since 2005.

Husband, father, programmer, speaker, writer, blogger, podcaster, collector, traveler, golfer.

31 Days of Mango | Day #4: Compass

Written in

by

Day4-CompassDay3-RemindersDay2-DeviceStatusDay1-EmulatorTools

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!

DownloadIcon

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:

image

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:

image

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 x:Name="LayoutRoot" Background="Transparent">
    <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 System;
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 System;
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 System;
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.

download

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!

toolsbutton

Tags

14 responses to “31 Days of Mango | Day #4: Compass”

  1. Georg Avatar

    Hi Jeff,

    While translating the article to German I stumbled across the paragraph above the last code snippet. You are saying that “we need to move off of the primary UI thread” to update the UI.

    I wonder if it should not be the other way round? I thought that the event handler would be called on a non-UI thread (which would cause the invalid cross-thread exception if we tried to directly update the UI). In order to update the UI we have to dispatch back to the UI thread.

    Am I mistaking something here?

  2. […] 31 Days of Mango | Day #4: Compass […]

  3. […] 31 Days of Mango | Day #4: Compass […]

  4. […] Der Originalartikel befindet sich hier: Day #4: Compass. […]

  5. […] Эта статья День#4 из серии статей 31 День c Mango. Версия на английском языке доступна по ссылке. […]

  6. […] Англоязычная версия доступна здесь. […]

  7. […] Este artículo es una traducción de Día 4: Compass , puedes encontrar aquí la versión en inglés… […]

  8. Frostie Avatar
    Frostie

    Hi there

    I went through all the steps in this tutorial and I got heaps of errors in my project. Then I downloaded your “working solution” code and compared the two sets of XAML and C# codes side by side (your solution next to my solution). The two projects consist of the same XAML and C# code. But for some reason there are heaps of errors in mine. So I did some error checking and discovered I needed to add references, so I did. Still I had errors in mine! It told me that “‘Gyroscope’ is a ‘namespace’ but is used like a ‘type’” and some other “type” related things.

    Is there something you’ve set up differently in your computer for Visual Studio, or is it something that I don’t know about. I haven’t personalized VS on my computer, it’s stock standard.

    Please help
    Frostie

    1. jeffblankenburg Avatar
      jeffblankenburg

      I haven’t done anything special to my setup of Visual Studio…do you have the newest Windows Phone SDK?

  9. Linn Avatar
    Linn

    Hi!
    Is there any way to easily replace the white line with a picture of an arrow? I had no problems following your tutorial, and just wanted to take it a step further. 🙂

    Linn

  10. […] If you have done any Windows Phone development previously, you might have read Jeff’s article on the Windows Phone compass, which provides magnetic and true north headings, as well as X, Y, and Z rotation […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a website or blog at WordPress.com

%d bloggers like this: