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





5.00/5 (1投票)
本指南为开发人员提供了 Microsoft Windows* 8 传感器应用程序编程接口 (API) 的概述,适用于桌面和 Windows UI 风格的应用程序,特别关注 Windows 8 桌面模式中可用的各种传感器功能。
引言
本指南为开发人员提供了 Microsoft Windows* 8 传感器应用程序编程接口 (API) 的概述,适用于桌面和 Windows UI 风格的应用程序,特别关注 Windows* 8 桌面模式中可用的各种传感器功能。我们通过包含 Windows* 8 中的一些常见传感器(如加速度计、磁力计和陀螺仪)来总结了可以创建交互式应用程序的 API。
Content
- 引言
- Windows* 8 的编程选择
- 传感器
- 识别传感器
- 使用传感器管理器对象
- 传感器生命周期 – 进入和离开事件
- 为您的应用选择传感器
- 在 Windows UI 应用中使用传感器
- 摘要
- 附录
- 注意事项
- 优化通知
Windows* 8 的编程选择
开发人员有多种 API 选择来编程 Win8 上的传感器。新的触摸友好型应用环境,称为“Windows UI 应用”,如图 1 左侧所示。Windows UI 应用唯一可以使用的 API 库是全新的 WinRT。WinRT 传感器 API 是整个 WinRT 库的一部分。更多详细信息,请参阅:http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.sensors.aspx
传统的 Win Forms 或 MFC 风格的应用程序,如右侧所示,现在称为“桌面应用程序”,因为它们在桌面 Windows 管理器环境中运行。桌面应用程序可以使用原生 Win32/COM API 或 .NET 风格的 API。
在这两种情况下,这些 API 都通过一个名为 Windows 传感器框架的 Windows 中间件组件。Windows 传感器框架定义了传感器对象模型。不同的 API 以略微不同的方式“绑定”到该对象模型。
本文档稍后将讨论桌面和 Windows UI 应用程序开发的差异。为简洁起见,我们将只考虑桌面应用程序开发。对于 Windows UI 应用程序开发,请参阅 http://msdn.microsoft.com/library/windows/apps/br211369
传感器
传感器种类繁多,但我们感兴趣的是 Windows* 8 所需的传感器,即加速度计、陀螺仪、环境光传感器、指南针和 GPS。Windows* 8 使用面向对象的抽象来表示物理传感器。为了操作传感器,程序员使用 API 与对象交互。
您可能已经注意到,下面(图 2)显示的对象比实际硬件多。Windows 通过组合来自多个物理传感器的信息来定义一些“逻辑传感器”对象。这称为“传感器融合”。
传感器融合
物理传感器芯片有一些固有的自然局限性。例如:
- 加速度计测量线性加速度,这是相对运动和地球重力合力的测量。如果您想知道计算机的倾斜度,您需要进行一些数学计算。
- 磁力计测量磁场强度,它指示地球磁北极的位置。
这些测量值受固有漂移问题的影响,这可以通过使用陀螺仪的原始数据来纠正。这两种测量值都(按比例)取决于计算机相对于地平面的倾斜度。
如果您真的想知道计算机相对于地球真北极的航向(磁北极位置不同且随时间移动),您需要对此进行校正。
传感器融合(图 3)是指从多个物理传感器(特别是加速度计、陀螺仪和磁力计)获取原始数据,执行数学计算以纠正自然传感器局限性,计算更适合人类使用的数据,并将其表示为逻辑传感器抽象。应用程序开发人员必须实现必要的转换,将物理传感器数据转换为抽象传感器数据。如果您的系统设计包含传感器集线器,则融合操作将在微控制器固件内部进行。如果您的系统设计不包含传感器集线器,则融合操作必须在 IHV 和/或 OEM 提供的一个或多个设备驱动程序内部完成。
识别传感器
要操纵传感器,您需要一个系统来识别和引用它。Windows 传感器框架定义了传感器分组的许多类别。它还定义了大量的特定传感器类型。表 1 列出了可用于您的桌面应用程序的一些传感器。
生物识别 | 电气 | 环境 | 光线 | Location | 机械 | 运动 | Orientation | 扫描器 |
人体存在 | 电容 | 大气压 | 环境光 | 广播 | 布尔开关 | 加速度计 1D | 指南针 1D | 条形码 |
人体接近* | Current | 湿度 | GPS* | 布尔开关阵列 | 加速度计 2D | 指南针 2D | RFID | |
触摸 | 电能 | 温度 | 静态 | 力 | 加速度计 3D | 指南针 3D | ||
电感 | 风向 | 多值开关 | 陀螺仪 1D | 设备方向* | ||||
电位计 | 风速 | 压力 | 陀螺仪 2D | 距离 1D | ||||
电阻 | 应变 | 陀螺仪 3D | 距离 2D | |||||
电压 | 权重 | 运动检测器 | 距离 3D | |||||
速度计 | 倾斜计 1D | |||||||
倾斜计 2D | ||||||||
倾斜计 3D* |
Windows* 8 所需的传感器类型以粗体*字显示
- 加速度计、陀螺仪、指南针和环境光是必需的“真实/物理”传感器
- 设备方向和倾斜计是必需的“虚拟/融合”传感器(请注意,指南针还包括融合增强/倾斜补偿数据)
- 如果您有 WWAN 无线电,GPS 是必需传感器,否则 GPS 是可选的
- 人体接近传感器是经常被提及可能添加到所需列表中的传感器,但目前并非必需。
表 1 中所示的类别和类型名称是易于人类阅读的形式。但是,对于编程,您需要了解每种传感器类型的编程常量。所有这些常量实际上只是称为 GUID(全局唯一 ID)的数字。下面,在表 2 中,是一些传感器类别和类型的示例,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} |
类型指南针 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} |
这些是最常用的 GUID——还有更多您可以探索。起初您可能会认为 GUID 愚蠢而繁琐,但使用它们有一个很好的理由:可扩展性。由于 API 不关心实际的传感器名称(它们只是传递 GUID),因此供应商可以为“增值”传感器发明新的 GUID。
生成新的 GUID
Microsoft 在 Visual Studio* 中提供了一个工具,允许任何人生成新的 GUID。图 4 显示了 Visual Studio 执行此操作的屏幕截图。供应商只需发布它们,就可以公开新功能,而无需更改 Microsoft API 或任何操作系统代码。
使用传感器管理器对象
按类型查询
您的应用可以请求特定类型的传感器,例如 Gyrometer3D。传感器管理器会查询计算机上存在的传感器硬件列表,并返回与该硬件绑定的匹配对象集合。尽管传感器集合可能包含 0、1 或更多对象,但通常只有一个。下面是一个 C++ 代码示例,说明了 Sensor Manager 对象的 GetSensorsByType
方法的用法,用于搜索 3 轴陀螺仪并将其返回到 Sensor Collection 中。请注意,您必须首先 ::CoCreateInstance()
Sensor Manager 对象。
// 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;
}
按类别查询
您的应用可以按类别请求传感器,例如所有运动传感器。传感器管理器会查询计算机上的传感器硬件列表,并返回与该硬件绑定的运动对象集合。SensorCollection
中可能包含 0、1 或更多对象。在大多数计算机上,该集合将包含两个运动对象:Accelerometer3D 和 Gyrometer3D。
下面的 C++ 代码示例说明了 Sensor Manager 对象的 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 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;
}
按类别“所有”查询
实际上,最有效的方法是您的应用请求计算机上的所有传感器。传感器管理器会查询计算机上存在的传感器硬件列表,并返回与该硬件绑定的所有对象集合。传感器集合中可能包含 0、1 或更多对象。在大多数计算机上,该集合将包含七个或更多对象。
C++ 没有 GetAllSensors
调用,因此您必须改用 GetSensorsByCategory(SENSOR_CATEGORY_ALL, …)
,如下面的示例代码所示。
// 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_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;
}
传感器生命周期 – 进入和离开事件
在 Windows 上,与大多数硬件设备一样,传感器被视为即插即用设备。起初您可能会说,“传感器是计算机主板上的硬连线设备,如果它们永远不会被插入或拔出,我们为什么要担心即插即用?” 有几种不同的情况会发生:
- 可能存在外部 USB 传感器并插入 USB 端口。
- 可以设想存在通过不可靠的无线接口(如蓝牙)或有线接口(如以太网)连接的传感器,其中会发生连接和断开。
- 如果 Windows Update 升级传感器的设备驱动程序,它们会表现为断开连接然后重新连接。
- 当 Windows 关闭(到 S4 或 S5)时,传感器会表现为断开连接。
在传感器上下文中,即插即用连接称为进入事件,断开连接称为离开事件。弹性应用需要能够处理这两种情况。
进入事件回调
您的应用可能在传感器插入时就已经运行。当这种情况发生时,传感器管理器会报告传感器进入事件。注意:如果您的应用开始运行时传感器已经插入,您将不会收到这些传感器的进入事件。在 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;
}
为您的应用选择传感器
我们关心传感器是因为它们告诉我们的信息。不同类型的传感器告诉我们不同的事情。Microsoft 将这些信息称为数据字段,它们被分组在 SensorDataReport 中。您的计算机可能(潜在地)有多种类型的传感器可以告诉您的应用您关心的信息。您的应用可能不关心从哪个传感器获取信息,只要能获取到即可。
表 3 显示了 Win32/COM 和 .NET 最常用的数据字段的常量名称。就像传感器标识符一样,这些常量只是人类可读的名称,实际上它们是底层的巨大数字。这为数据字段的扩展提供了可能性,超出了 Microsoft 预定义的“已知”数据字段。还有许多其他“已知”ID 供您探索。
常量 (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(类似于传感器所拥有的)和一个称为“PID”(属性 ID)的额外数字组成。您可能会注意到,同一类别中的传感器,其 PROPERTYKEY 的 GUID 部分是相同的。数据字段对其所有值都有一个原生数据类型,例如布尔值、无符号字符、整数、浮点数、双精度浮点数等。
在 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;
.
.
.
}
}
传感器属性
除了数据字段,传感器还具有可用于识别和配置的属性。表 4 显示了最常用的属性。与数据字段一样,属性在 Win32/COM 和 .NET 中具有常量名称,而这些常量实际上是底层的 PROPERTYKEY 数字。属性可由供应商扩展,并且也具有 PROPVARIANT 多态数据类型。与只读的数据字段不同,属性具有读/写能力。是否拒绝写入尝试由单个传感器自行决定。作为应用程序开发人员,您需要执行写入-读取-验证操作,因为写入尝试失败时不会抛出异常。
识别(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 | 状态 | {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) | 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 事件。Microsoft 定义此灵敏度属性数据类型的方式有点不寻常。它是一个容器类型,在 Win32/COM 中称为 IPortableDeviceValues,在 .NET 中称为 SensorPortableDeviceValues
。此容器包含一个元组集合,每个元组都是一个数据字段 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 代码如下所示。请注意,您必须提供一个 (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 传感器框架线程中,这是一个与用于更新您应用 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;
}
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(在 Win32/COM API 中,您必须使用 GetDataField
方法),并且适用于该特定 SensorDataReport 子类的“已知”或“预期”数据字段。底层驱动程序/固件可以通过“动态数据字段”将任何“扩展/意外”数据字段“附带”在 SensorDataReports 中。要提取这些数据字段,您必须使用 GetDataField 方法。
在 Windows UI 应用中使用传感器
与桌面模式不同,Windows UI/WinRT 传感器 API 为每个传感器遵循一个通用模板:
- 通常有一个名为
ReadingChanged
的事件,它使用包含实际数据的 Reading 对象的 xxxReadingChangedEventArgs 调用回调。(加速度计是一个例外;它还有一个 Shaken 事件)。 - 传感器类的硬件绑定实例使用
GetDefault()
方法检索。 - 可以使用
GetCurrentReading()
方法进行轮询。
Windows UI 应用程序通常用 JavaScript* 或 C# 编写。API 有不同的语言绑定,导致 API 名称的大小写外观略有不同,以及事件处理方式略有不同。简化的 API 更易于使用,优点和缺点列在表 5 中。
功能 | 优点 | 缺点 |
SensorManager | 无需处理 SensorManager。应用程序使用 GetDefault() 方法获取传感器类的实例。 |
|
事件 | 应用程序只关心 DataUpdated 事件。 |
|
传感器属性 | 应用程序只关心 ReportInterval 属性。 |
|
数据报告属性 | 应用程序只关心每个传感器特有的少数预定义数据字段。 |
|
摘要
Windows* 8 API 为开发人员提供了机会,可以利用传统桌面模式和新的 Windows UI 应用程序界面下不同平台上可用的传感器。在本文档中,我们概述了可供希望使用 Windows* 8 创建应用程序的开发人员使用的传感器 API,重点介绍了桌面模式应用程序的 API 和代码示例。
附录
不同外形尺寸的坐标系
Windows API 以与 HTML5 标准(和 Android*)兼容的方式报告 X、Y 和 Z 轴。它也称为“ENU”系统,因为 X 面向虚拟“东”(East),Y 面向虚拟“北”(North),Z 面向“上”(Up)。
要确定旋转方向,请使用“右手定则”:
* 将右手拇指指向其中一个轴的方向。
* 围绕该轴的正角度旋转将沿着手指的弯曲方向。
这些是平板电脑或手机(左)以及翻盖式电脑(右)的 X、Y 和 Z 轴。对于更深奥的外形尺寸(例如,可转换为平板电脑的翻盖式电脑),“标准”方向是处于平板电脑状态时。
如果您打算开发导航应用程序(例如,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