超极本™ 和平板电脑 Windows* 8 传感器开发指南

介绍


本指南为开发人员提供了面向 Windows 8.1 桌面和 Windows 商店应用的 Microsoft Windows 8.1 传感器应用编程接口 (API) 的概述并重点介绍了 Windows 8.1 桌面模式中可用的传感器功能。本开发指南对可以创建交互应用的 API 进行了总结,包括采用Windows 8.1 的加速器、磁力计和陀螺仪等常见传感器。

Windows 8.1 的编程选择


开发人员在 Windows 8.1 上对传感器进行编程时具有多种 API 选择。这种支持触控的应用环境又称作“Windows 商店应用”。Windows 商店应用可以运行那些在开发时加入了 Windows 运行时 (WinRT) 界面的软件。WinRT 传感器 API 是整个 WinRT 库的一部分。更多细节,请参见 MSDN Sensor API library

传统的 Win Forms 或 MFC 应用现在被称为“桌面应用”,因为它们运行于 Desktop Windows Manager 环境中。桌面应用可以使用本地的 Win32*/COM API、.NET 样式 API 或者部分的 WinRT 精选 API。 

以下列出的 WinRT API 可以通过桌面应用访问:

  • Windows.Sensors(加速计、陀螺测试仪、环境光传感器、方向传感器...)
  • Windows.Networking.Proximity.ProximityDevice (NFC)
  • Windows.Device.Geolocation (GPS)
  • Windows.UI.Notifications.ToastNotification
  • Windows.Globalization
  • Windows.Security.Authentication.OnlineId(包括 LiveID 集成)
  • Windows.Security.CryptographicBuffer(有用的二进制编码/解码函数)
  • Windows.ApplicationModel.DataTransfer.Clipboard(访问和监控 Windows 8 剪贴板)

在这两种情形中,这些 API 都通过一个名为 Windows Sensor Framework 的 Windows 中间件组件。Windows Sensor Framework 定义了传感器对象模型。不同的 API 以略有不同的方式“绑定”至相应的对象模型。

关于桌面应用和 Windows 商店应用开发的不同之处将会在本文的稍后部分介绍。为了简单起见,我们将只考虑桌面应用开发。有关 Windows 商店应用开发的信息,请参见 API Reference for Windows Store apps.

传感器


传感器的类型很多,但是我们感兴趣的是 Windows 8.1 需要的传感器,即加速器、陀螺仪、环境光传感器、罗盘和 GPS。Windows 8.1 通过对象导向的抽象来表现物理传感器。为了操控传感器,程序员使用 API 与对象进行交互。以下表格中的信息说明了如何通过 Windows 8 桌面应用和 Windows 商店应用访问各种传感器。

Windows 8.1 桌面模式应用

Windows 商店应用

功能/工具集

C++

C#/VB

JavaScript*/ HTML5

C++, C#, VB & XAML
JavaScript/HTML5

Unity* 4.2

方向传感器(加速计、测斜仪、陀螺测试仪

 

 

 

 

光线传感器

NFC

GPS

表 1.   Windows* 8.1 开发者环境功能矩阵

下面的图形 1 表明对象的数量超过了实际硬件的数量。Windows 通过利用多个物理传感器的信息,定义了某些“逻辑传感器”对象。这称之为“传感器融合”。

图 1.  Windows* 8 支持的不同类型的传感器

传感器融合

物理传感器芯片本身具有一些内在的局限性。例如:

  • 加速器测量线性加速,它是对联合的相对运动和地球重力进行测量。如果您想要了解电脑的倾角,则您必须进行一些数学运算。
  • 磁力计用于测量磁场的强度,会显示地球磁北极的位置。

这些测量都受固有偏移问题制约,可以使用陀螺仪中的原始数据进行校正。这两项测量(扩展)取决于电脑与地球水平面的倾斜度。例如,为了确保电脑的磁向与地球真正的北极一致(磁北极处于不同的位置,会随着时间的改变而移动),你需要对其进行校正。

传感器融合(图 2)通过获取多个物理传感器(尤其是加速器、陀螺仪和磁力计)的原始数据并执行数学运算对传感器自身的局限性进行校正,计算更适合人类使用的数据以及将这些数据以逻辑传感器抽象形式显示出来。应用开发人员必须实施必要的转换,以将物理传感器数据转换为抽象传感器数据。如果您的系统设计具有一个SensorHub,融合操作将发生在微控制器固件内。如果您的系统设计中没有 SensorHub,则融合操作必须在 IHV 和/或 OEM 提供的一个或多个设备驱动程序中完成。

图 2.  过组合来自多个传感器的输出进行传感器融合

识别传感器

为了控制一个传感器,系统需要对其进行识别并关联。Windows Sensor Framework 定义了划分传感器的若干类别。它还定义了若干特定的传感器类型。表 2 列出了一些适用于桌面应用的传感器类型。

“全部”

生物识别

环境

位置

机械

运动

方向

扫描仪

人体存在

电容

大气压

环境光

广播

布尔开关

1D 加速计

1D 罗盘

条形码

人体接近*

电流

湿度

Gps

布尔开关阵列

2D 加速计

2D 罗盘

Rfid

触控

电源

温度

静态

机械力

3D 加速计

3D 罗盘

电感

风向

多值开关

1D 陀螺仪

设备方向

电位计

风速

压力

2D 陀螺仪

1D 距离

电阻

应力

3D 陀螺仪

2D 距离

电压

重量

运动检测器

3D 距离

速度计

1D 测斜仪

2D 测斜仪

3D 测斜仪

表 2. 传感器类型和类别

Windows 必需的传感器类型以粗体显示:

  • 加速器、陀螺仪、罗盘和环境光是必需的“真正/物理”传感器
  • 设备方向和测斜仪是必需的“虚拟/融合”传感器(注:指南针还包括融合增强/倾斜补偿数据)
  • 如果存在一个 WWAN 广播,则 GPS 是必须的;否则 GPS 为可选
  • “人体接近”是必需列表中的常见选项,但是现在并不是必需的。

所有这些常量对应于全局唯一标识符(GUID)。以下表 3 中举例说明了一些传感器的类别和类型、对应于 Win32/COM 和 .NET 的常量名称以及它们基本的 GUID 值。

标识符

常量 (Win32*/COM)

常量 (.NET)

GUID

“全部”类别

SENSOR_CATEGORY_ALL

SensorCategories.SensorCategoryAll

{C317C286-C468-4288-9975-D4C4587C442C}

生物识别类别

SENSOR_CATEGORY_BIOMETRIC

SensorCategories.SensorCategoryBiometric

{CA19690F-A2C7-477D-A99E-99EC6E2B5648}

电类别

SENSOR_CATEGORY_ELECTRICAL

SensorCategories.SensorCategoryElectrical

{FB73FCD8-FC4A-483C-AC58-27B691C6BEFF}

环境类别

SENSOR_CATEGORY_ENVIRONMENTAL

SensorCategories.SensorCategoryEnvironmental

{323439AA-7F66-492B-BA0C-73E9AA0A65D5}

光类别

SENSOR_CATEGORY_LIGHT

SensorCategories.SensorCategoryLight

{17A665C0-9063-4216-B202-5C7A255E18CE}

位置类别

SENSOR_CATEGORY_LOCATION

SensorCategories.SensorCategoryLocation

{BFA794E4-F964-4FDB-90F6-51056BFE4B44}

机械类别

SENSOR_CATEGORY_MECHANICAL

SensorCategories.SensorCategoryMechanical

{8D131D68-8EF7-4656-80B5-CCCBD93791C5}

运动类别

SENSOR_CATEGORY_MOTION

SensorCategories.SensorCategoryMotion

{CD09DAF1-3B2E-4C3D-B598-B5E5FF93FD46}

方向类别

SENSOR_CATEGORY_ORIENTATION

SensorCategories.SensorCategoryOrientation

{9E6C04B6-96FE-4954-B726-68682A473F69}

扫描仪类别

SENSOR_CATEGORY_SCANNER

SensorCategories.SensorCategoryScanner

{B000E77E-F5B5-420F-815D-0270ª726F270}

人体接近类型

SENSOR_TYPE_HUMAN_PROXIMITY

SensorTypes.SensorTypeHumanProximity

{5220DAE9-3179-4430-9F90-06266D2A34DE}

环境光类型

SENSOR_TYPE_AMBIENT_LIGHT

SensorTypes.SensorTypeAmbientLight

{97F115C8-599A-4153-8894-D2D12899918A}

Gps 类型

SENSOR_TYPE_LOCATION_GPS

SensorTypes.SensorTypeLocationGps

{ED4CA589-327A-4FF9-A560-91DA4B48275E}

3D 加速计类型

SENSOR_TYPE_ACCELEROMETER_3D

SensorTypes.SensorTypeAccelerometer3D

{C2FB0F5F-E2D2-4C78-BCD0-352A9582819D}

3D 陀螺仪

SENSOR_TYPE_GYROMETER_3D

SensorTypes.SensorTypeGyrometer3D

{09485F5A-759E-42C2-BD4B-A349B75C8643}

3D 罗盘类型

SENSOR_TYPE_COMPASS_3D

SensorTypes.SensorTypeCompass3D

{76B5CE0D-17DD-414D-93A1-E127F40BDF6E}

设备方向类型

SENSOR_TYPE_DEVICE_ORIENTATION

SensorTypes.SensorTypeDeviceOrientation

{CDB5D8F7-3CFD-41C8-8542-CCE622CF5D6E}

3D 测斜仪类型

SENSOR_TYPE_INCLINOMETER_3D

SensorTypes.SensorTypeInclinometer3D

{B84919FB-EA85-4976-8444-6F6F5C6D31DB}

表 3. 常量和全局唯一标识符示例 (GUID)

以上列表中列出了最常使用的 GUID;其中许多现已可用。最初,您可能认为 GUID 无聊而且单调乏味,但是使用它们的一个最大原因就是:可扩展性。因为 API 不关注实际的传感器名称(它们仅传输 GUID),所以厂商可以为“增值”传感器创建新 GUID。

生成新的 GUID

微软在 Visual Studio* 中提供了一个用于生成新 GUID 的工具。图 3 显示了 Visual Studio 关于此操作的截图。所有厂商必须要做的就是发布它们,这样无需更改 Microsoft API 或任意操作系统代码即可看到新功能了。

图 3. 为增值传感器定义新 GUID

使用传感器管理器对象


为了使一个应用可以使用传感器,Microsoft Sensor Framework 需要通过一种方式将对象“绑定”到真实的硬件上。它采取了“即插即用”的方式,使用的是一种称为Sensor Manager Object(传感器管理器对象)的特殊工具。

通过类型询问

一款应用可以寻找特定类型的传感器,如 Gyrometer3D。传感器管理器询问电脑上显示的传感器硬件列表,然后返回绑定至该硬件的匹配对象的集合。虽然传感器集合可能有 0 个、1 个或多个对象,但通常只有 1 个。以下的 C++ 示例代码显示了使用传感器管理器对象的 GetSensorsByType 方法搜索 3 轴陀螺仪,并在传感器集合中返回搜索结果。请注意必须首先为传感器管理器对象创建一个 ::CoCreateInstance()。

// Additional includes for sensors
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>
// Create a COM interface to the SensorManager object.
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;
}
// Get a collection of all 3-axis Gyros on the computer.
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;
}
 

通过类别询问

一款应用可以通过类别寻找传感器,比如运动传感器。传感器管理器询问电脑上显示的传感器硬件列表,然后返回绑定至该硬件的运动对象的集合。SensorCollection 中可能有 0 个、1 个或多个对象。在大多数电脑上,集合都具有 2 个运动对象。Accelerometer3D 和 Gyrometer3D。

以下的 C++ 示例代码显示了使用传感器管理器对象的 GetSensorsByCategory 方法搜索运动传感器,并在传感器集合中返回搜索结果。

// Additional includes for sensors
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>
// Create a COM interface to the SensorManager object.
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;
}
// Get a collection of all 3-axis Gyros on the computer.
ISensorCollection* pSensorCollection = NULL;
hr = pSensorManager->GetSensorsByCategory(SENSOR_CATEGORY_MOTION, &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;
}
 

通过“全部”类别询问

实际上,一款应用如果能在电脑上同时寻找所有的传感器将会实现最高的效率。传感器管理器询问电脑上显示的传感器硬件列表,然后返回绑定至该硬件的所有对象的集合。传感器集合中可能有 0 个、1 个或多个对象。在大多数电脑上,集合都具有 7 个或以上的对象。

由于 C++ 不能进行 GetAllSensors 调用 ,因此您必须使用 GetSensorsByCategory(SENSOR_CATEGORY_ALL, …),如以下示例代码所示。

C++ does not have a GetAllSensors call, so you must use GetSensorsByCategory(SENSOR_CATEGORY_ALL, …) instead as shown in the sample code below.
// Additional includes for sensors
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>
// Create a COM interface to the SensorManager object.
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;
}
// Get a collection of all 3sensors on the computer.
ISensorCollection* pSensorCollection = NULL;
hr = pSensorManager->GetSensorsByCategory(SENSOR_CATEGORY_ALL, &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;
}
 

传感器生命周期 – 进入 (Enter) 和离开 (Leave) 事件

在 Windows 上,与大多数硬件设备一样,传感器被视为即插即用设备。在以下几种不通过的情况中传感器可被连接/断开:

  1. 可能系统外部有基于 USB 的传感器,并将其插入 USB 端口。
  2. 在连接和断开时,可能通过不可靠的无线接口(如蓝牙*)或有线接口(如以太网)连接了传感器。
  3. 如果 Windows Update 升级传感器的设备驱动程序,它们将显示为断开连接,然后再重新连接。
  4. Windows 关闭(S4 或 S5)时,传感器显示为断开连接。

在传感器操作中,即插即用称之为“进入” (Enter) 事件,断开称之为“离开” (Leave) 事件。需要有灵活的应用来处理这两种事件。

“进入”事件回调

如果在插入传感器时应用处于运行状态,传感器管理器将报告传感器“进入”事件;而如果传感器在应用开始运行时已经提前插好,此时就不会出现“进入”事件;而如果传感器在应用开始运行时已经提前插好,此时就不会出现“进入”事件。在 C++/COM 中,您必须使用 SetEventSink 方法钩起回调。该回调必须针对从 ISensorManagerEvents 继承的整类函数进行,同时必须实施 IUnknown此外,ISensorManagerEvents 接口必须执行回调函数:

	STDMETHODIMP OnSensorEnter(ISensor *pSensor, SensorState state);
// Hook the SensorManager for any SensorEnter events.
pSensorManagerEventClass = new SensorManagerEventSink();  // create C++ class instance
// get the ISensorManagerEvents COM interface pointer
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;
}
// hook COM interface of our class to SensorManager eventer
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;
}

代码:针对进入事件钩起回调

以下是等同于“进入”回调的 C++/COM。所有来自主循环的初始化步骤将在本函数中执行。事实上,重构您的代码更为有效,这样您的主循环只需调用 OnSensorEnter,就能模拟“进入”事件。

STDMETHODIMP SensorManagerEventSink::OnSensorEnter(ISensor *pSensor, SensorState state)
{
    // Examine the SupportsDataField for SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX.
    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)
    {
        // This is not the sensor we want.
        return -1;
    }
    ISensor *pAls = pSensor;  // It looks like an ALS, memorize it. 
    ::MessageBox(NULL, _T("Ambient Light Sensor has entered."), 
        _T("Sensor C++ Sample"), MB_OK | MB_ICONINFORMATION);
    .
    .
    .
    return hr;
}

代码: 进入事件回调

“离开”事件

单个传感器(并非传感器管理器)报告何时发生“离开”事件。本代码与之前的“进入”事件钩起回调相同。

// Hook the Sensor for any DataUpdated, Leave, or StateChanged events.
SensorEventSink* pSensorEventClass = new SensorEventSink();  // create C++ class instance
ISensorEvents* pSensorEvents = NULL;
// get the ISensorEvents COM interface pointer
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); // hook COM interface of our class to Sensor eventer
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;
}

代码: 离开事件钩起回调 

OnLeave 事件处理程序接收离开传感器的ID 并将其作为一个参数。

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);
    // Perform any housekeeping tasks for the sensor that is leaving.
    // For example, if you have maintained a reference to the sensor,
    // release it now and set the pointer to NULL.
    return hr;
}

代码:离开事件回调

为一款应用选择传感器


不同类型的传感器报告不同的信息。微软将这些信息称之为数据域 (Data Field),它们集合在一个 SensorDataReport 中。一台电脑可能需要配备一个以上的传感器供一款应用使用。这款应用在信息可用的情况下并不会关注信息的来源(即来自于哪个传感器)。

表 4 显示了 Win32/COM 和 .NET 最常用数据域的常量名称。与传感器标识符一样,这些常量仅代表它们对应的 GUID 的人类可读的名称。这种对应方法为那些微软预定义的“已知”数据域之外的数据域提供了扩展性。

常量 (Win32*/COM)

常量 (.NET)

属性键 (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_DEGRE
ES_PER_SECOND

SensorDataTypeAngularVelocityXDegreesPerSecond

{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},10

SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Y_DEGRE
ES_PER_SECOND

SensorDataTypeAngularVelocityYDegreesPerSecond

{3F8A69A2-07C5-4E48-A965-CD797AAB56D5},11

SENSOR_DATA_TYPE_ANGULAR_VELOCITY_Z_DEGRE
ES_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_COMPEN
SATED_MAGNETIC_NORTH_DEGREES

SensorDataTypeMagneticHeadingCompen
satedTrueNorthDegrees

{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_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

表 4. 数据域标识符常量   

使得数据域标识符与传感器 ID 不同的原因是使用了名为属性键的数据类型。一个属性键包括一个 GUID (类似于传感器的 GUID) 以及一个名为“PID”的额外编号(属性 ID)。您可能会注意到属性键的 GUID 部分对于同一类别的传感器是通用的。数据域的所有值都具有本机数据类型,例如Boolean、unsigned char、int、float、double 等。

在 Win32/COM 中,数据域的值存储在名为 PROPVARIANT 的多态数据类型中。在 .NET 中,有一个名为“对象”(object) 的 CLR(通用语言运行时)数据类型执行相同的操作。而多态数据类型则需要被查询和/或转换为“预期”或“记录”数据类型。

使用传感器的 SupportsDataField() 方法检查传感器,获取感兴趣的数据域。这是选择传感器时最常使用的编程术语。根据应用的使用模型不同,可能仅需要使用部分的数据域。应当选择那些可以支持预期数据域的传感器。类型转换时要求对来自基类传感器的子类成员变量进行分配。

ISensor* m_pAls;
ISensor* m_pAccel;
ISensor* m_pTilt;
// Cycle through the collection looking for sensors we care about.
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;
        .
        .
        .
    }
}

代码:使用传感器中的 SupportsDataField() 方法查看支持的数据域

传感器属性

除了数据域,传感器还具有可用于辨识和配置的属性。表 5 显示了最常用的属性。与数据域类似,属性也有 Win32/COM 和 .NET 使用的常量名称,而且这些常量确实是下面的属性键数字。属性可通过厂商扩展,还具有 PROPVARIANT 多态数据类型。与只读的数据域不同,属性可以读/写。它取决于单个传感器是否拒绝写入尝试。由于在写入尝试失败时不会引发异常,因此需要执行读写验证。 

标识 (Win32*/COM)

标识 (.NET)

属性键 (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

 

配置 (Win32/COM)

配置 (.NET)

属性键 (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

表 5. 常用的传感器属性和 PID

设置传感器敏感度

敏感度设置是非常有用的传感器属性。可用于分配控制或过滤发送至主机计算机的 SensorDataReports 数量的阈值。流量可通过这种方式得以降低:仅发出那些真正会干扰主机 CPU 的 DataUpdated 事件。微软已经将敏感度属性的数据类型定义为一个容器类型,该容器类型在 Win32/COM 中被称为 IPortableDeviceValues,而在 .NET 中则被称为 SensorPortableDeviceValues。容器中包含一个元组集合,其中每个都是一个数据域属性键,随后是该数据域的敏感度值。敏感度通常使用与匹配数据相同的测量单位和数据类型。

// Configure sensitivity
// create an IPortableDeviceValues container for holding the <Data Field, Sensitivity> tuples.
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;
}
// fill in IPortableDeviceValues container contents here: 0.1 G sensitivity in each of X, Y, and Z axes.
PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_R8; // COM type for (double)
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);
// create an IPortableDeviceValues container for holding the <SENSOR_PROPERTY_CHANGE_SENSITIVITY, pInSensitivityValues> tuple.
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;
}
// fill it in
pInValues->SetIPortableDeviceValuesValue(SENSOR_PROPERTY_CHANGE_SENSITIVITY, pInSensitivityValues);
// now actually set the sensitivity
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;
}
// check to see if any of the setting requests failed
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);

申请传感器权限

一些有传感器提供的信息可能被认为是敏感信息,例如个人可识别信息 (PII)。计算机的位置等数据域(如纬度和经度)可以用于追踪用户。为此,Windows 强制应用获取最终用户权限,以访问传感器。根据需要,可以使用传感器的 State 属性以及 SensorManager 的 RequestPermissions() 方法。

RequestPermissions() 方法将一组传感器作为一个参数,所以一款应用可以一次申请多个传感器的权限。C++/COM 代码显示如下。注:(ISensorCollection *) 必须作为一个参数提供给 RequestPermissions()。

// Get the sensor's state

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;
}
// Check for access permissions, request permission if necessary.
if (state == SENSOR_STATE_ACCESS_DENIED)
{
    // Make a SensorCollection with only the sensors we want to get permission to access.
    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); // add 1 or more sensors to request permission for...
    // Have the SensorManager prompt the end-user for permission.
    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;
    }
}

 

传感器数据更新

传感器通过发出名为 DataUpdated 的事件来报告数据。实际的数据域在 SensorDataReport 内打包,被传输至所有附带的 DataUpdated 事件处理程序中。一款应用通过钩起一个回调处理程序至传感器的 DataUpdated 事件获取 SensorDataReport。事件发生在 Windows Sensor Framework 线程,该线程与用于更新应用 GUI 的消息泵线程不同。因此,需要将 SensorDataReport 从事件处理程序 (Als_DataUpdate) 传至可以在 GUI 线程环境中执行的单独的处理程序 (Als_UpdateGUI)。在 .NET 中,此类处理程序称之为委托函数。

以下示例显示了委托函数的实现。在 C++/COM 中,必须使用 SetEventSink 方法钩起回调。回调不仅仅是一个函数,它必须是从 ISensorEvents 继承并执行 IUnknown 的整类函数。ISensorEvents 接口必须执行回调函数:

	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);
// Hook the Sensor for any DataUpdated, Leave, or StateChanged events.
SensorEventSink* pSensorEventClass = new SensorEventSink();  // create C++ class instance
ISensorEvents* pSensorEvents = NULL;
// get the ISensorEvents COM interface pointer
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); // hook COM interface of our class to Sensor eventer
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;
}

代码:为传感器设置一个 COM 事件接收器

DataUpdated 事件处理程序以参数形式接收 SensorDataReport(以及初始化事件的传感器)。它调用表格中的 Invoke() 方法将这些条目转至委托函数。GUI 线程运行转至其 Invoke 队列的委托函数并将参数传输至该函数。委托函数将 SensorDataReport 的数据类型转换为所需的子类,获得数据域访问。数据域是使用 SensorDataReport 对象中的 GetDataField() 方法提取的。每个数据域都必须将类型转换至它们的“预期”或“记录”数据类型(从使用 GetDataField() 方法返回的一般/多态数据类型)。然后应用会在 GUI 中排列并显示数据。

OnDataUpdated 事件处理程序以参数形式接收 SensorDataReport(以及初始化事件的传感器)。数据域是使用 SensorDataReport 对象中的 GetSensorValue() 方法提取的。每个数据域都需要检查自身的 PROPVARIANT,以适合“预期”或“记录”数据类型。然后应用会在 GUI 中排列并显示数据。不需要使用同等的 C# 委托。这是因为所有 C++ GUI 函数(如此处显示的 ::SetWindowText() 使用 Windows 消息传递将 GUI 更新转至 GUI 线程/消息循环(您主窗口或对话框的 WndProc)。

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) // make sure the PROPVARIANT holds a float as we expect
        {
            // Get the lux value.
            fLux = pv.fltVal;
            // Update the GUI
            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;
}

可以通过引用 SensorDataReport 对象的属性从 SensorDataReport 中提取数据域。这种情况仅适合于 .NET API 和 SensorDataReport 特殊子类的 “已知” 或 “预期” 数据域。在Win32/COM API 中,必须使用 GetDataField 方法。可以使用“动态数据域”为底层驱动程序/固件“搭载” SensorDataReports 内的任意“扩展/非预期”数据域。GetDataField 方法可用于提取这些内容。

在 Windows 商店应用中使用传感器


不同于桌面模式,WinRT 传感器 API 对每个传感器遵循一个通用模板:

  • 这通常是一个名为 ReadingChanged 的单个事件,使用包含 Reading 对象(具有实际数据)的 xxxReadingChangedEventArgs 调用回调。加速计是一个例外;它还具有a Shaken 事件。
  • 传感器类的硬件绑定实例使用 GetDefault() 方法检索。
  • 可以通过 GetCurrentReading() 方法执行轮询。

Windows 商店应用一般使用JavaScript* 或 C# 编写。API 有不同的语言绑定,这导致 API 名称中的大写稍有不同以及事件处理方式也稍有不同。简化的 API 更易于使用,表 6 中列出了利弊。

特性

SensorManager

没有 SensorManager 需要处理。应用使用 GetDefault() 方式获取传感器类实例。

  • 可能无法搜索任意传感器实例。如果计算机上存在多个特定类型的传感器,您将只能看到“第一个”。
  • 可能无法使用 GUID 搜索任意传感器类型或类别。厂商增值扩展不可用。

事件

应用仅关注 DataUpdated 事件。

  • 应用无法访问 Enter、Leave、 StatusChanged 或任意事件类型。厂商增值扩展不可用。

传感器属性

应用仅关注 ReportInterval 属性。

  • 应用无法访问其他属性,包括最有用的:敏感度。
  • 除了操控 ReportInterval 属性,Windows 商店应用无法调整或控制数据报告的流量。
  • 应用无法通过属性键访问任意属性。厂商增值扩展不可用。

数据报告属性

应用仅关注仅存在于每个传感器中的少数预定义数据域。

  • 应用无法访问其他数据域。如果传感器在 Windows 商店应用预计之外的数据报告中“搭载”其他已知数据域,该数据域将不可用。
  • 应用无法通过属性键访问任意数据域。厂商增值扩展不可用。
  • 应用无法在运行时查询传感器支持哪些数据域。它仅可以假定 API 预定义的数据域。

 表 6. Metro 风格应用的传感器 API 以及利弊

总结


Windows 8 API 支持开发人员在传统的桌面模式和全新的 Windows 商店应用界面下在不同的平台上使用传感器。在本文中,我们概述了开发人员在创建 Windows 8 应用时可以用到的传感器 API,重点讲述了适合桌面应用的 API 和示例代码。许多新的 Windows 8 API 在 Windows 8.1 操作系统中得到了进一步的改进,本文为许多 MSDN 上的相关示例提供了链接。

附录


不同外形的坐标系统
Windows API 通过与 HTML5 标准(和Android*)兼容的方式报告 X、Y 和 Z 轴。它还称之为 “ENU” 系统,因为 X 面向虚拟的“东”(East)、Y 面向虚拟的“北”(North),而 Z 面向“上”(Up)。

如要弄清楚旋转的方向,请使用“右手定则”:

   * 右手拇指指向其中一个轴的方向。
   * 沿该轴正角旋转将顺着您手指的曲线。

这些是面向平板电脑或者手机(左)和蛤壳式电脑(右)的 X、Y 和 Z 轴。对于更复杂的外形(如可转换为平板的蛤壳式),“标准”方向是其处于“TABLET”(平板)状态时。

如果您想要开发一个导航应用(如 3D 空间游戏),就需要在您的程序中从“ENU”系统进行转换。可通过矩阵乘法轻松完成该操作。Direct3D* 和 OpenGL* 等图形库都有可处理这一操作的 API。

MSDN 资源


作者介绍


Gael Hofemeier
Gael 是英特尔开发人员关系分部的软件工程师,主攻业务客户端技术。她拥有新墨西哥大学的数学理科学士和 MBA 学位,爱好徒步旅行、自行车和摄影。

Deepak Vembar 博士
Deepak Vembar 是英特尔实验室交互与体验研究 (IXR) 事业部的一位研究员。他的研究主要关注计算机图形交互和人机交互,包括实时图形、虚拟现实、触觉、眼睛追踪和用户交互等领域。在进入英特尔实验室之前,Deepak 是英特尔软件与服务事业部 (SSG) 的一位软件工程师,与电脑游戏开发人员一起针对英特尔平台优化游戏、传授异构平台优化课程和指南以及使用游戏演示编写大学课程(作为教学媒体在学校课程中使用)。 

英特尔和 Intel 标识是英特尔在美国和/或其他国家的商标。
版权所有 © 2012 英特尔公司。所有权利受到保护。
*其他的名称和品牌可能是其他所有者的资产。


附件

下载 windows-8-1-sensor-dev-guide.pdf (999.63 KB)

Per informazioni complete sulle ottimizzazioni del compilatore, consultare l'Avviso sull'ottimizzazione