Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Silverlight 5 in Action Book Excerpt: Using the Accelerometer from Silverlight 5 via Native Extensions

Pete Brown - 04 October 2011

What follows is a small pre-editing excerpt from chapter 33 of Silverlight 5 in Action (without footnotes, annotations and other publishing stuff). The chapter as a whole goes into greater detail on using COM Automation, the Native Extensions for Silverlight, and PInvoke.

Help me avoid putting my family out on the street to support my unquenchable robotics, synthesizer, CNC and other habits. Order your copy of Silverlight 5 in Action from Manning and get chapters sent to you as soon as they are submitted by me and my development editor, or pre-order your print-only copy from Amazon.com.

Helpful References for this excerpt

33.2.1 Accessing an accelerometer

An accelerometer is a device, often as small or smaller than a shelled sunflower seed, which can report movement in some combination of the X, Y and Z planes. Some accelerometers only do X and Y - sufficient for joy sticks. Others will handle all three. When you tilt your phone (or iPad or Windows slate) to play a game that moves a marble around the screen, you are using an accelerometer. Accelerometers are also used to quickly shut down and park an old fashioned spinning hard drive in the event that the device is dropped.

External USB accelerometer boards may also be purchased from the likes of FreeScale Semiconductor. They have an external board called the JMBADGE2008 (http://bit.ly/JMBadge) which includes an accelerometer as well as several other Windows Sensor API-compatible sensors.

Of course, you can't really follow this particular example unless you have one of those sensor boards installed on your system. I have information on setting up the Freescale sensor on your system at my blog http://bit.ly/10RemFreescale. Even if you do not have the sensor, however, you'll want to follow the next steps and install and recompile the native extensions.

Install and recompile the native extensions

First, download and install NESL from the link in the introduction to this section. You'll want to download both the Runtime and the Source zip files. Once downloaded, unzip both files into a known location on your machine. For example, in a subfolder of your projects folder.

Because this is the first Silverlight 5 project we've built with the Native Extensions, and because the Native Extensions target Silverlight 4, we need to do a small recompile. Open up the solution found in the Source folder. On the version I'm writing against, it's named NESLSourcev2.sln.

The solution has two folders, one for libraries and one for the samples. If you're using a free version of Visual Studio, those folders may not be present. We'll be working with the Silverlight libraries first.

For each of the Silverlight libraries projects, go to its project properties and change the target version to Silverlight 5 as shown in figure 33.2.

image

Figure 33.2 Change each of the library project's build properties to target Silverlight 5

I admit that this isn't strictly necessary, but I personally hate referencing DLLs from other versions of any framework, especially Silverlight. Recompiling to Silverlight 5 guarantees that you'll get the expected behavior and compile-time warnings or errors, when available, due to any breaking changes.

Once you've completed that, do the same for the samples. Because they're in the same solution, you'll need to update them to avoid a whole mess of compile errors. Once complete, do a Rebuild All, and you're done with that part. Take note of where the projects are stored and compiled to.

Project setup

Now create a new Silverlight 5 project; I named mine NeslSensorDemo. Add a reference to the Microsoft.Silverlight.Windows.dll from the NESL bin/debug folder. This will drag along a dependency on Microsoft.CSharp.dll for the dynamic keyword as seen in section 33.1. Remember, NESL is just using COM automation under the covers.

The library we'll be primarily working with is the Microsoft.Silverlight.Windows.Sensors library. That has a dependency on Microsoft.Silverlight.Windows.Platform, so go ahead and add references to both of those as well.

Before we create the test user interface, we'll need to create the AccelerometerJoystick class itself. This class interfaces with NESL to perform the hard work of creating the accelerometer and then translating its movement to the cardinal points of a compass. Listing 33.6 has the code required to find the accelerometer and initialize it.

Listing 33.6 The AccelerometerJoystick class structure and Main Functions

using System;
using System.Linq;
using System.Windows;
using Microsoft.Silverlight.Windows.Platform;
using Microsoft.Silverlight.Windows.Sensors;

namespace NeslSensorDemo
{
public class AccelerometerJoystick : IDisposable
{
private SensorManager _sensorManager = null;
private Sensor _accelerometer = null;
private PropertyKey _keyXGs = null;
private PropertyKey _keyYGs = null;

public AccelerometerJoystick()
{
if (Application.Current.HasElevatedPermissions)
{
_sensorManager = new SensorManager();

_accelerometer = GetFirstAccelerometer();

if (_accelerometer != null)
{
_keyXGs = new PropertyKey(
MotionDataFields.Base,
MotionDataFields.SENSOR_DATA_TYPE_ACCELERATION_X_G);
_keyYGs = new PropertyKey(
MotionDataFields.Base,
MotionDataFields.SENSOR_DATA_TYPE_ACCELERATION_Y_G);

_accelerometer.SensorDataUpdated +=
new EventHandler<SensorDataUpdatedEventArgs>(
Accelerometer_SensorDataUpdated);
}
else
{
throw new Exception("No accelerometers found.");
}
}
else
{
throw new Exception(
"Elevated permissions required to use the accelerometer.");
}
}

private Sensor GetFirstAccelerometer()
{
var sensors = _sensorManager.GetSensorsByCategory(
SensorCategories.SENSOR_CATEGORY_MOTION)
.ToList();

if (sensors.Count > 0)
return sensors[0];
else
return null;
}

// insert other code here
}
}

This listing includes the definition for the AccelerometerJoystick class as well as the initialization functions. When using the Sensor API in Windows, without a friendlier wrapper, the data you get back is based on key/value pairs. A good practice is to set up the property keys ahead of time, and set them aside for use in the sensor reporting event. That's what I did here. Note that I'm only getting the X and Y acceleration G's. I'm ignoring the Z force.

Inside the GetFirstAccelerometer fuction, the code looks for the first accelerometer in the system and uses it. That's hardly robust, but typically a computer will not have more than one accelerometer. So, for demo sake, it's good enough.

Listing 33.7 shows the code for reporting direction changes.

Listing 33.7 The AccelerometerJoystick class Reporting Code

public bool IsPointedNorth { get; private set; }
public bool IsPointedSouth { get; private set; }
public bool IsPointedEast { get; private set; }
public bool IsPointedWest { get; private set; }

private const double _sensitivity = 0.3;

public event EventHandler OrientationChanged;

private void Accelerometer_SensorDataUpdated(
object sender, SensorDataUpdatedEventArgs args)
{
var values = args.NewData.GetSensorValues(
new PropertyKey[] {_keyXGs, _keyYGs}).ToList();

double gForceX = Convert.ToDouble(values[0].Value);
double gForceY = Convert.ToDouble(values[1].Value);

bool north = false, south = false,
east = false, west = false ;

if (Math.Abs(gForceX) > _sensitivity)
{
if (gForceX > 0)
east = true;
else
west = true;
}

if (Math.Abs(gForceY) > _sensitivity)
{
if (gForceY > 0)
south = true;
else
north = true;
}

if (east != IsPointedEast || west != IsPointedWest ||
north != IsPointedNorth || south != IsPointedSouth)
{
IsPointedNorth = north;
IsPointedSouth = south;
IsPointedEast = east;
IsPointedWest = west;

if (OrientationChanged != null)
OrientationChanged(this, EventArgs.Empty);
}
}

This listing contains the AccelerometerJoystick code for reporting the g-force changes back in the form of joystick directions represented by four bool properties, each representing a cardinal point.

Each time the sensor reports back a new set of values, the code uses the cached property keys to retrieve the X and Y g-forces. It then checks to see if the value reported is greater than the sensitivity constant. This is to allow some tilt without constantly changing direction on you. For my accelerometer, the values reported back are zero for center, and then negative or positive values for the opposite directions on a given axis. Finally, it checks to see if the new direction is different from the currently set direction. If that's true, it sets the properties and raises an OrientationChanged event.

The NESL SensorManager and Sensor classes both implement IDisposable. Because I am keeping class-level references to those, I also need to implement IDisposable. Remember, this code bridges out to COM, so releasing those resources is extra important. The code for the IDisposable implementation is in listing 33.8.

Listing 33.8 The AccelerometerJoystick class Cleanup Code

~AccelerometerJoystick()
{
Dispose();
}

private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_accelerometer != null)
_accelerometer.Dispose();

if (_sensorManager != null)
_sensorManager.Dispose();
}
_accelerometer = null;
_sensorManager = null;
}
}

This code handles the cleanup of the class and its resources, to help ensure that the COM objects are properly released. Place it near the end of the AccelerometerJoystick class and we're ready to start using it.

The demo application will show how to use the X and Y axes of the accelerometer so create something like a joystick. I'll simply light up the appropriate location in a nine grid when the accelerometer is tilted in that direction. The application will look like figure 33.3.

image

Figure 33.3 The running accelerometer demo application

The user interface consists of a nine grid, like a tic-tac-toe board, implemented as a regular old grid. Inside each out the outer cells, I have a rectangle and some text. The default style for the rectangle is defined using an implicit style (see chapter 14). The style for the TextBlocks with the compass points is also defined using an implicit style. I'll talk some more about the styles in use after the code. Listing 33.9 shows the full XAML for the UI.

Listing 33.9 The UI XAML for the accelerometer demo application

<UserControl x:Class="NeslSensorDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="400">
<UserControl.Resources>
<Style x:Key="BaseRectangleStyle" TargetType="Rectangle">
<Setter Property="Fill" Value="White" />
<Setter Property="Stroke" Value="Gray" />
<Setter Property="Margin" Value="5" />
</Style>

<Style TargetType="Rectangle"
BasedOn="{StaticResource BaseRectangleStyle}" />

<Style x:Key="SelectedRectangle" TargetType="Rectangle"
BasedOn="{StaticResource BaseRectangleStyle}">
<Setter Property="Fill" Value="LightGreen" />
<Setter Property="Stroke" Value="Black" />
</Style>

<Style x:Key="DeselectedRectangle" TargetType="Rectangle"
BasedOn="{StaticResource BaseRectangleStyle}" />

<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="40" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources>

<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="NineGrid" Width="300" Height="300"
Background="LightGray">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>

<Rectangle x:Name="NorthWest" Grid.Row="0" Grid.Column="0" />
<Rectangle x:Name="North" Grid.Row="0" Grid.Column="1" />
<Rectangle x:Name="NorthEast" Grid.Row="0" Grid.Column="2" />
<Rectangle x:Name="WestWest" Grid.Row="1" Grid.Column="0" />
<Rectangle x:Name="East" Grid.Row="1" Grid.Column="2" />
<Rectangle x:Name="SouthWest" Grid.Row="2" Grid.Column="0" />
<Rectangle x:Name="South" Grid.Row="2" Grid.Column="1" />
<Rectangle x:Name="SouthEast" Grid.Row="2" Grid.Column="2" />

<TextBlock Text="NW" Grid.Row="0" Grid.Column="0" />
<TextBlock Text="N" Grid.Row="0" Grid.Column="1" />
<TextBlock Text="NE" Grid.Row="0" Grid.Column="2" />
<TextBlock Text="W" Grid.Row="1" Grid.Column="0" />
<TextBlock Text="E" Grid.Row="1" Grid.Column="2" />
<TextBlock Text="SW" Grid.Row="2" Grid.Column="0" />
<TextBlock Text="S" Grid.Row="2" Grid.Column="1" />
<TextBlock Text="SE" Grid.Row="2" Grid.Column="2" />
</Grid>
</Grid>
</UserControl>

In addition to the styles already mentioned, I include two important keyed styles as resources. The first style defines what the rectangles should look like when they are "selected". That is, what they should look like when the joystick is pointed in their direction. The second defines the "deselected" look. Because deselected is the default look, the style is empty except for referring back to the base style.

When the joystick is moved in a particular direction, the rectangle representing that direction is "lit up" using the appropriate style. The code for this may be found in listing 33.10.

Listing 33.10 Code-Behind for the Main Page

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;

namespace NeslSensorDemo
{
public partial class MainPage : UserControl
{
private AccelerometerJoystick _joystick;
private Style _selectedStyle;
private Style _deselectedStyle;

public MainPage()
{
InitializeComponent();

Loaded +=new RoutedEventHandler(MainPage_Loaded);
}

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
try
{
_selectedStyle =
Resources["SelectedRectangle"] as Style;
_deselectedStyle =
Resources["DeselectedRectangle"] as Style;

_joystick = new AccelerometerJoystick();

_joystick.OrientationChanged +=
new EventHandler(JoystickOrientationChanged);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}

private void JoystickOrientationChanged(object sender, EventArgs e)
{
foreach (UIElement child in NineGrid.Children)
{
if (child is Rectangle)
((Rectangle)child).Style = _deselectedStyle;
}

if (_joystick.IsPointedNorth &&
_joystick.IsPointedEast)
NorthEast.Style = _selectedStyle;
else if (_joystick.IsPointedNorth &&
_joystick.IsPointedWest)
NorthWest.Style = _selectedStyle;
else if (_joystick.IsPointedSouth &&
_joystick.IsPointedEast)
SouthEast.Style = _selectedStyle;
else if (_joystick.IsPointedSouth &&
_joystick.IsPointedWest)
SouthWest.Style = _selectedStyle;
else
{
if (_joystick.IsPointedSouth)
South.Style = _selectedStyle;
else if (_joystick.IsPointedEast)
East.Style = _selectedStyle;
else if (_joystick.IsPointedWest)
WestWest.Style = _selectedStyle;
else if (_joystick.IsPointedNorth)
North.Style = _selectedStyle;
}
}
}
}

The code in this listing creates the AccelerometerJoystick class and then wires up the OrientationChanged event. Each time the orientation changes, it updates the UI by applying a style to the appropriate rectangle. Note how this code translates a simultaneous two-point value into one of the secondary compass points (Northeast, Southwest etc.)

With this final bit of code in place, run the application. When you tilt the accelerometer, you should see the different squares light up on the screen. If you're using the Freescale board and aren't seeing any results, run their test program to be sure the board is working and the driver is installed. Don't forget to enable the sensor in the Control panel in Windows as described in my blog post at http://bit.ly/10RemFreescale.

It may seem like a fair bit of code to accomplish what we did here. However, if you really think about what just happened, it's pretty amazing. We took a web technology, had it talk to a set of custom C++ COM automation servers which then talked to the Windows 7 Sensor and Location API and returned back g-force information whenever a board was tilted. That's pretty awesome.

Of course, the accelerometer is interesting, and has lots of practical uses. However, it's unlikely that most people will have one on their work PC. Another use of NESL is to integrate with the Windows 7 taskbar, something almost every application can take advantage of.

[Next book section shows how to integrate with the Windows taskbar]

Source code available via the download link below. If you enjoyed this read, please consider picking up a copy of my book, Silverlight 5 in Action.

       

Source Code and Related Media

Download /media/78354/neslsensordemo.zip
posted by Pete Brown on Tuesday, October 4, 2011
filed under:        

3 comments for “Silverlight 5 in Action Book Excerpt: Using the Accelerometer from Silverlight 5 via Native Extensions”

  1. Asif Mushtaqsays:
    I think the following method can be improved:

    private Sensor GetFirstAccelerometer()
    {
    var sensors = _sensorManager.GetSensorsByCategory(
    SensorCategories.SENSOR_CATEGORY_MOTION)
    .ToList()
    .FirstOrDefault();
    }
  2. Asif Mushtaqsays:
    I think the following method can be improved:

    private Sensor GetFirstAccelerometer()
    {
    var sensor = _sensorManager.GetSensorsByCategory(
    SensorCategories.SENSOR_CATEGORY_MOTION)
    .ToList()
    .FirstOrDefault();

    return sensor;
    }

Comment on this Post

Remember me