Introduction
This guide provides developers with an overview of the Microsoft Windows* 8 sensors application programming interfaces (APIs) for Desktop and Windows UI style applications with a specific focus on the various sensor capabilities available in Windows* 8 Desktop mode. We summarize the APIs that enable creating interactive applications by including some of the common sensors such as accelerometers, magnetometers, and gyroscopes with Windows* 8.
Content
Programming Choices for Windows* 8
Developers have multiple API choices to program sensors on Win8. The new touch-friendly app environment, called "Windows UI apps" is shown on the left in Figure 1. The only API library that Windows UI apps can use is the completely new one called WinRT. The WinRT sensor API represents a portion of the overall WinRT library. For more details, please refer to: http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.sensors.aspx
Traditional Win Forms, or MFC-style apps, shown on the right, are now called "Desktop apps" because they run in the Desktop Windows Manager environment. Desktop apps can either use the native Win32/COM API or a .NET-style API.
In both cases, these APIs go through a Windows middleware component called the Windows Sensor Framework. The Windows Sensor Framework defines the Sensor Object Model. The different APIs "bind" to that object model in slightly different ways.
Figure 1: Windows UI and Desktop Sensor frameworks in Windows* 8
Differences in the Desktop and Windows UI application development will be discussed later in this document. For brevity, we will consider only Desktop app development. For Windows UI app development, please refer to http://msdn.microsoft.com/library/windows/apps/br211369
Sensors
There are many kinds of sensors, but we are interested in the ones required for Windows* 8, namely accelerometers, gyroscopes, ambient light sensors, compass and GPS. Windows* 8 represents the physical sensors with object-oriented abstractions. To manipulate the sensors, programmers use APIs to interact with the objects.
You may have noticed that there are more objects shown below (Figure 2) than actual hardware. Windows defines some "logical sensor" objects by combining information from multiple physical sensors. This is called "Sensor Fusion."
Figure 2: Different sensors supported on Windows* 8
Sensor Fusion
The physical sensor chips have some inherent natural limitations. For example:
- Accelerometers measure linear acceleration, which is a measurement of the combined relative motion and the force of Earth’s gravity. If you want to know the computer’s tilt, you’ll have to do some mathematical calculations.
- Magnetometers measure the strength of magnetic fields, which indicate the location of the Earth’s Magnetic North Pole.
These measurements are subject to an inherent drift problem, which can be corrected by using raw data from the Gyro. Both measurements are (scaled) dependent upon the tilt of the computer from level with respect to the Earth’s surface.
If you really want the computer’s heading with respect to the Earth’s True North Pole (Magnetic North Pole is in a different position and moves over time), you need to correct for that.
Sensor Fusion(Figure 3) is obtaining raw data from multiple physical sensors, especially the Accelerometer, Gyro, and Magnetometer, performing mathematical calculations to correct for natural sensor limitations, computing more human-usable data, and representing those as logical sensor abstractions. The application developer has to implement the necessary transformations required to translate physical sensor data to the abstract sensor data. If your system design has a SensorHub, the fusion operations will take place inside the microcontroller firmware. If your system design does not have a SensorHub, the fusion operations must be done inside one-or-more device drivers that the IHVs and/or OEMs provide.
Figure 3: Sensor fusion by combining output from multiple sensors
Identifying Sensors
To manipulate a sensor, you need a system to identify and refer to it. The Windows Sensor Framework defines a number of categories that sensors are grouped into. It also defines a large number of specific sensor types. Table 1 lists some of the sensors available for your Desktop application.
Biometric |
Electrical |
Environmental |
Light |
Location |
Mechanical |
Motion |
Orientation |
Scanner |
Human Presence |
Capacitance |
Atmospheric Pressure |
Ambient Light |
Broadcast |
Boolean Switch |
Accelerometer 1D |
Compass 1D |
Barcode |
Human Proximity* |
Current |
Humidity |
|
Gps* |
Boolean Switch Array |
Accelerometer 2D |
Compass 2D |
Rfid |
Touch |
Electrical Power |
Temperature |
|
Static |
Force |
Accelerometer 3D |
Compass 3D |
|
|
Inductance |
Wind Direction |
|
|
Multivalue Switch |
Gyrometer 1D |
Device Orientation* |
|
|
Potentio-meter |
Wind Speed |
|
|
Pressure |
Gyrometer 2D |
Distance 1D |
|
|
Resistance |
|
|
|
Strain |
Gyrometer 3D |
Distance 2D |
|
|
Voltage |
|
|
|
Weight |
Motion Detector |
Distance 3D |
|
|
|
|
|
|
|
Speedometer |
Inclinometer 1D |
|
|
|
|
|
|
|
|
Inclinometer 2D |
|
|
|
|
|
|
|
|
Inclinometer 3D* |
|
Table 1: Sensor types and categories
The sensor types required by Windows* 8 are shown in bold* font:
- Accelerometer, Gyro, Compass, and Ambient Light are the required "real/physical" sensors
- Device Orientation and Inclinometer are the required "virtual/fusion" sensors (note that the Compass also includes fusion-enhanced/tilt-compensated data)
- GPS is a required sensor if you have a WWAN radio, otherwise GPS is optional
- Human Proximity is an oft-mentioned possible addition to the required list, but, for now, it’s not required.
The names of the categories and types shown in Table 1 are nice, human-readable forms. However, for programming, you’ll need to know the programming constants for each type of sensor. All of these constants are actually just numbers called GUIDs (Globally Unique IDs). Below, in Table 2, is a sample of some of the sensor categories and types, the names of the constants for Win32/COM and .NET, and their underlying GUID values.
Identifier |
Constant (Win32/COM) |
Constant (.NET) |
GUID |
Category "All" |
SENSOR_CATEGORY_ALL |
SensorCategories.SensorCategoryAll |
{C317C286-C468-4288-9975-D4C4587C442C} |
Category Biometric |
SENSOR_CATEGORY_BIOMETRIC |
SensorCategories.SensorCategoryBiometric |
{CA19690F-A2C7-477D-A99E-99EC6E2B5648} |
Category Electrical |
SENSOR_CATEGORY_ELECTRICAL |
SensorCategories.SensorCategoryElectrical |
{FB73FCD8-FC4A-483C-AC58-27B691C6BEFF} |
Category Environmental |
SENSOR_CATEGORY_ENVIRONMENTAL |
SensorCategories.SensorCategoryEnvironmental |
{323439AA-7F66-492B-BA0C-73E9AA0A65D5} |
Category Light |
SENSOR_CATEGORY_LIGHT |
SensorCategories.SensorCategoryLight |
{17A665C0-9063-4216-B202-5C7A255E18CE} |
Category Location |
SENSOR_CATEGORY_LOCATION |
SensorCategories.SensorCategoryLocation |
{BFA794E4-F964-4FDB-90F6-51056BFE4B44} |
Category Mechanical |
SENSOR_CATEGORY_MECHANICAL |
SensorCategories.SensorCategoryMechanical |
{8D131D68-8EF7-4656-80B5-CCCBD93791C5} |
Category Motion |
SENSOR_CATEGORY_MOTION |
SensorCategories.SensorCategoryMotion |
{CD09DAF1-3B2E-4C3D-B598-B5E5FF93FD46} |
Category Orientation |
SENSOR_CATEGORY_ORIENTATION |
SensorCategories.SensorCategoryOrientation |
{9E6C04B6-96FE-4954-B726-68682A473F69} |
Category Scanner |
SENSOR_CATEGORY_SCANNER |
SensorCategories.SensorCategoryScanner |
{B000E77E-F5B5-420F-815D-0270ª726F270} |
Type HumanProximity |
SENSOR_TYPE_HUMAN_PROXIMITY |
SensorTypes.SensorTypeHumanProximity |
{5220DAE9-3179-4430-9F90-06266D2A34DE} |
Type AmbientLight |
SENSOR_TYPE_AMBIENT_LIGHT |
SensorTypes.SensorTypeAmbientLight |
{97F115C8-599A-4153-8894-D2D12899918A} |
Type Gps |
SENSOR_TYPE_LOCATION_GPS |
SensorTypes.SensorTypeLocationGps |
{{ED4CA589-327A-4FF9-A560-91DA4B48275E} |
Type Accelerometer3D |
SENSOR_TYPE_ACCELEROMETER_3D |
SensorTypes.SensorTypeAccelerometer3D |
{C2FB0F5F-E2D2-4C78-BCD0-352A9582819D} |
Type Gyrometer3D |
SENSOR_TYPE_GYROMETER_3D |
SensorTypes.SensorTypeGyrometer3D |
{09485F5A-759E-42C2-BD4B-A349B75C8643} |
Type Compass3D |
SENSOR_TYPE_COMPASS_3D |
SensorTypes.SensorTypeCompass3D |
{76B5CE0D-17DD-414D-93A1-E127F40BDF6E} |
Type Compass3D |
SENSOR_TYPE_COMPASS_3D |
SensorTypes.SensorTypeCompass3D |
{76B5CE0D-17DD-414D-93A1-E127F40BDF6E} |
Type DeviceOrientation |
SENSOR_TYPE_DEVICE_ORIENTATION |
SensorTypes.SensorTypeDeviceOrientation |
{CDB5D8F7-3CFD-41C8-8542-CCE622CF5D6E} |
Type Inclinometer3D |
SENSOR_TYPE_INCLINOMETER_3D |
SensorTypes.SensorTypeInclinometer3D |
{B84919FB-EA85-4976-8444-6F6F5C6D31DB} |
Table 2: Constants and unique Globally Unique IDs (GUIDs) for some common sensors
These are the most commonly used GUIDs—there are many more you can explore. At first you might think that the GUIDs are silly and tedious, but there is one good reason for using them: extensibility. Since the APIs don’t care about the actual sensor names (they just pass GUIDs around), it is possible for vendors to invent new GUIDs for "value add" sensors.
Generating New GUIDs
Microsoft provides a tool in Visual Studio* that allows anyone to generate new GUIDs. Figure 4 shows a screenshot from Visual Studio for doing this. All the vendor has to do is publish them, and new functionality can be exposed without the need to change the Microsoft APIs or any operating system code at all.
Figure 4: Defining new GUIDs for value add sensors
Using Sensor Manager Object
Ask by Type
Your app can ask for a specific type of sensor, such as Gyrometer3D. The Sensor Manager consults the list of sensor hardware present on the computer and returns a collection of matching objects bound to that hardware. Although the Sensor Collection may have 0, 1, or more objects, it usually has only one. Below is a C++ code sample illustrating the use of the Sensor Manager object’s GetSensorsByType
method to search for 3-axis Gyros and return them in a Sensor Collection. Note that you have to ::CoCreateInstance()
the Sensor Manager Object first.
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>
ISensorManager* pSensorManager = NULL;
HRESULT hr = ::CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pSensorManager));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to CoCreateInstance() the SensorManager."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
ISensorCollection* pSensorCollection = NULL;
hr = pSensorManager->GetSensorsByType(SENSOR_TYPE_GYROMETER_3D, &pSensorCollection);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to find any Gyros on the computer."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
Ask by Category
Your app can ask for sensors by category, such as all motion sensors. The Sensor Manager consults the list of sensor hardware on the computer and returns a collection of motion objects bound to that hardware. The SensorCollection
may have 0, 1, or more objects in it. On most computers, the collection will have two motion objects: Accelerometer3D and Gyrometer3D.
The C++ code sample below illustrates the use of the Sensor Manager object’s GetSensorsByCategory
method to search for motion sensors and return them in a sensor collection.
#include <initguid.h>
#include <sensorsapi.h>
#include <sensors.h>
ISensorManager* pSensorManager = NULL;
HRESULT hr = ::CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pSensorManager));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to CoCreateInstance() the SensorManager."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
ISensorCollection* pSensorCollection = NULL;
hr = pSensorManager->GetSensorsByCategory(SENSOR_CATEGORY_MOTION, &pSensorCollection);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to find any sensors on the computer."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
Ask by Category "All"
In practice, the most efficient way is for your app to ask for all of the sensors on the computer. The Sensor Manager consults the list of sensor hardware on the computer and returns a collection of all the objects bound to that hardware. The Sensor Collection may have 0, 1, or more objects in it. On most computers, the collection will have seven or more objects.
C++ does not have a GetAllSensors
call, so you must use GetSensorsByCategory(SENSOR_CATEGORY_ALL, …)
instead as shown in the sample code below.
#include <initguid.h>
#include <sensorsapi.h>
#include <sensors.h>
ISensorManager* pSensorManager = NULL;
HRESULT hr = ::CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pSensorManager));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to CoCreateInstance() the SensorManager."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
ISensorCollection* pSensorCollection = NULL;
hr = pSensorManager->GetSensorsByCategory(SENSOR_CATEGORY_ALL, &pSensorCollection);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to find any Motion sensors on the computer."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
Sensor Life Cycle – Enter and Leave Events
On Windows, as with most hardware devices, sensors are treated as Plug and Play devices. At first you might say, "The sensors are hard-wired on the computer’s motherboard, why do we have to worry about Plug and Play if they’ll never be plugged in or unplugged?" There are a few different scenarios where it occurs:
- It is possible to have USB-based sensors external to the system and plugged into a USB port.
- It is conceivable to have sensors that are attached by an unreliable wireless interface (such as Bluetooth) or wired interface (such as Ethernet), where connects and disconnects happen.
- If and when Windows Update upgrades the device driver for the sensors, they appear to disconnect and then reconnect.
- When Windows shuts down (to S4 or S5), the sensors appear to disconnect.
In the context of sensors, a Plug and Play connect is called an Enter event, and disconnect is called a Leave event. Resilient apps need to be able to handle both.
Enter Event Callback
Your app may already be running at the time a sensor is plugged in. When this happens, the Sensor Manager reports the sensor Enter event. Note: if the sensors are already plugged in when your app starts running, you will not get Enter events for those sensors. In C++/COM, you must use the SetEventSink
method to hook the callback. The callback cannot simply be a function, it must be an entire class that inherits from ISensorManagerEvents
, and also implements IUnknown
. The ISensorManagerEvents
interface must have callback function implementations for:
STDMETHODIMP OnSensorEnter(ISensor *pSensor, SensorState state);
pSensorManagerEventClass = new SensorManagerEventSink(); HRESULT hr = pSensorManagerEventClass->QueryInterface(IID_PPV_ARGS(&pSensorManagerEvents));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot query ISensorManagerEvents interface for our callback class."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
hr = pSensorManager->SetEventSink(pSensorManagerEvents);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot SetEventSink on SensorManager to our callback class."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
Code: Hook Callback for Enter event
Below is the C++/COM equivalent of the Enter callback. You would normally perform all the initialization steps from your main loop in this function. In fact, it is more efficient to refactor your code so your main loop merely calls OnSensorEnter
to simulate an Enter event.
STDMETHODIMP SensorManagerEventSink::OnSensorEnter(ISensor *pSensor, SensorState state)
{
VARIANT_BOOL bSupported = VARIANT_FALSE;
HRESULT hr = pSensor->SupportsDataField(SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX, &bSupported);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot check SupportsDataField for SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONINFORMATION);
return hr;
}
if (bSupported == VARIANT_FALSE)
{
return -1;
}
ISensor *pAls = pSensor; ::MessageBox(NULL, _T("Ambient Light Sensor has entered."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONINFORMATION);
.
.
.
return hr;
}
Code: Callback for Enter Event
Leave Event
The individual sensor reports when the Leave event happens (not the Sensor Manager). This code is actually the same as the previous hook callback for an Enter event.
SensorEventSink* pSensorEventClass = new SensorEventSink(); ISensorEvents* pSensorEvents = NULL;
HRESULT hr = pSensorEventClass->QueryInterface(IID_PPV_ARGS(&pSensorEvents));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot query ISensorEvents interface for our callback class."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
hr = pSensor->SetEventSink(pSensorEvents); if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot SetEventSink on the Sensor to our callback class."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
Code: Hook Callback for Leave event
The OnLeave
event handler receives the ID of the leaving sensor as an argument.
STDMETHODIMP SensorEventSink::OnLeave(REFSENSOR_ID sensorID)
{
HRESULT hr = S_OK;
::MessageBox(NULL, _T("Ambient Light Sensor has left."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONINFORMATION);
return hr;
}
Code: Callback for Leave event
Picking Sensors for Your App
We care about sensors because of what they tell us. Different types of sensors tell us different things. Microsoft calls these pieces of information Data Fields, and they are grouped together in a SensorDataReport. Your computer may (potentially) have more than one type of sensor that can tell your app the information you care about. Your app probably doesn’t care which sensor it gets the information from, so long as it can get it.
Table 3 shows the constant names for the most commonly used Data Fields for Win32/COM and.NET. Just like sensor identifiers, these constants are just human-readable names for what are really just big numbers underneath. This provides for extensibility of Data Fields beyond the "well known" ones Microsoft has pre-defined. There are many other "well known" IDs for you to explore.
Constant (Win32/COM) |
Constant (.NET) |
PROPERTYKEY (GUID,PID) |
SENSOR_DATA_TYPE_TIMESTAMP |
SensorDataTypeTimestamp |
{DB5E0CF2-CF1F-4C18-B46C-D86011D62150},2 |
SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX |
SensorDataTypeLightLevelLux |
{E4C77CE2-DCB7-46E9-8439-4FEC548833A6},2 |
SENSOR_DATA_TYPE_ACCELERATION_X_G |
SensorDataTypeAccelerationXG |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},2 |
SENSOR_DATA_TYPE_ACCELERATION_Y_G |
SensorDataTypeAccelerationYG |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},3 |
SENSOR_DATA_TYPE_ACCELERATION_Z_G |
SensorDataTypeAccelerationZG |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},4 |
SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DEG
REES_PER_SECOND |
SensorDataTypeAngularVelocityXDegreesPerSecond |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},10 |
SENSOR_DATA_TYPE_ANGULAR_VELOCITY_X_DE
GREES_PER_SECOND |
SensorDataTypeAngularVelocityXDegreesPerSecond |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},10 |
SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DE
GREES_PER_SECOND |
SensorDataTypeAngularVelocityYDegreesPerSecond |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},11 |
SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DE
GREES_PER_SECOND |
SensorDataTypeAngularVelocityYDegreesPerSecond |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},11 |
SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DE
GREES_PER_SECOND |
SensorDataTypeAngularVelocityZDegreesPerSecond |
{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},12 |
SENSOR_DATA_TYPE_TILT_X_DEGREES |
SensorDataTypeTiltXDegrees |
{1637D8A2-4248-4275-865D-558DE84AEDFD},2 |
SENSOR_DATA_TYPE_TILT_Y_DEGREES |
SensorDataTypeTiltYDegrees |
{1637D8A2-4248-4275-865D-558DE84AEDFD},3 |
SENSOR_DATA_TYPE_TILT_Z_DEGREES |
SensorDataTypeTiltZDegrees |
{1637D8A2-4248-4275-865D-558DE84AEDFD},4 |
SENSOR_DATA_TYPE_MAGNETIC_HEADING_COM
PENSATED_MAGNETIC_NORTH_DEGREES |
SensorDataTypeMagneticHeadingCompensated
TrueNorthDegrees |
{1637D8A2-4248-4275-865D-558DE84AEDFD},11 |
SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH
_X_MILLIGAUSS |
SensorDataTypeMagneticFieldStrengthXMilligauss |
{1637D8A2-4248-4275-865D-558DE84AEDFD},19 |
SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH
_Y_MILLIGAUSS |
SensorDataTypeMagneticFieldStrengthYMilligauss |
{1637D8A2-4248-4275-865D-558DE84AEDFD},20 |
SENSOR_DATA_TYPE_MAGNETIC_FIELD_STRENGTH
_Z_MILLIGAUSS |
SensorDataTypeMagneticFieldStrengthZMilligauss |
{1637D8A2-4248-4275-865D-558DE84AEDFD},21 |
SENSOR_DATA_TYPE_QUATERNION |
SensorDataTypeQuaternion |
{1637D8A2-4248-4275-865D-558DE84AEDFD},17 |
SENSOR_DATA_TYPE_QUATERNION |
SensorDataTypeQuaternion |
{1637D8A2-4248-4275-865D-558DE84AEDFD},17 |
SENSOR_DATA_TYPE_ROTATION_MATRIX |
SensorDataTypeRotationMatrix |
{1637D8A2-4248-4275-865D-558DE84AEDFD},16 |
SENSOR_DATA_TYPE_LATITUDE_DEGREES |
SensorDataTypeLatitudeDegrees |
{055C74D8-CA6F-47D6-95C6-1ED3637A0FF4},2 |
SENSOR_DATA_TYPE_LONGITUDE_DEGREES |
SensorDataTypeLongitudeDegrees |
{055C74D8-CA6F-47D6-95C6-1ED3637A0FF4},3 |
SENSOR_DATA_TYPE_ALTITUDE_ELLIPSOID_METERS |
SensorDataTypeAltitudeEllipsoidMeters |
{055C74D8-CA6F-47D6-95C6-1ED3637A0FF4},5 |
Table 3: Data Field identifier constants
One thing that makes Data Field identifiers different from sensor IDs is the use of a data type called PROPERTYKEY. A PROPERTYKEY consists of a GUID (similar to what sensors have), plus an extra number called a "PID" (property ID). You might notice that the GUID part of a PROPERTYKEY is common for sensors that are in the same category. Data Fields have a native data type for all of their values, such as Boolean, unsigned char, int, float, double, and so on.
In Win32/COM, the value of a Data Field is stored in a polymorphic data type called PROPVARIANT. In .NET, there is a CLR (Common Language Runtime) data type called "object" that does the same thing. You have to query and/or typecast the polymorphic data type to the "expected"/"documented" data type.
Use the SupportsDataField()
method of the sensor to check the sensors for the Data Fields of interest. This is the most common programming idiom that we use to select sensors. Depending on the usage model of your app, you may only need a subset of the Data Fields, not all of them. Pick the sensors you want based on whether they support the Data Fields you need. Note that you also need to use type casting to assign the sub-classed member variables from the base class sensor.
ISensor* m_pAls;
ISensor* m_pAccel;
ISensor* m_pTilt;
ULONG ulCount = 0;
HRESULT hr = pSensorCollection->GetCount(&ulCount);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to get count of sensors on the computer."), _T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
for (int i = 0; i < (int)ulCount; i++)
{
hr = pSensorCollection->GetAt(i, &pSensor);
if (SUCCEEDED(hr))
{
VARIANT_BOOL bSupported = VARIANT_FALSE;
hr = pSensor->SupportsDataField(SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX, &bSupported);
if (SUCCEEDED(hr) && (bSupported == VARIANT_TRUE)) m_pAls = pSensor;
hr = pSensor->SupportsDataField(SENSOR_DATA_TYPE_ACCELERATION_Z_G, &bSupported);
if (SUCCEEDED(hr) && (bSupported == VARIANT_TRUE)) m_pAccel = pSensor;
hr = pSensor->SupportsDataField(SENSOR_DATA_TYPE_TILT_Z_DEGREES, &bSupported);
if (SUCCEEDED(hr) && (bSupported == VARIANT_TRUE)) m_pTilt = pSensor;
.
.
.
}
}
Code: Use the SupportsDataField() method of the sensor to check for supported data field
Sensor Properties
In addition to Data Fields, sensors have Properties that can be used for identification and configuration. Table 4 shows the most commonly-used Properties. Just like Data Fields, Properties have constant names used by Win32/COM and .NET, and those constants are really PROPERTYKEY numbers underneath. Properties are extensible by vendors and also have PROPVARIANT polymorphic data types. Unlike Data Fields that are read-only, Properties have the ability to be Read/Write. It is up to the individual sensor’s discretion as to whether or not it rejects Write attempts. As an app developer, you need to perform write-read-verify because no exception is thrown when a write attempt fails.
Identification(Win32/COM) |
Identification(.NET) |
PROPERTYKEY (GUID,PID) |
SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID |
SensorID |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},5 |
WPD_FUNCTIONAL_OBJECT_CATEGORY |
CategoryID |
{8F052D93-ABCA-4FC5-A5AC-B01DF4DBE598},2 |
SENSOR_PROPERTY_TYPE |
TypeID |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},2 |
SENSOR_PROPERTY_STATE |
State |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},3 |
SENSOR_PROPERTY_MANUFACTURER |
SensorManufacturer |
7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},6 |
SENSOR_PROPERTY_MODEL |
SensorModel |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},7 |
SENSOR_PROPERTY_SERIAL_NUMBER |
SensorSerialNumber |
(7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},8 |
SENSOR_PROPERTY_FRIENDLY_NAME |
FriendlyName |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},9 |
SENSOR_PROPERTY_DESCRIPTION |
SensorDescription |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},10 |
SENSOR_PROPERTY_MIN_REPORT_INTERVAL |
MinReportInterval |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},12 |
SENSOR_PROPERTY_CONNECTION_TYPE |
SensorConnectionType |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},11 |
SENSOR_PROPERTY_DEVICE_ID |
SensorDevicePath |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},15 |
SENSOR_PROPERTY_RANGE_MAXIMUM |
SensorRangeMaximum |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},21 |
SENSOR_PROPERTY_RANGE_MINIMUM |
SensorRangeMinimum |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},20 |
SENSOR_PROPERTY_ACCURACY |
SensorAccuracy |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},17 |
SENSOR_PROPERTY_RESOLUTION |
SensorResolution |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},18 |
Configuration(Win32/COM) |
Configuration(.NET) |
PROPERTYKEY (GUID,PID) |
SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL |
ReportInterval |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},13 |
SENSOR_PROPERTY_CHANGE_SENSITIVITY |
ChangeSensitivity |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},14 |
SENSOR_PROPERTY_REPORTING_STATE |
ReportingState |
{7F8383EC-D3EC-495C-A8CF-B8BBE85C2920},27 |
Table 4: Commonly used sensor Properties and PIDs
Setting Sensor Sensitivity
The sensitivity setting is probably the most useful Property of a sensor. It can be used to assign a threshold that controls or filters the number of SensorDataReports sent to the host computer. In this way, traffic can be reduced: only send up those DataUpdated events that are truly worthy of bothering the host CPU. The way Microsoft has defined the data type of this Sensitivity property is a little unusual. It is a container type called IPortableDeviceValues in Win32/COM and SensorPortableDeviceValues
in .NET. This container holds a collection of tuples, each of which is a Data Field PROPERTYKEY followed by the sensitivity value for that Data Field. The sensitivity always uses the same units of measure and data type as the matching Data Field.
IPortableDeviceValues* pInSensitivityValues;
hr = ::CoCreateInstance(CLSID_PortableDeviceValues, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pInSensitivityValues));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to CoCreateInstance() a PortableDeviceValues collection."), _T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_R8; pv.dblVal = (double)0.1;
pInSensitivityValues->SetValue(SENSOR_DATA_TYPE_ACCELERATION_X_G, &pv);
pInSensitivityValues->SetValue(SENSOR_DATA_TYPE_ACCELERATION_Y_G, &pv);
pInSensitivityValues->SetValue(SENSOR_DATA_TYPE_ACCELERATION_Z_G, &pv);
IPortableDeviceValues* pInValues;
hr = ::CoCreateInstance(CLSID_PortableDeviceValues, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pInValues));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to CoCreateInstance() a PortableDeviceValues collection."), _T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
pInValues->SetIPortableDeviceValuesValue(SENSOR_PROPERTY_CHANGE_SENSITIVITY, pInSensitivityValues);
IPortableDeviceValues* pOutValues;
hr = pAls->SetProperties(pInValues, &pOutValues);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to SetProperties() for Sensitivity."), _T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
DWORD dwCount = 0;
hr = pOutValues->GetCount(&dwCount);
if (FAILED(hr) || (dwCount > 0))
{
::MessageBox(NULL, _T("Failed to set one-or-more Sensitivity values."), _T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
PropVariantClear(&pv);
Requesting Permissions for Sensors
The end user may consider the information provided by sensors to be sensitive, i.e., Personally Identifiable Information (PII). Data Fields such as the computer’s location (e.g., latitude and longitude), could be used to track the user. Therefore, before use, Windows forces apps to get end-user permission to access the sensor. Use the State property of the sensor and the RequestPermissions()
method of the SensorManager if needed.
The RequestPermissions()
method takes an array of sensors as an argument, so you can ask for permission for more than one sensor at a time if you want. The C++/COM code is shown below. Note that you must provide an (ISensorCollection *) argument to RequestPermissions()
.
SensorState state = SENSOR_STATE_ERROR;
HRESULT hr = pSensor->GetState(&state);
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to get sensor state."), _T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
if (state == SENSOR_STATE_ACCESS_DENIED)
{
ISensorCollection *pSensorCollection = NULL;
hr = ::CoCreateInstance(CLSID_SensorCollection, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSensorCollection));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Unable to CoCreateInstance() a SensorCollection."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
pSensorCollection->Clear();
pSensorCollection->Add(pAls); hr = m_pSensorManager->RequestPermissions(NULL, pSensorCollection, TRUE);
if (FAILED(hr))
{
::MessageBox(NULL, _T("No permission to access sensors that we care about."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
}
Sensor Data Update
Sensors report data by throwing an event called a DataUpdated event. The actual Data Fields are packaged inside a SensorDataReport, which is passed to any attached DataUpdated event handlers. Your app can obtain the SensorDataReport by hooking a callback handler to the sensor’s DataUpdated event. The event occurs in a Windows Sensor Framework thread, which is a different thread than the message-pump thread used to update your app’s GUI. Therefore, you will need to do a "hand-off" of the SensorDataReport from the event handler (Als_DataUpdate) to a separate handler (Als_UpdateGUI) that can execute on the context of the GUI thread. In .NET, such a handler is called a delegate function.
The example below shows preparation of the delegate function. In C++/COM, you must use the SetEventSink method to hook the callback. The callback cannot simply be a function; it must be an entire class that inherits from ISensorEvents and also implements IUnknown. The ISensorEvents interface must have callback function implementations for:
STDMETHODIMP OnEvent(ISensor *pSensor, REFGUID eventID, IPortableDeviceValues *pEventData);
STDMETHODIMP OnDataUpdated(ISensor *pSensor, ISensorDataReport *pNewData);
STDMETHODIMP OnLeave(REFSENSOR_ID sensorID);
STDMETHODIMP OnStateChanged(ISensor* pSensor, SensorState state);
SensorEventSink* pSensorEventClass = new SensorEventSink(); ISensorEvents* pSensorEvents = NULL;
HRESULT hr = pSensorEventClass->QueryInterface(IID_PPV_ARGS(&pSensorEvents));
if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot query ISensorEvents interface for our callback class."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
hr = pSensor->SetEventSink(pSensorEvents); if (FAILED(hr))
{
::MessageBox(NULL, _T("Cannot SetEventSink on the Sensor to our callback class."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
return -1;
}
Code: Set a COM Event Sink for the sensor
The DataUpdated event handler receives the SensorDataReport (and the sensor that initiated the event) as arguments. It calls the Invoke()
method of the form to post those items to the delegate function. The GUI thread runs the delegate function posted to its Invoke queue and passes the arguments to it. The delegate function casts the data type of the SensorDataReport to the expected subclass, gaining access to its Data Fields. The Data Fields are extracted using the GetDataField()
method of the SensorDataReport object. Each of the Data Fields has to be typecast to their "expected"/"documented" data types (from the generic/polymorphic data type returned by the GetDataField()
method). The app can then format and display the data in the GUI.
The OnDataUpdated
event handler receives the SensorDataReport (and the sensor what initiated the event) as arguments. The Data Fields are extracted using the GetSensorValue()
method of the SensorDataReport object. Each of the Data Fields needs to have their PROPVARIANT checked for their "expected"/"documented" data types. The app can then format and display the data in the GUI. It is not necessary to use the equivalent of a C# delegate. This is because all C++ GUI functions (such as ::SetWindowText()
shown here) use Windows message-passing to post the GUI update to the GUI thread / message-loop (the WndProc of your main window or dialog box).
STDMETHODIMP SensorEventSink::OnDataUpdated(ISensor *pSensor, ISensorDataReport *pNewData)
{
HRESULT hr = S_OK;
if ((NULL == pNewData) || (NULL == pSensor)) return E_INVALIDARG;
float fLux = 0.0f;
PROPVARIANT pv = {};
hr = pNewData->GetSensorValue(SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX, &pv);
if (SUCCEEDED(hr))
{
if (pv.vt == VT_R4) {
fLux = pv.fltVal;
wchar_t *pwszLabelText = (wchar_t *)malloc(64 * sizeof(wchar_t));
swprintf_s(pwszLabelText, 64, L"Illuminance Lux: %.1f", fLux);
BOOL bSuccess = ::SetWindowText(m_hwndLabel, (LPCWSTR)pwszLabelText);
if (bSuccess == FALSE)
{
::MessageBox(NULL, _T("Cannot SetWindowText on label control."),
_T("Sensor C++ Sample"), MB_OK | MB_ICONERROR);
}
free(pwszLabelText);
}
}
PropVariantClear(&pv);
return hr;
}
You can just reference properties of the SensorDataReport object to extract Data Fields from the SensorDataReport. This only works for the .NET API (in the Win32/COM API, you must use the GetDataField
method), and for "well known" or "expected" Data Fields of that particular SensorDataReport subclass. It is possible (using something called "Dynamic Data Fields") for the underlying driver/firmware to "piggyback" any "extended/unexpected" Data Fields inside SensorDataReports. To extract those, you must use the GetDataField method.
Using Sensors in Windows UI Apps
Unlike the Desktop mode, Windows UI/WinRT Sensor API follows a common template for each of the sensors:
- There is usually a single event called
ReadingChanged
that calls the callback with an xxxReadingChangedEventArgs containing a Reading object holding the actual data. (the accelerometer is an exception; it also has a Shaken event).
- The hardware-bound instance of the sensor class is retrieved using the
GetDefault()
method.
- Polling can be done with the
GetCurrentReading()
method.
Windows UI apps are typically written either in JavaScript* or in C#. There are different language-bindings to the API, which result in a slightly different capitalization appearance in the API names and a slightly different way that events are handled. The simplified API is easier to use, and the pros and cons are listed in Table 5.
Feature |
Pros |
Cons |
SensorManager |
There is no SensorManager to deal with. Apps use the GetDefault() method to get an instance of the sensor class. |
- It is not possible to search for arbitrary sensor instances. If more than one of a particular sensor type exists on a computer, you will only see the "first" one.
- It is not possible to search for arbitrary sensor types or categories by GUID. Vendor value-add extensions are inaccessible.
|
Events |
Apps only worry about the DataUpdated event.. |
- Apps have no access to Enter, Leave, StatusChanged, or arbitrary event types. Vendor value-add extensions are inaccessible.
|
Sensor properties |
Apps only worry about the ReportInterval property. |
- Apps have no access to the other properties, including the most useful one: Sensitivity.
- Other than manipulating the ReportInterval property, there is no way for Windows UI apps to tune or control the flow rate of Data Reports.
- Apps cannot access arbitrary Properties by PROPERTYKEY. Vendor value-add extensions are inaccessible.
|
Data Report properties |
Apps only worry about a few, pre-defined Data Fields unique to each sensor. |
- Apps have no access to other Data Fields. If sensors "piggy-back" additional well-known Data Fields in a Data Report beyond what Windows UI apps expect, the Data Fields are inaccessible.
- Apps cannot access arbitrary Data Fields by PROPERTYKEY. Vendor value-add extensions are inaccessible.
- Apps have no way to query at run-time what Data Fields a sensor supports. It can only assume what the API pre-defines.
|
Table 5: Sensor APIs for Windows UI Apps, pros and cons
Summary
Windows* 8 APIs provide developers an opportunity to take advantage of sensors available on different platforms under both the traditional Desktop mode and the new Windows UI app interface. In this document, we have presented an overview of the sensor APIs available to developers looking to create applications with Windows* 8, focusing on the APIs and code samples for Desktop mode apps.
Appendix
Coordinate System for Different Form Factors
The Windows API reports X, Y, and Z axes in a manner that is compatible with the HTML5 standard (and Android*). It is also called the "ENU" system because X faces virtual "East", Y faces virtual "North", and Z faces "Up."
To figure out the direction of rotation, use the "Right Hand Rule":
* Point the thumb of your right hand in the direction of one of the axes.
* Positive angle rotation around that axis will follow the curve of your fingers.
These are the X, Y, and Z axes for a tablet form-factor PC, or phone (left) and for a clamshell PC (right). For more esoteric form factors (for example, a clamshell that is convertible into a tablet), the "standard" orientation is when it is in the TABLET state.
If you intend to develop a navigation application (e.g., 3D space game), you need to convert from "ENU" systems in your program. This can be done easily using matrix multiplication. Graphics libraries such as Direct3D* and OpenGL* have APIs for handling this.
Resources
Win 7 Sensor API: http://msdn.microsoft.com/library/windows/desktop/dd318953(VS.85).aspx
Sensor API Programming Guide: http://msdn.microsoft.com/en-us/library/dd318964(v=vs.85).aspx
Integrating Motion and Orientation Sensors: http://msdn.microsoft.com/en-us/library/windows/hardware/br259127.aspx