7 on 7: Sensor and Location API

Windows 7You just arrived in LA from Boston, and when you get to your hotel and pop open your laptop, the weather widget on your desktop notes the local temperature on this bright sunny day, and when you search for a coffee shop in Santa Monica the nearest one 10 miles away, not 3000.  Once you get to the coffee shop, the only table is near a window, and while the glare is awful, your laptop adjusts to the ambient lighting so you can use your applications with ease.  What’s the secret?  sensors and location awareness.

Depending on the source, notebook sales eclipsed desktop sales as long ago as 2006, and with the Netbook phenomenon, we are only becoming more and more mobile, computing-wise.  *We* know where we are, why shouldn’t our computer?  That’s the primary scenario that the Windows 7 Sensor and Location Platform enables.

Sensor and Location Framework You can almost think of the API as “ODBC for sensors.”  Just as ODBC provided a common API to access essentially any relational (and some not) data sources, the new API provides a common approach for both driver vendors and software developers to tap into the power of devices like GPS, accelerometers, temperature sensors, biometric devices, and so on. 

As you can see on the left, vendors can build sensor drivers, which amount to COM objects, via the User-Mode Driver Framework along with a specific  Sensor Class Extension that simplifies the effort and amount of code required.  If you’re looking to build your own driver, I’d recommend the Windows Hardware Developer Center as a start, where you can download the Windows Driver Kit specifically.

Virtual light sensorIf you just want to experiment with an existing sensor, there’s a couple of options.  The Windows 7 SDK includes a virtual light sensor (look in the Bin directory), which provides a simple user interface to simulate various lighting conditions.  If you have an application that interacts with the sensor API, this a quick way to test.

Another option is the Freescale JM Badge board, which includes a light-sensor, accelerometer, and capacitive touch sensors.  Bob’s blog post will lead you through the setup and installation of the driver and hardware, so you can do cool things like play around with the XNA Racing Game!

When you’ve installed a sensor, it surfaces in the Control Panel under Hardware and Sound > Location and Other Sensors, where you can enable or disable the sensor hardware.  If you select a sensor from the list, you can even provide access policies, specifying which users and groups have access to that sensor.

 

Location and Sensor control panel Sensor access

Now that you’ve got a sensor installed, how can you use it within your applications?  The Sensor API standardizes access by formalizing

  • Sensor categories, types and properties
  • Data formats
  • COM interfaces
  • Event mechanisms for asynchronously receiving sensor data

so you’re left with really just three steps:

  1. The entry point into the Sensor API is the Sensor Manager (COM: ISensorManager interface; managed: SensorManger class).   Via the Sensor Manager you can enumerate all of the sensors present on the machine, get a list of a specific class of sensors (say all accelerometers), or a pointer to a specific sensor.  In the unmanaged world, you’ll end up with either a ISensorCollection or a specific ISensor pointer.  With the Windows API Code Pack, static methods on the Sensor Manager classes return either a generic SensorList or an instance of the Sensor class.

    As you might expect, ISensor and the managed Sensor class, allow access to a number of properties of the sensor including type, friendly name, manufacturer, serial number, and so on.  The managed Sensor class includes a SensorChangedEvent (use ISensorEvent.OnStateChanged in the COM API) to enable your code to respond to state changes like ready, not available, error, etc.; these are defined by the SensorState enumeration.

  2. The main event here is DataReportChanged (exposed in COM as ISensorEvent.OnDataUpdated), which fires when the sensor has information to share with the host application.  In the handler for DataReportChanged, you’ll get a SensorReport as one of the event arguments; the report includes a timestamp, the sensor that collected the information, and essentially a property bag of information pertinent to that sensor. 

    Properties are defined in the SensorPropertyKeys class of the Windows API Code Pack, and they map back to the GUIDs defined in the sensors.h header of the Windows 7 SDK.  The managed API Code Pack abstracts these property keys somewhat by providing specific inherited classes for light and motion sensors, so they expose the data relevant to that sensor as named properties.

  3. The only thing you’re left to do then is react to the contents of the data report (or the current state of the sensor) when the DataReportChanged event fires.  Keep in mind, events may be firing quickly, so you may want to incorporate asynchronous patterns within your code to provide the responsiveness a user will expect.

One example you may have seen that highlights the sensor API is the MSDN Reader, a WPF application that is light-aware.  In low light conditions, text tends to shrink, and the color scheme matches the MSDN brand.  In extreme light conditions, the text grows, and the contrast increases to the point that the interface is rendered in black and white – to provide better readability outdoors, for instance.  Below you can see the difference in the rendering based on changing the ambient lighting as recorded through my JM Badge board.

 MSDN Reader - normal lighting

MSDN Reader in normal light conditions

 

MSDN Reader - intense lighting

MSDN Reader in intense lighting conditions 

The code for this is actually pretty simple, and in this case localized to a LightSensorProvider class.  Per the three steps above, the first action is to enumerate the sensors so we can get a handle to the light sensor.  Here the application actually attaches to all of the available light sensors (Line 4) and then ‘averages’ the output, but since I have only one sensor board, it’s not doing much.

  1. try {
  2.     SensorManager.SensorsChanged += OnSensorChanged;
  3.     // get a collection of ambient light sensors
  4.     var sensorCollection = SensorManager.GetSensorsByTypeId<AmbientLightSensor>();
  5.     AddSensorCollection(sensorCollection);
  6. }
  7. catch(SensorPlatformException ex) {
  8.     System.Diagnostics.Debug.WriteLine(ex.Message);
  9. }

 

In Line 5 above, the sensor collection is passed to a method for initialization, which looks like the following; it’s in Line 10 that we’re performing Step 2 of our setup, assigning a handler to the DataReportChanged event.

 

  1. private void AddSensorCollection(IEnumerable<AmbientLightSensor> sensorCollection) {
  2.     foreach(AmbientLightSensor sensor in sensorCollection) {
  3.         AddSensor(sensor);
  4.     }
  5. }
  6.  
  7. private void AddSensor(AmbientLightSensor sensor) {
  8.     if(sensor != null) {
  9.         System.Diagnostics.Debug.WriteLine("AddSensor: " + sensor.FriendlyName);
  10.         sensor.DataReportChanged += OnDataUpdated;
  11.         Sensors.Add(sensor);

 

Here’s what happens in DataReportChanged (delegating to OnDataUpdated), Step 3 of the process.  The data report isn’t actually used here since all that’s needed is to access the sensor’s current intensity at line 8.

 

  1. void OnDataUpdated(Sensor sensor, EventArgs e) {
  2.     var alsReport = sensor.DataReport;
  3.     if(alsReport != null) {
  4.         System.Diagnostics.Debug.WriteLine("Data event timestamp: " + alsReport.TimeStamp.ToString());
  5.         var alsSensor = (AmbientLightSensor)sensor;
  6.  
  7.         // get the illuminance property value
  8.         double lightLux = alsSensor.CurrentLuminousIntensity.Intensity;
  9.  
  10.         // Get the sensor id so we can associate the sensor lux value
  11.         // with a specific sensor id in our collection
  12.         Guid sensorId = (Guid)sensor.SensorId;
  13.         if(sensorId != Guid.Empty) {
  14.             // Set the sensor light value
  15.             SetSensorLightLuxValue(sensorId, lightLux);
  16.         }
  17.     }
  18. }

 

There’s a bit of code I’m leaving out at that point, code that aggregates the multiple light sensors (if there are multiple ones) and handles a timer and threshold values to prevent reacting too much. 

Ultimately though a dependency property on the class comprising the sensor (LightSensorProvider) is set to a lux value.  From that point, the UI update – this is WPF remember – is handled via a simple binding.   On line 4 below is the XAML to address the change in font size according to the value of that dependency property: LightSensorProvider.LuxValue.  The magic of data binding in WPF takes over, and the application adjusts itself based on the ambient lighting.

 

  1. <Window.Resources>
  2.   <Style x:Key="StoryPhotoCredit" TargetType="{x:Type TextBlock}">
  3.     <Setter Property="FontFamily" Value="Arial" />
  4.     <Setter Property="FontSize" Value="{Binding Source={StaticResource LightSensorProvider}, Path=LuxValue, Converter={StaticResource LightLevelToFontSizeConverter}, ConverterParameter=8pt}" />
  5.     <Setter Property="TextAlignment" Value="Right" />
  6.     <Setter Property="Foreground" Value="{DynamicResource Brush_ChromeSolidColor}" />
  7.   </Style>

 

Location API

Now what about the Location API?  As you can see from the diagram at the top of this post, the location API is an abstraction of the lower level sensor API.  It extends the sensor API to provide a couple of new interfaces (in the COM world).  Specifically it provides two specialized data reports, one containing a latitude/longitude pair and the other containing a civic (street) address for the detected location.

COM Interface Purpose
ILocation main sensor class
ILatLongReport report of lat/long location
ICivicAddressReport report of location as street address
IDefaultLocation access to default location
ILocationEvents events to handle change in sensor status and location

 

Note, at this point, a managed interface for the Location API has not yet been released as part of the Windows API Code Pack.  There is, however, an alternative interoperability library at the Windows Sensor and Location Platforms project on Code Gallery.

What’s cool with the Location API is that you can control it as well via JavaScript, and since desktop widgets are merely combinations of JavaScript, CSS, and HTML, you can quickly create some location aware widgets for the desktop, like the weather and places of interest.

Weather widget Places widget

Here’s a few more links to get help you start integrating the Sensor and Location API with your own applications.  Have fun!

Freescale JM Badge board

Sensor Development Kit (for use with Freescale board)

Windows API Code Pack (managed wrapper for Sensor library)

Windows Sensor and Location Platforms (Code Gallery)

Sensor API (MSDN)

Location API (MSDN)

Gavin Gear’s Tech Blog (MSDN Blogs)