Exercise 1: Adjusting Font Size in Response to Ambient Light Intensity

In this exercise, you will modify a Win32 MFC application so that it adjusts the font size used in response to varying levels of light intensity as sensed by an ambient light sensor. Most of the application's user interface is already implemented; you will have to fill in the missing parts to interact with the Windows 7 Sensor API. The end result looks like the following image

To begin this exercise, open the AmbientLightAware solution (under the HOL root folder) in Visual Studio.

This is a simple and standard MFC application. Spend a minute or two exploring the C++ source file (.cpp/.h) that comprises the demo application. For your convenience, ensure that the Task List tool window is visible (in the View menu, choose Task List) and in the tool window’s combo box select Comments—you will now see TODO items for each of the tasks in this exercise.Run the application to familiarize yourself with its looks.

Note:
Note

In the interests of brevity and simplicity, the demo application does not demonstrate best practices of MFC UI development, nor does it exhibit the best design guidelines for object-oriented development and COM development

Task 1—Adding Ambient Light Awareness

In this task, you will add support classes and modify your application to adjust the font size of a control based on the average luminance of one or more ambient light sensors.

  1. Right-click on the AmbientLightAware project and click Properties.
    1. Go to Linker --> Input --> Additional dependencies.
    2. Add sensorsapi.lib to the list.
    3. Click OK to close the dialog.
  2. Navigate to StdAfx.h and uncomment the following includes:#include <Sensors.h>
    #include <SensorAPI.h>

  3. Right-click on the AmbientLightAware project and click Add --> Existing Item...
    1. Navigate to the project's directory if you're not already in it.
    2. Select (Ctrl-Click) the following files:
    3. AmbientLightAwareSensorManagerEvents.cpp
    4. AmbientLightAwareSensorManagerEvents.h
    5. AmbientLightAwareSensorEvents.cpp
    6. AmbientLightAwareSensorEvents.h
    7. Click the Add button.
  4. The Sensor API consists of the ISensorManager object, which you can use to query existing sensors by their type, category, or instance ID. You can also request permission from other users to use their sensors. In addition, you can register to be notified when new sensors become available.
  5. The CAmbientLightAwareSensorManagerEvents class serves a dual purpose:
  6. It contains the code to interact with the Sensor Manager
  7. It also implements the OnSensorEnter (new sensor becomes available) event
  8. From the Sensor Manager you can obtain ISensor objects that represent individual sensors. You can query a sensor's state and get or set other properties, such as manufacturer, model, precision, and requested update interval. You can request a data report that contains the results of measurements the sensor did. You can also register for events (sensor becomes unavailable, state changes, new data report available, generic event).
  9. The CAmbientLightAwareSensorEvents class servers a dual purpose:
  10. It contains the code to interact with the ambient light sensor
  11. It also implements the aforementioned sensor events
  12. Navigate to the top of the AmbientLightAwareSensorManagerEvents.cpp file.
  13. Examine the constructor, AddRef, Release and QueryInterface methods. This is just standard COM boilerplate code.
  14. Uncomment the Initialize() method.
    1. The code creates a Sensor Manager object (equivalent to CoCreateInstance).
    2. The call to SetEventSink subscribes us for the OnSensorEnter event (the class implements this event).
    3. By calling GetSensorsByType,we enumerate all sensors that are of type SENSOR_TYPE_AMBIENT_LIGHT. Each sensor has three GUIDs: category, type, and instance ID. Category represents what is being measured (for example, environment), Type represent how it is being measured (for example, temperature, humidity). The instance ID uniquely identifies the sensor. Instance IDs may be persistent (if a serial number is "burned" on each sensor), or non-persistent.
    4. The SENSOR_TYPE_AMBIENT_LIGHT #define maps to a GUID. There are many predefined sensor types in sensors.h. You can view them by right-clicking on the SENSOR_TYPE_AMBIENT_LIGHT define and selecting Go To Definition.
    5. We request permission to use the ambient light sensors by calling ISensorManager::RequestPermissions(), specifying the parent HWND, sensor collection and whether the function should block until the user selects what to do. This displays the following dialog:

      If the user refused to enable a sensor, subsequent calls to RequestPermissions() will not show the UI.

    6. GetSensorsByTypereturns an ISensorCollection object whose item count could be determined with GetCount() and is accessed by GetAt().
    7. For each sensor, we will call the AddSensormethod, which we will later implement.
    8. Well call the GetSensorsData() method of the Sensor Events class because we want to force a fresh data report to be generated. Otherwise, the sensor may not produce a data report until the luminance has changed significantly enough.
  15. Uncomment the AddSensor() method.
    1. The SetEventSink method of ISensor subscribes the CAmbientLightAwareSensorEvents object that we created in the constructor to this particular sensor's events (state changed, sensor detached/unavailable, and new data report).
    2. The sensor object is added to the m_Sensors CAtlMap.
  16. Uncomment the two RemoveSensor() methods.
    1. Uncomment the method that gets ISensor* as a parameter. The application calls this method when it wants to unsubscribe voluntarily from a particular sensor. This method is called when the application shuts down.
    2. Uncomment the method that gets REFSENSOR_ID as a parameter. The Sensor Events class (CAmbientLightAwareSensorEvents) calls this function when there is a forced removal of the sensor (for example, when the hardware is disconnected). This method gets a GUID and not an ISensor* because the sensor can no longer be accessed after it has been removed.
  17. Uncomment the UnInitiliaze() method. This method unsubscribes from handing events of all previously subscribed to sensors. It also un-subscribes from the Sensor Manager events. This method is called when shutting down.
  18. Uncomment the OnSensorEnter() method.
    1. This method implements ISensorManagerEvents. It is invoked when a new sensor becomes available (for example, physically attached).
    2. While Initialize() handles sensors which are already available at time of application startup, this method handles sensors that later become available.
    3. This method queries sensor state to determine if it's ready to use, and requests a data report.
  19. Navigate to the top of the AmbientLightAwareSensorEvents.cpp file.
  20. Examine the constructor, AddRef, Release and QueryInterface methods. This is just standard COM boilerplate code
  21. Examine m_mapLux. It is a CAtlMap of (SENSOR_ID to float). This map keeps track of each sensor's last recorded ambient light value (in units of Lux). The values are kept separately for each sensor so that they can be averaged later.
  22. Uncomment the OnLeave() method. This method implements ISensorEvents and is invoked when a sensor becomes unavailable (that is, detached).
    1. This method calls the CAmbientLightAwareSensorManagerEvents::RemoveSensor() method that we previously implemented.
    2. The method removes the removed sensor's last ambient light value, so that it would no longer be counted when averaging. UpdateLux() is called to reflect the change by recalculating the average light intensity and by updating the UI .
  23. Uncomment the OnStateChanged() method. This method implements ISensorEvents and is invoked when a sensor's state changes. When the sensor is ready, a data report is requested and the UI is updated.
    1. Even if a sensor is attached, it may not be ready (SENSOR_STATE_READY ) for use. The state gives the reason why.
    2. Another important state is SENSOR_STATE_ACCESS_DENIED, which means that permissions to use this sensor have not been granted yet in Control Panel. After you grant permissions, the state will change. You can also call ISensorManager::RequestPermissions to display the permissions dialog.
    3. You can query the state on demand, by calling ISensor::GetState().
  24. Uncomment the OnDataUpdated()method. This method implements ISensorEvents and is invoked when the sensor has a new data report available. Some sensors update periodically, others update when the data value(s) have changed significantly enough. In some sensors, you can control the change sensitivity by setting the SENSOR_PROPERTY_CHANGE_SENSITIVITY property with SetProperties().
  25. Uncomment the OnEvent() method. This method implements ISensorEvents and is invoked when any event fires, including OnLeave(), OnStateChanged()andOnData().
    1. You can differentiate events by the event GUID.
    2. The reason this event exists is to allow sensors to define custom events (for example, a GPS sensor might have satellite found/lost event). In our case, we don't have custom events, so this method always returns S_OK.
  26. Uncomment the UpdateLux() method. This method iterates over the m_mapLux map and calculates the average value. It calls a method by the same name in the CAmbientLightAwareDlg class.
  27. Uncomment the UpdateData(ISensor* pSensor) method. Notice that there is another method by that name with different parameters (overload).
    1. This method is used throughout the code whenever we need to manually request a new data report.
    2. This method calls ISensor::GetData() to produce an IDataReport object. With this object, the other overload of UpdateData is called, which takes the IDataReport as a second parameter. This saves code duplication.
  28. Uncomment the GetSensorData(ISensor *pSensor, ISensorDataReport *pDataReport)method. This method implement ISensorEvents and is invoked when the sensor has new data available.
    1. The Sensor data report consists of one or more data values (fields). They can be of any number of types a PROPVARIANT supports.
    2. You can see the pre-determined properties by right-clicking on SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX and selecting Go To Definition. The properties are grouped together in the sensors.h file, by sensor category. A comment is next to each entry specifying its data type.
    3. You can determine what data fields are available in a given data report by calling GetSensorValues(),which returns a key-value collection.
    4. You can also determine a-priori if a sensor supports a given data field by calling ISensor::SupportsDataField().
    5. Use the PropVariantInit and PropVariantClear method to initialize the PROPVARIANT.
    6. This method updates the sensor's luminance value in the m_mapLux collection and then calls UpdateLux()to recalculate and update the UI.
  29. Navigate to the top of AmbientLightAwareDlg.h. Uncomment the CleanUp() and UpdateLux() method declarations.
  30. Navigate to the bottom of the file. Uncomment them_pSensorManagerEvents and m_lfLogFont class members.
  31. Navigate to the top of AmbientLightAwareDlg.cpp. Uncomment the following #include lines:#include "AmbientLightAwareSensorEvents.h"
    #include "AmbientLightAwareSensorManagerEvents.h"

  32. Navigate to the AmbientLightAwareDlgconstructor. Uncomment the initialization of m_pSensorManagerEvents and m_lfLogFont class members.
  33. Uncomment CleanUp(). This method will be called when shutting down.
  34. Uncomment InitAmbientLightAware(). This method will be called on startup to perform sensor-related initializations.
  35. Navigate to OnInitDialog(), and uncomment the call to InitAmbientLightAware().
  36. Uncomment UpdateLux(). This method determines the appropriate font size for the current illumination. It updates UI members (number of sensors an average luminance) and updates the UI. A proper application might implement low-pass filtering to avoid too frequent font updates that might irritate the user.
  37. Navigate to OnPaint(), uncomment the code that changes the font for the IDC_STATIC_SAMPLE static control.
  38. Navigate to the top of AmbientLightAware.cpp. Uncomment the following #include lines:(C++)#include "AmbientLightAwareSensorManagerEvents.h"

  39. Navigate to InitInstance(). Uncomment calls to CoInitializeEx(), CoUninitialize() and dlg.CleanUp().
  40. Compile and run the application. Change illumination value and see text grow and shrink.