After a quick overview of the Sensor and Location Platform - I Can Feel You – Using the Windows 7 Sensor Platform, it is time to start digging into the API. We’ll start with an overview of the Win32 API to understand the data flow and then will review the managed API via the Windows API Code Pack for the .NET Framework.

The Sensor and Location platform has two components, the sensor piece and, built on top of that, the location piece. We’ll cover the sensor piece in great detail to make sure that when the time comes to speak about location, we understand the foundation upon which it is built.

The Sensor API (as well as the Location API) is COM-based. You will find all the required API and GUID definitions in the Sensorsapi.h and Sensors.h files found in your local Windows 7 SDK install. The Sensors.h file contains a ton of GUIDs that define all the different aspects of a sensor’s categories, types, and data. The API and interface are found in the Sensorsapi.h file. A closer look at the Sensorsapi.h file reveals three main COM Interfaces that you should be aware of when working with Sensors:

  • ISensorManager is your main interface to all the sensors that are connected to the platform. The sensor manager allows you to enumerate through the sensors list, request permissions from the user to access the sensor data, and receive events upon new sensor arrivals.
  • ISensor is the actual sensor object you want to work with. Through this interface you will be able to set and get properties, get data reports and register for a range of events that happen during the life of the sensor.
  • ISensorDataReport is the one interface that you will learn to love and hate, as this is the one and only way to read actual data from the sensor; but it is a generic form of a generic data report that needs to span

You will need to work with these three interfaces to integrate sensors into your application.

Integrating Sensors into Your Application

When integrating sensors into your application, you need to follow three basic steps:

  1. Discover sensors – Before you can obtain a sensor, you need to discover which sensors are connected to the system. Use the ISensorManager interface to enumerate through the list of sensor devices that are connected to the platform and register to events that notify you when new sensor devices arrive.
  2. Acquire a sensor and request sensor permissions – Once you discover and choose the sensor with which you wish to work, you need to make sure you have permission to access it. If you don’t have permission to access the sensor’s data (use it…), you need to ask the user for permission in order for your application to access the sensor’s data.
  3. Interact with the sensor –Now that you have obtained a sensor whose data you have permission to access, you can start interacting with it, reading and setting properties, and registering for events.

Discovering Sensors

The first step you need to take for each application for which you want to use sensors is to discover which sensors are connected to the platform and then get access to one. To enable your application to do this, you need to use the ISensorManager COM Interface. The sensor manager maintains the list of available sensors. Since it is a COM interface, you need to initialize it using CoCreateInstance. You can think of this interface as the root interface for the sensor API. The following code snippet creates an instance of the sensor manager:

// Declare a smart pointer to receive the sensor manager interface pointer.
// Originally, this smart pointer is defined in the class prototype definition
// CAmbientLightAwareSensorManagerEvents
CComPtr<ISensorManager> m_spISensorManager;
HRESULT hr;
// Create the sensor manager
hr = m_spISensorManager.CoCreateInstance(CLSID_SensorManager);

The ISensorManager interface provides a set of methods for discovering and retrieving available sensors. It also exposes an event that is used for receiving notifications when a new sensor becomes available. You may ask yourself where to register for the event where a sensor “leaves” the system. Well, this can be found within the sensor itself. With an ISensorManager pointer at hand, you can start searching for sensors. ISensorManager has three methods that help you search for connected sensors:

  • GetSensorsByID – The first method returns a specific sensor by its ID, which is just a GUID. This is not the most useful function, since the sensor GUID is automatically generated when the sensor is first connected to the system to support multiple sensors of the same make and model.
  • GetSensorsByCategory – This function returns a collection of sensors that share the same category. For example, I could have several location sensors, including GPS and Wi-Fi triangulation, connected to the platform and you don’t want to exclude any.
  • GetSensorsByType – This function returns a collection of sensors that share the same type.

As you noticed, all function names reflect that there are multiple sensors connected at the same time to the computer. It is possible that several sensors of the same category are connected to the same PC. For example, on my machine, I have one light sensor built into the PC enclosure and another one as part of my Sensor Development Kit. It makes sense to use the light sensor built in to the enclosure because it provides the most accurate lighting condition reading from the PC. For an application, the best way to discover sensors is to use the Sensor Manager interface's type and category functions and then choose the sensor you need to work with from the list.

Through this series of posts, we’ll use a very simple Microsoft Foundation Classes (MFC) light-aware application that is part of the Windows 7 SDK. The following code is taken from the Initialize method of the CAmbientLightAwareSensorManagerEvents class. This class implements the ISensorManagerEvents interface notifications from the platform upon arrival of a new sensor.

The Initialize method is called during the initialization process of the main dialog (we have only one):

HRESULT CAmbientLightAwareSensorManagerEvents::Initialize()
{
    HRESULT hr;
    // Create the sensor manager
    hr = m_spISensorManager.CoCreateInstance(CLSID_SensorManager);
    if (SUCCEEDED(hr))
    {
        hr = m_spISensorManager->SetEventSink(this);
        if (SUCCEEDED(hr))
        {
            // Find all Ambient Light Sensors
            CComPtr<ISensorCollection> spSensors;
            hr = m_spISensorManager->GetSensorsByType
                        (SENSOR_TYPE_AMBIENT_LIGHT, &spSensors);
            if (SUCCEEDED(hr) && NULL != spSensors)
            {
                ULONG ulCount = 0;
                hr = spSensors->GetCount(&ulCount);
                if (SUCCEEDED(hr))
                {
                    for(ULONG i=0; i < ulCount; i++)
                    {
                        CComPtr<ISensor> spSensor;
                        hr = spSensors->GetAt(i, &spSensor);
                        if (SUCCEEDED(hr))
                        {
                            // Helper function that sets up event sinking 
                            // for a specific sensor
                            hr = AddSensor(spSensor);
                            if (SUCCEEDED(hr))
                            {
                                // Check the current sensor state.
                                SensorState state = SENSOR_STATE_READY;
                                hr = spSensor->GetState(&state);
                                if(SUCCEEDED(hr))
                                {
                                      if(state == SENSOR_STATE_READY)
                                     {
                                        // Read the sensor data and update the 
                                        // application's UI
                                        hr = m_pSensorEvents->GetSensorData
                                                                (spSensor);
                                     }
                            }
                        }
                    }
                }
            }
        }
    }
    return hr;
}

Here you can see how, after successfully obtaining an ISensorManager interface, you call ISensorManager::GetSensorsByType, passing SENSOR_TYPE_AMBIENT_LIGHT and a pointer to an ISensorCollection collection, spSensors. The SENSOR_TYPE_AMBIENT_LIGHT indicates that we want to receive only ambient light sensors (ALS). At the same time, you could ask for all sensors of the type SENSOR_TYPE_VOLTAGE from the electrical category, or SENSOR_TYPE_ACCELEROMETER_3D from the motion category.

If successful, the GetSensorsByType function fills ISensorCollection with a list of ALS. Next, you iterate through the sensor collection and call the AddSensor helper function for each sensor. This sets up event sinking (a delegate) by registering the sensor to m_pSensorEvents, which is a SensorEvents implementation class used for event sinking that I’ll explain later. Finally, you call another helper method, GetSensorData, to read data from the sensor and to update the LUX value in the application’s UI. We’ll address the actual reading of sensor data in future posts.

During an application's runtime, new light sensors might get connected to the PC, so you need a mechanism for notifying applications when new sensor devices are connected. The ISensorManager interface contains a SetEventSink method to set an event sinking implementation class – that is, a delegate that handles the events. This function receives an ISensorManagerEvents callback interface that receives the event notifications when a new sensor device is connected. This event sink acts as a listener that handles OnSensorEnter, the only event the ISensorManagerEvents interface has.

If you take a look at the above sample, you will see that you call SetEventSink right after the successful creation of the ISensorManager interface and pass thisas the input parameter, and then the local class CAmbientLightAwareSensorManagerEvents implements the ISensorManagerEvents interface.

Therefore, in this class you can find the implementation for the ISensorManager::OnSensorEnter event, which looks like the following code snippet:

HRESULT CAmbientLightAwareSensorManagerEvents::OnSensorEnter
                                    (ISensor* pSensor, SensorState state)
{
    HRESULT hr = S_OK;
 
    if (NULL != pSensor)
    {
        SENSOR_TYPE_ID idType = GUID_NULL;
        hr = pSensor->GetType(&idType);
        if (SUCCEEDED(hr))
        {
            // we are interested only in light sensors
            if (IsEqualIID(idType, SENSOR_TYPE_AMBIENT_LIGHT))
            {
                hr = AddSensor(pSensor);
                if (SUCCEEDED(hr))
                {
                    if (SENSOR_STATE_READY == state)
                    {
                        hr = m_pSensorEvents->GetSensorData(pSensor);
                    }
                }
            }
        }
    }
    else
    {
        hr = E_POINTER;
    }
 
    return hr;
}

In this code you can see that the OnSensorEnter method receives a pointer to the newly connected sensor device and the state status of the sensor. If the sensor pointer is valid (not null), you first read the sensor’s type by calling the GetType method on the ISensor interface. Because we are in a light-aware application and are interested only in light sensors, we’ll check if the newly connected sensor device is a light sensor by checking its type. Remember that ISensorManager receives notification of any type of sensor devices that are connected to the PC. Assuming the sensor is a light sensor, you then call the same AddSensor helper function that you used in the Initialize method to set up event sinking for specific sensors. (Don’t get confused with the ISensorManager event sinking.) The last thing you do is check whether the sensor is in a ready state. If it is, you read the sensor’s data and update the application’s UI.

But we'll save reading sensor data for our next posts.

You can find more Windows 7 Sensor and Location training at the Channel 9 Learning Center.