So far we covered the Windows 7 Sensor and Location Platform architecture (
I Can Feel You – Using the Windows 7 Sensor Platform), and
Using Sensors in Your Application – Native Part 1. This post introduces the managed API for using sensors. In future posts I’ll continue with the native API.
So far, you have seen C++ and COM examples of the Sensor and Location platform. Now let’s take a look at how managed code developers can use the platform, using the
Windows API Code Pack for the .NET Framework, to discover and work with sensors.
Discovering Sensors Using Managed Code
Follow the same guidelines in managed code as you would in native code: first discover sensors, next check their state and request permissions if needed, and then read data from the sensor. Let’s start by discovering sensors.
The main namespace for sensors in the Windows API Code Pack is
Microsoft.WindowsAPICodePack.Sensors asimplemented in the Microsoft.WindowsAPICodePack.Sensors.dll assembly. This namespace contains the
SensorManager class that manages the sensor devices that are connected to the PC. This class exposes a set of methods that are similar to those of the native
ISensorManager interface. These methods include
GetSensorsByCategoryId,
GetSensorsByTypeId, and
GetSensorsBySensorId, the last of which receives as an input parameter a GUID that either represents a sensor category, type, or single sensor ID. In addition, you can also find the
GetAllSensors method, which returns all the sensors that are connected to the system regardless of type or category, as shown by the following code snippet.
private void PrintAllSensors() { SensorList sensorList = SensorManager.GetAllSensors(); foreach (var sensor in sensorList) { StringBuilder sb = new StringBuilder(); sb.Append("Sensor Information:"); sb.Append(Environment.NewLine); sb.Append(sensor.FriendlyName); sb.Append(Environment.NewLine); sb.Append(sensor.CategoryId); sb.Append(Environment.NewLine); sb.Append(sensor.State); sb.Append(Environment.NewLine); Console.WriteLine(sb.ToString()); } }
Running the above code snippet on my local dev machine yields the following output, showing the sensors installed on my local machine. Note that only the virtual light is
Ready and the rest are showing
AccessDenied indicating that they are not enabled.
Sensor Information: Legacy GPS Driver bfa794e4-f964-4fdb-90f6-51056bfe4b44 AccessDenied Sensor Information: Skyhook Wireless XPS Location Sensor bfa794e4-f964-4fdb-90f6-51056bfe4b44 AccessDenied Sensor Information: Ambient Light Sensor 17a665c0-9063-4216-b202-5c7a255e18ce Ready
Since the Windows API Code Pack includes the strongly typed sensor class
Sensor, it is easy to get a list of sensors and print their various properties. The native API has a Sensor interface through which you work with sensors, but anything beyond that
ISensor Interface requires you to use GUIDs. The Windows API Code Pack provides a list of all the GUIDs that are available in
Sensors.h. The
SensorPropertyKeys and
SensorCategories classes contain the public read-only property of GUID objects that correspond to the same values in the
Sensors.h file. However, this is not the usual or preferred programming model that .NET developers are accustomed to, mainly because the native sensor objects are not strongly typed and you have to use the more generic GUID system to access a sensor’s data. This doesn’t allow you to use all the great features .NET offers, such as data binding, type safety, and properties. Therefore, the
Microsoft.WindowsAPICodePack.Sensors namespace, described in the image to the right, includes several strongly typed sensor classes that allow you to bind to their properties. For example, you can find
AmbientLightSensor, which has one public property,
CurrentLuminousIntensity, which represents the current amount of light (luminosity) detected by the sensors. The namespace also includes the interop layer that wraps the native interface, all the metadata information, and the object model that developers work with.
Please note that the
Microsoft.WindowsAPICodePack.Sensors namespace offers an extensibility model that allows you to create any strongly typed sensor. When this is combined with the extensibility offered by the native API, you can create any type of sensor you want with any data values. You can read more about the Sensor and Location platform extensibility module at the Sensor and Location Platform Web site:
http://www.microsoft.com/whdc/device/sensors/.
With a strongly typed sensor class, the Windows API Code pack can define a .NET Generics version of the
Get methods. For example,
GetSensorsByTypeId, where
S is a type derived from the
Sensor base class. The prototype looks like this:
public static SensorList GetSensorsByTypeId( ) where S: Sensor
When using this function, you need to predefine a specific
SensorList of the desired sensor type, (
AmbientLightSensor, in our example), and then call the method requesting the sensor’s manager to return only
AmbientLightSensor sensors. The following code snippet illustrates this process:
// Strongly typed SensorList of type AmbientLightSensor SensorList alsList = null; try { alsList = SensorManager.GetSensorsByTypeId(); } catch (SensorPlatformException) { //handle error when no sensor device is accessible }
The
SensorManager class contains one event called
SensorChanged, which is equivalent to the native
ISensorManager::OnSensorEnter event. The one main difference between the native and managed code implementations is that the managed code implementation, in addition to receiving an event when a new sensor device is connected to the PC, also generates an event when a sensor gets disconnected. Therefore,
SensorsChangedEventArgs, the arguments passed to the
SensorManager.SensorChanged event handler, includes a
SensorAvailabilityChange member that defines the type of change for each sensor, which can be
Addition for new sensor devices and
Removal when sensors are disconnected from the PC.
SensorManager_SensorsChanged is the function that handles the
SensorsChanged event in our application, and it looks like this:
void SensorManager_SensorsChanged( SensorsChangedEventArgs change ) { // The SensorsChanged event comes in on a non-UI thread. // Whip up an anonymous delegate to handle the UI update. BeginInvoke( new MethodInvoker( delegate { PopulatePanel( ); } ) ); }
The
SensorsChanged event is dispatched on a different thread than the application’s main form (UI) thread. Windows Forms does not allow you to update the UI of the application from a non-UI thread. Therefore, for any Windows application with a window-based UI, we highly recommend that you use a different thread than the main UI thread to execute long computations or any I/O-bound communication (as we do in our example of how to synchronously read sensor data). Therefore, to properly handle non-UI-thread UI updates, you should use
BeginInvoke to execute the specified delegate asynchronously on the thread upon which the form’s underlying handle was created. This is also true for any WPF application. The
PopulatePanel method iterates through all the ambient light sensors and updates the application UI as it verifies the sensor state and reads its data. We'll cover this in future posts.
More...