内容简介
本指南为开发人员提供了面向 Desktop和Windows* 8商店应用的 Microsoft Windows* 8 传感器应用编程接口 (API) 的概述并重点介绍了 Windows 8 Desktop 模式中可用的传感器功能。 我们对可以创建交互应用的 API 进行了总结,包括采用Windows 8 的加速器、磁力计和陀螺仪等常见传感器。
内容
- 内容简介
- Windows 8 的编程选择
- 传感器
- 识别传感器
- 使用传感器管理器对象
- 传感器生命周期 – 进入 (Enter) 和离开 (Leave) 事件
- 为您的应用挑选传感器
- 在 Metro 风格应用中使用传感器
- 总结
- 附录
- 声明
- 优化声明
Windows 8 的编程选择
开发人员在 Win8 上对传感器进行编程时具有多种 API 选择。 图 1 的左侧显示了新的支持触摸的应用环境,称之为“ Windows* 8商店应用”。Windows* 8商店应用唯一可以使用的 API 库是名为 WinRT 的全新 API 库。 WinRT 传感器 API 是整个 WinRT 库的一部分。 如欲了解更多详细信息,请参阅: http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.sensors.aspx
右侧显示的是传统的 Win Forms 或 MFC 风格应用,因为它们在 Desktop Windows Manager 环境中运行,所以称之为“Desktop 应用”。 Desktop 应用可以使用本机 Win32/COM API 或 .NET 样式 API。
在这两种情形中,这些 API 都通过一个名为 Windows Sensor Framework 的 Windows 中间件组件。 Windows Sensor Framework 定义了传感器对象模型。 不同的 API 以略有不同的方式“绑定”至相应的对象模型。
图 1: Windows 8 中 商店应用 和 Desktop 传感器架构
关于 Desktop 和 Windows* 8商店应用开发的不同之处将会在本文的稍后部分介绍。 为了简单起见,我们将只考虑 Desktop 应用开发。 如欲获取 Windows* 8商店应用开发的相关信息,请访问:http://msdn.microsoft.com/library/windows/apps/br211369
传感器
传感器的类型很多,但是我们感兴趣的是 Windows 8 需要的传感器,即加速器、陀螺仪、环境光传感器、指南针和 GPS。 Windows 8 通过对象导向的抽象来表现物理传感器。 为了操控传感器,程序员使用 API 与对象进行交互。
您可能已经注意到下图(图 2)中显示的对象要比实际硬件多。 Windows 通过利用多个物理传感器的信息,定义了某些“逻辑传感器”对象。 这称之为“Sensor Fusion”。
图 2: Windows 8 上支持的各种传感器
Sensor Fusion
物理传感器芯片具有一些固有的自然限制。 例如:
- 加速器测量线性加速,它是对联合的相对运动和地球重力进行测量。 如果您想要了解电脑的倾角,则您必须进行一些数学运算。
- 磁力计用于测量磁场的强度,会显示地球磁北极的位置。
这些测量都受固有偏移问题制约,可以使用陀螺仪中的原始数据进行校正。 这两项测量(扩展)取决于电脑与地球水平面的倾斜度。
如果您确实希望电脑的磁向与地球真正的北极一致(磁北极处于不同的位置,会随着时间的改变而移动),则需要对其进行校正。
Sensor Fusion(图 3)正在获取多个物理传感器(尤其是加速器、陀螺仪和磁力计)的原始数据、执行数学运算校正自然传感器限制、计算更适合人类使用的数据以及将这些数据以逻辑传感器抽象形式显示出来。 应用开发人员必须实施必要的转换,以将物理传感器数据转换为抽象传感器数据。 如果您的系统设计具有一个SensorHub,融合操作将发生在微控制器固件内。 如果您的系统设计中没有 SensorHub,则融合操作必须在 IHV 和/或 OEM 提供的一个或多个设备驱动程序中完成。
图 3: 通过组合来自多个传感器的输出进行传感器融合
识别传感器
如要操控传感器,您需要一个系统进行识别并指向它。 Windows Sensor Framework 定义了划分传感器的若干类别。 它还定义了若干特定的传感器类型。 表 1 列出了一些适用于您 Desktop 应用的传感器。
表 1: 传感器类型与类别
“All” | ||||||||
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* |
Windows 必需的传感器类型以粗体*显示:
- 加速器、陀螺仪、指南针和环境光是必需的“真正/物理”传感器
- 设备定向和倾角计是必需的“虚拟/融合”传感器(注意:指南针还包括融合增强/倾斜补偿数据)
- 如果您有一个 WWAN 广播,则 GPS 是必须的;否则 GPS 为可选
- Human Proximity 是必需列表中的常见选项,但是现在并不是必需的。
表 1 中所显示的类别和类型的名称都以人类可读的形式显示。 但是,在编程时,您将需要了解每种类型传感器的编程常量。 所有这些常量实际上仅仅是名为 GUID(全球唯一 ID)的编号。 以下的表 2 中是一些传感器类别和类型的样本、面向 Win32/COM 和 .NET 的常量名称以及他们基本的 GUID 值。
表 2: 一些常见传感器的常量和唯一的全球唯一 ID (GUID)。
标识符 | 常量 (Win32/COM) | 常量 (.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} |
这些都是最常用的 GUID--您还可以开发更多。 最初,您可能认为 GUID 无聊而且单调乏味,但是使用它们的一个最大原因就是: 可扩展性。 因为 API 不关注实际的传感器名称(它们仅传输 GUID),所以厂商可以为“增值”传感器创建新 GUID。
生成新的 GUID
微软在 Visual Studio* 中提供了一个可供任何人生成新 GUID 的工具。 图 4 显示了 Visual Studio 关于此操作的截图。 所有厂商必须要做的就是发布它们,这样无需更改 Microsoft API 或任意操作系统代码即可看到新功能了。
图 4: 为增值传感器定义新 GUID
使用传感器管理器对象
通过类型询问
您的应用寻找特定类型的传感器,如 Gyrometer3D。 传感器管理器询问电脑上显示的传感器硬件列表,然后返回绑定至该硬件的匹配对象的集合。 虽然传感器集合可能有 0 个、1 个或多个对象,但通常只有 1 个。 以下的 C++ 代码样本显示了使用传感器管理器对象的 GetSensorsByType方法搜索 3 轴陀螺仪,并在传感器集合中返回搜索结果。 注意:您必须首先 ::CoCreateInstance() the Sensor Manager Object。
[xhtml]// 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 motion sensors 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; } [/xhtml]
通过类别询问
您的应用可以通过类别寻找传感器,比如运动传感器。 传感器管理器询问电脑上显示的传感器硬件列表,然后返回绑定至该硬件的运动对象的集合。 SensorCollection 中可能有 0 个、1 个或多个对象。 在大多数电脑上,集合都具有 2 个运动对象。 Accelerometer3D 和 Gyrometer3D。
以下的 C++ 代码样本显示了使用传感器管理器对象的 GetSensorsByCategory方法搜索运动传感器,并在传感器集合中返回搜索结果。
[xhtml]// Additional includes for sensors #include #include #include // 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 sensors on the computer. 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; } [/xhtml]
通过 Category ll?询问
在实践中,适用于您应用的最有效方法就是在电脑上寻找所有传感器。 传感器管理器询问电脑上显示的传感器硬件列表,然后返回绑定至该硬件的所有对象的集合。 传感器集合中可能有 0 个、1 个或多个对象。 在大多数电脑上,集合都具有 7 个或以上对象。
C++ 没有调用 GetAllSensors,所以您必须使用 GetSensorsByCategory(SENSOR_CATEGORY_ALL, …)替代以下样本代码中所示的内容。
[xhtml]// Additional includes for sensors #include #include #include // 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_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; } [/xhtml]
传感器生命周期 – 进入 (Enter) 和离开 (Leave) 事件
在 Windows 上,与大多数硬件设备一样,传感器被视为即插即用设备。 首先您可能会问,“传感器是硬连接至电脑主板的,如果它们从未插入或拔下,我们为什么要担心即插即用呢?” 这可能发生在以下情形中:
1. 可能系统外部有基于 USB 的传感器,并将其插入 USB 端口。
2. 在连接和断开时,可能通过不可靠的无线接口(如蓝牙)或有线接口(如以太网)连接了传感器。
3. 如果 Windows Update 升级传感器的设备驱动程序,它们将显示为断开连接,然后再重新连接。
4. Windows 关闭(S4 或 S5)时,传感器显示为断开连接。
在传感器操作中,即插即用称之为 “进入”(Enter)事件,断开称之为“离开”(Leave)事件。 需要有灵活的应用来处理这两种事件。
“进入”事件回调
可能在传感器插入时您的应用已经处于运行状态。此时,传感器管理器会报告传感器“进入”事件。 注: 如果在您的应用开始运行时传感器已经插入,您将无法获取这些传感器的“进入”事件。 在 C++/COM 中,您必须使用 SetEventSink方法 hook 回调。 回调不仅仅是一个函数,它必须是从 ISensorManagerEvents继承并执行 IUnknown的整类函数。 ISensorManagerEvents接口必须执行回调函数:
STDMETHODIMP OnSensorEnter(ISensor *pSensor, SensorState state);
[xhtml]// 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; } [/xhtml]
Code: Hook “进入”事件的回调
以下是等同于“进入”回调的 C++/COM。 在该函数中,您可以从您的主循环中正常执行所有初始化步骤。 事实上,重构您的代码更为有效,这样您的主循环只调用 OnSensorEnter,模拟“进入”事件。
[xhtml]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; } [/xhtml]
代码: 回调“进入”事件
Leave Event
单个传感器报告何时发生“离开”事件(并非传感器管理器)。 代码与之前 hook “进入”事件的回调相同。
[xhtml]// 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; } [/xhtml]
代码: Hook“离开”事件的回调
OnLeave事件处理程序以参数形式接收离开传感器的 ID。
[xhtml]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; } [/xhtml]
代码: 回调“离开”事件
为您的应用挑选传感器
我们关心传感器是因为它们告知我们的内容。 不同类型的传感器向我们传达不同的信息。 微软将这些信息称之为“数据域”(Data Fields),它们集合在一个 SensorDataReport 中。 您的电脑可能(潜在)具有多种类型的传感器,会向您的应用传达您关心的信息。 您的应用可能并不关心是从哪个传感器中获得的信息,只要能够获得信息即可。
表 3 显示了 Win32/COM 和 .NET 最常用“数据域”的常量名称。与传感器标识符一样,这些常量只是代表大量数字的人类可读的名称。 除了微软之前预定义的“well known”数据域,还提供了数据域的扩展性。 还有很多其它“well known” ID 等待您的开发。
表 3: 数据域标识符常量
常量 (Win32/COM) | 常量 (.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 |
使得数据域标识符与传感器 ID 不同的原因是使用了名为 PROPERTYKEY 的数据类型。 一个 PROPERTYKEY 包括一个 GUID(类似于传感器的 GUID)以及一个名为“PID”的额外编号(属性 ID)。 您可能会注意到 PROPERTYKEY 的 GUID 部分对于同一类别的传感器是通用的。 数据域的所有值都具有本机数据类型,例如Boolean、unsigned char、int、float、double 等。
在 Win32/COM 中,数据域的值存储在名为 PROPVARIANT 的多态数据类型中。 在 .NET 中,有一个名为“对象”(object) 的 CLR(通用语言运行时)数据类型执行相同的操作。 您必须询问和/或将多态数据类型转换为“expected”/“documented”数据类型。
使用传感器的 SupportsDataField()方法检查传感器,获取感兴趣的数据域。 这是我们选择传感器时经常使用的编程术语。 根据您应用的使用模式,您可能仅需一个数据域子集,而并非全部。 选择您想要的传感器,基于它们是否支持您所需的数据域。 注意:您还需要使用类型转换从基本类传感器分配子类成员变量。
[xhtml]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; . . . } } [/xhtml]
代码: 使用传感器中的 SupportsDataField() 方法查看支持的数据域。
传感器属性
除了数据域,传感器还具有可用于辨识和配置的属性。 表 4 显示了最常用的属性。 与数据域类似,属性也有 Win32/COM 和 .NET 使用的常量名称,而且这些常量确实是下面的 PROPERTYKEY 数字。 属性可通过厂商扩展,还具有 PROPVARIANT 多态数据类型。 不同于数据域的只读特性,属性具有读/写能力。 它取决于单个传感器是否拒绝写入尝试。 作为一名应用开发人员,您需要执行写-读-验证,因为尝试写入失败时不会发生异常情况。
表 4: 常用的传感器属性和 PID
标识 (Win32/COM) | 标识 (.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 |
设置传感器敏感度
敏感度设置可能是最有用的传感器属性。 可用于分配控制或过滤发送至主机计算机的 SensorDataReports 数量的阈值。 流量可通过这种方式得以降低: 仅发出那些真正会干扰主机 CPU 的 DataUpdated 事件。 微软定义这一敏感度属性数据类型的方式有稍许不同。 它是一个容器类型,在 Win32/COM中称之为“IPortableDeviceValues”,在 .NET 中称之为“ensorPortableDeviceValues”。 容器中包含一个元组集合,其中每个都是一个数据域 PROPERTYKEY,随后是该数据域的敏感度值。 敏感度通常使用与匹配数据相同的测量单位和数据类型。
// 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 代码显示如下。 注意:您必须向 RequestPermissions()提供一个 (ISensorCollection *) 参数。
[xhtml]// 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; } } [/xhtml]
传感器数据更新
传感器通过发出名为 DataUpdated 的事件来报告数据。 实际的数据域在 SensorDataReport 内打包,被传输至所有附带的 DataUpdated 事件处理程序中。 您的应用通过将 hook 一个回调处理程序至传感器的 DataUpdated 事件获取 SensorDataReport。 事件发生在 Windows Sensor Framework 线程,该线程与用于更新您应用 GUI 的消息泵线程不同。 因此,您将需要将 SensorDataReport 从事件处理程序 (Als_DataUpdate) 传至可以在 GUI 线程环境中执行的单独的处理程序 (Als_UpdateGUI)。 在 .NET 中,此类处理程序称之为委托函数。
以下示例显示了委托函数的实现。 在 C++/COM 中,您必须使用 SetEventSink 方法 hook 回调。 回调不仅仅是一个函数,它必须是从 ISensorEvents 继承并执行 IUnknown 的整类函数。 ISensorEvents 接口必须执行回调函数:
[xhtml]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; } [/xhtml]
代码: 为传感器设置一个 COM 事件接收器
DataUpdated 事件处理程序以参数形式接收 SensorDataReport(以及初始化事件的传感器)。 它调用表格中的 Invoke() 方法将这些条目转至委托函数。 GUI 线程运行转至其 Invoke 队列的委托函数并将参数传输至该函数。 委托函数将 SensorDataReport 的数据类型转换为所需的子类,获得数据域访问。 数据域是使用 SensorDataReport 对象中的 GetDataField()方法提取的。 每个数据域都必须将类型转换至它们的“expected”/“documented”数据类型(从使用 GetDataField()方法返回的普通/多态数据类型)。 然后应用会在 GUI 中排列并显示数据。
OnDataUpdated 事件处理程序以参数形式接收 SensorDataReport(以及初始化事件的传感器)。 数据域是使用 SensorDataReport 对象中的 GetSensorValue()方法提取的。 每个数据域需具有自己的 PROPVARIANT,以检查它们的“expected”/“documented”数据类型。 然后应用会在 GUI 中排列并显示数据。 不需要使用同等的 C# 委托。 这是因为所有 C++ GUI 函数(如此处显示的 SetWindowText())使用 Windows 消息传递将 GUI 更新转至 GUI 线程/消息循环(您主窗口或对话框的 WndProc)。
[xhtml]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; } [/xhtml]
您可以仅参考 SensorDataReport 对象中的属性从 SensorDataReport 中提取数据域。 这仅适用于 .NET API(在 Win32/COM API 中,您必须使用 GetDataField 方法),及特定 SensorDataReport 子类的“well known”或“expected”数据域。 可以(使用“动态数据域”)为以下驱动程序固件“搭载” SensorDataReports 内的任意“extended/unexpected”数据域。 为了提取它们,您必须使用 GetDataField 方法。
在 Metro 风格应用中使用传感器
不同于 Desktop 模式,Metro/WinRT 传感器 API 对每个传感器遵循一个通用模板。
- 这通常是一个名为 ReadingChanged的单个事件,使用包含 Reading 对象(具有实际数据)的 xxxReadingChangedEventArgs 调用回调。 (加速器是一个例外,它还具有 Shaken 事件)。
- 传感器类的硬件绑定实例使用 GetDefault()方式检索。
- 可以通过 GetCurrentReading()方法执行轮询。
Metro 风格应用一般使用JavaScript* 或 C# 编写。 API 有不同的语言绑定,这导致 API 名称中的大写稍有不同以及事件处理方式也稍有不同。 简化的 API 更易于使用,表 5 中列出了利弊。
表 5: Metro 风格应用的传感器 API 以及利弊
特性 | 利 | 弊 |
SensorManager | 没有 SensorManager 需要处理。 应用使用 GetDefault() 方式获取传感器类实例。 |
|
事件 | 应用仅关注 DataUpdated 事件。 |
|
传感器属性 | 应用仅关注 ReportInterval 属性。 |
|
数据报告属性 | 应用仅关注仅存在于每个传感器中的少数预定义数据域。 |
|
总结
Windows 8 API 支持开发人员在传统的 Desktop 模式和全新的 Windows* 8商店应用接口下在不同的平台上使用传感器。 在本文中,我们概述了开发人员在 Windows 8 内创建应用可用的传感器 API,重点是 Desktop 模式应用的 API 和代码样本。
附录
不同外形的坐标系统
Windows API 通过与 HTML5 标准(和Android*)兼容的方式报告 X、Y 和 Z 轴。 它还称之为“ENU”系统,因为 X 面向虚拟的“东”(E)、Y 面向虚拟的“北(N)、而 Z 面向“上(U)。
如要弄清楚旋转的方向,请使用“右手定则”。
*右手拇指指向其中一个轴的方向。
* 沿该轴正角旋转将顺着您手指的曲线。
这些是面向平板电脑或者手机(左)和蛤壳式电脑的 X、Y 和 Z 轴。 对于更复杂的外形(如可转换为平板的蛤壳式),“标准”方向是其处于“TABLET”(平板)状态时。
如果您想要开发一个导航应用(如 3D 空间游戏),您需要在您的程序中从“ENU”转换。 可通过矩阵乘法轻松完成该操作。 Direct3D* 和 OpenGL* 等图形库都有可处理这一操作的 API。
资源
Win 7 传感器 API: http://msdn.microsoft.com/library/windows/desktop/dd318953(VS.85).aspx
传感器 API 编程指南: http://msdn.microsoft.com/en-us/library/dd318964(v=vs.85).aspx
集成运动与方向传感器: http://msdn.microsoft.com/en-us/library/windows/hardware/br259127.aspx
作者简介
Deepak Vembar
Deepak Vembar 是英特尔实验室交互与体验研究 (IXR) 事业部的一位研究员。 他的研究主要关注计算机图形交互和人机交互,包括实时图形、虚拟现实、触觉、眼睛追踪和用户交互等领域。 在进入英特尔实验室之前,Deepak 是英特尔软件与服务事业部 (SSG) 的一位软件工程师,与电脑游戏开发人员一起针对英特尔平台优化游戏、传授异构平台优化课程和指南以及使用游戏演示编写大学课程(作为教学媒体在学校课程中使用)。
Deepak 拥有克莱姆森大学计算学院的博士学位,当时他主要研究如何使用计算机培训模拟器改善飞机检查。 他的论文“Visuohaptic simulation of a borescope for aircraft engine inspection”讨论了将现成的触觉设备与计算机模拟器相结合,培训新手巡查员正确检查飞机引擎。 他还拥有克莱姆森大学的学士学位,他的论文“Towards Improved Behavioral Realism in Avatars”主要研究了使用 2 个电磁追踪仪对手势进行识别和分类。 他还与他人合作撰写了 13 篇论文并在在多场学术会议中讨论和发表,包括 IEEE 3DUI、Graphics Interface 和游戏开发者大会 (GDC)。
声明
本文件中包含关于英特尔产品的信息。 本文不代表英特尔公司或其它机构向任何人明确或隐含地授予任何知识产权。 除相关产品的英特尔销售条款与条件中列明之担保条件以外,英特尔公司不对销售和/或使用英特尔产品做出其它任何明确或隐含的担保,包括对适用于特定用途、适销性,或不侵犯任何专利、版权或其它知识产权的担保。
除非经过英特尔的书面同意认可,英特尔的产品无意被设计用于或被用于以下应用:即在这样的应用中可因英特尔产品的故障而导致人身伤亡。
英特尔有权随时更改产品的规格和描述而毋需发出通知。 设计者不应信赖任何英特产品所不具有的特性,设计者亦不应信赖任何标有保留权利“或未定义”说明或特性描述。 英特尔保留今后对其定义的权利,对于因今后对其进行修改所产生的冲突或不兼容性概不负责。 此处提供的信息可随时改变而毋需通知。 请勿根据本文件提供的信息完成一项产品设计。
本文件所描述的产品可能包含使其与宣称的规格不符的设计缺陷或失误。 这些缺陷或失误已收录于勘误表中,可索取获得。
在发出订单之前,请联系当地的英特尔营业部或分销商以获取最新的产品规格。
如欲获得本文或其它英特尔文献中提及的带订单编号的文档副本,可致电 1-800-548-4725,或访问: http://www.intel.com/design/literature.htm
性能测试中的软件和工作负载可能仅在英特尔微处理器上针对性能进行了优化。 SYSmark 和 MobileMark 等性能测试使用特定的计算机系统、组件、软件、操作和功能进行测量。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。
本文档转载的软件源代码根据软件许可证提供,并且只能在许可证条款下使用或复制。
英特尔和 Intel 标识是英特尔公司在美国和/或其他国家(地区)的商标。
英特尔公司 2012 年版权所有。 所有权利保留。
*文中涉及的其它名称及商标属于各自持有者。
性能声明
有关性能声明和性能指标评测结果的完整信息,请访问: www.intel.com/benchmarks
优化声明
优化声明 |
英特尔编译器针对非英特尔微处理器的优化程度可能与英特尔微处理器相同(或不同)。 这些优化包括 SSE2、SSE3 和 SSSE3 指令集以及其它优化。 对于在非英特尔制造的微处理器上进行的优化,英特尔不对相应的可用性、功能或有效性提供担保。 该产品中依赖于处理器的优化仅适用于英特尔微处理器。 部分非针对英特尔微体系架构的优化也为英特尔微处理器保留了下来。 如欲了解更多有关本声明所涉及的特定指令集的信息,请参阅适用产品的《用户和参考指南》。 声明版本 #20110804 |
要了解有关编译器优化的更多信息,请参阅优化声明。