Using the Compass Sensor

,

The compass sensor is a required component of Windows Phone devices. In the first release of the Windows Phone OS (7.0), however, no API was provided for the compass, and some devices lacked the native drivers necessary to use the compass even when running the Windows Phone 7.5 OS (mango). If you are maintaining an app for a premango device, it is therefore important to verify that the compass is supported before attempting to use it. For this, the static Compass.IsSupported property is used, as demonstrated in the CompassViewModel class (see Listing 16.4). The Start method of the CompassViewModel oversees the creation of the Compass instance.

The default value of the compass TimeBetweenUpdates property is 25 milliseconds, which on my test device was the minimum allowed value.

As with all sensor types, the CurrentValueChanged event is used to receive periodic updates from the sensor.

The Calibrate event allows your app to respond to when the phone is calibrating the sensor. Discussion of calibration continues later in this section.


Note

Calibration is an activity triggered by the OS; there is no PerformCalibration method that you can call to cause the device to begin calibration.


LISTING 16.4. CompassViewModel.Start Method (excerpt 1)


public void Start()
{
    if (!Compass.IsSupported)
    {
        MessageService.ShowMessage("Compass is not supported on this device.");
        return;
    }

    compass = new Compass();
    compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(updateIntervalMs);
    UpdateIntervalMs = (int)compass.TimeBetweenUpdates.TotalMilliseconds;

    compass.CurrentValueChanged += HandleCompassCurrentValueChanged;
    compass.Calibrate += HandleCompassCalibrate;
    compass.Start();
...
}


When the CurrentValueChanged event is raised, the event handler receives a SensorReadingEventArgs<CompassReading> object. CompassReading contains the five properties listed in Table 16.1.

TABLE 16.1. CompassReading Properties

Image

The handler for the Compass object’s CurrentValueChanged event in the CompassViewModel extracts the values from the event arguments and assigns them to various viewmodel properties of the same name. XNA’s Vector3 data type is not amenable to data binding because it exposes its X, Y, and Z dimensions as public fields. Rather than use Vector3, a custom ThreeDimensionalVector object is created using the Vector3 values, as shown in the following excerpt:

void HandleCompassCurrentValueChanged(
          object sender, SensorReadingEventArgs<CompassReading> e)
{
    MagneticHeading = e.SensorReading.MagneticHeading;
    TrueHeading = e.SensorReading.TrueHeading;
    HeadingAccuracy = e.SensorReading.HeadingAccuracy;
    Vector3 reading = e.SensorReading.MagnetometerReading;
    MagnetometerReading = new ThreeDimensionalVector(reading.X,
                                                     reading.Y,
                                                     reading.Z);

    SmoothedReading
        = readingSmoother.ProcessReading(e.SensorReading.MagneticHeading);
}

The raw sensor readings for MagneticHeading and TrueHeading are subject to slight variations in magnetic forces and, like the accelerometer, can make elements look unsteady when bound directly to the UI. A custom class, called ReadingSmoother, is used to dampen the stream of sensor reading values. I have abstracted the data smoothing code of the EnhancedAccelerometer, which was presented in the “Accelerometer” section of this chapter, into a new class called ReadingSmoother. The ReadingSmoother class uses the strategy pattern to allow you to change its filtering behavior via an IFilterStrategy interface provided to its constructor, as shown:

public ReadingSmoother(
          IFilterStrategy filterStrategy = null, int samplesCount = 25)
{
...
}

Listing 16.5 shows the ReadingSmoother.ProcessReading method. ReadingSmoother tracks the previous 25 values provided to it and uses the IFilterStrategy to smooth the data.

LISTING 16.5. ReadingSmoother.ProcessReading Method


public double ProcessReading(double rawValue)
{
    double result = rawValue;

    if (!initialized)
    {
        lock (initilizedLock)
        {
            if (!initialized)
            {
                /* Initialize buffer with first value. */
                sampleSum = rawValue * samplesCount;
                averageValue = rawValue;

                for (int i = 0; i < samplesCount; i++)
                {
                    sampleBuffer[i] = averageValue;
                }

                initialized = true;
            }
        }
    }

    double latestValue;
    if (filterStrategy != null)
    {
        latestValue = result = filterStrategy.ApplyFilter(rawValue, result);
    }
    else
    {
        latestValue = rawValue;
    }
    /* Increment circular buffer insertion index. */
    if (++sampleIndex >= samplesCount)
    {
        /* If at max length then wrap samples back
            * to the beginning position in the list. */
        sampleIndex = 0;
    }
    /* Add new and remove old at sampleIndex. */
    sampleSum += latestValue;
    sampleSum -= sampleBuffer[sampleIndex];
    sampleBuffer[sampleIndex] = latestValue;

    averageValue = sampleSum / samplesCount;

    /* Stability check */
    double deltaAcceleration = averageValue - latestValue;

    if (Math.Abs(deltaAcceleration) > StabilityDelta)
    {
        /* Unstable */
        deviceStableCount = 0;
    }
    else
    {
        if (deviceStableCount < samplesCount)
        {
            ++deviceStableCount;
        }
    }

    if (filterStrategy == null)
    {
        result = averageValue;
    }

    return result;
}


The ReadingSmoother instance is defined as a field of the viewmodel, like so:

readonly ReadingSmoother readingSmoother
              = new ReadingSmoother(new LowPassFilterStrategy());

LowPassFilterStrategy removes noise from the raw sensor values, while allowing fast changes of sufficient amplitude to be detected. See the LowPassFilterStrategy source in the downloadable sample code for more detail.

The content panel of the CompassView page displays the various viewmodel properties, including the current magnetic heading and the accuracy of the compass. In addition, the viewmodel’s SmoothedReading is displayed using an arrow. The arrow is defined as a Path element. An enclosing Canvas uses a RotateTransform, which is bound to the SmoothedReading property, to rotate to the compass heading value. See the following excerpt:

<Canvas Width="280" Height="100">
    <Path Width="275" Height="95"
            Canvas.Left="0" Canvas.Top="0" Stretch="Fill"
            Fill="{StaticResource PhoneAccentBrush}"
            Data="** Arrow points omitted **">
        <Path.RenderTransform>
            <RotateTransform Angle="-90"
                            CenterX="140" CenterY="48" />
        </Path.RenderTransform>
    </Path>
    <Canvas.RenderTransform>
        <RotateTransform Angle="{Binding SmoothedReading,
                            Converter={StaticResource NegateConverter}}"
                            CenterX="140" CenterY="48" />
    </Canvas.RenderTransform>
</Canvas>

A custom IValueConverter, called NegateConverter, is used to negate the value so that the arrow’s heading corresponds to north, rather than the offset value. The NegateConverter.Convert method is shown in the following excerpt:

public object Convert(
    object value, Type targetType, object parameter, CultureInfo culture)
{
    double degrees = (double)value;
    return -degrees;
}

Figure 16.5 shows the CompassView page with the heading arrow pointing to magnetic north. As the phone is rotated, the arrow maintains its heading.

Image

FIGURE 16.5 CompassView page with arrow pointing to magnetic north.

The compass orientation value, shown in Figure 16.5, is determined by the accelerometer. This value is discussed in the following section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.165.234