65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2012 年 9 月 6 日

CPOL

20分钟阅读

viewsIcon

33895

本指南为开发人员提供了 Microsoft Windows* 8 传感器应用程序编程接口 (API) 的概述,适用于桌面和 Windows UI 风格的应用程序,特别关注 Windows 8 桌面模式中可用的各种传感器功能。

引言

本指南为开发人员提供了 Microsoft Windows* 8 传感器应用程序编程接口 (API) 的概述,适用于桌面和 Windows UI 风格的应用程序,特别关注 Windows* 8 桌面模式中可用的各种传感器功能。我们通过包含 Windows* 8 中的一些常见传感器(如加速度计、磁力计和陀螺仪)来总结了可以创建交互式应用程序的 API。

Content

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 以略微不同的方式“绑定”到该对象模型。

图 1:Windows* 8 中的 Windows UI 和桌面传感器框架

本文档稍后将讨论桌面和 Windows UI 应用程序开发的差异。为简洁起见,我们将只考虑桌面应用程序开发。对于 Windows UI 应用程序开发,请参阅 http://msdn.microsoft.com/library/windows/apps/br211369

传感器

传感器种类繁多,但我们感兴趣的是 Windows* 8 所需的传感器,即加速度计、陀螺仪、环境光传感器、指南针和 GPS。Windows* 8 使用面向对象的抽象来表示物理传感器。为了操作传感器,程序员使用 API 与对象交互。

您可能已经注意到,下面(图 2)显示的对象比实际硬件多。Windows 通过组合来自多个物理传感器的信息来定义一些“逻辑传感器”对象。这称为“传感器融合”。

图 2:Windows* 8 支持的不同传感器

传感器融合

物理传感器芯片有一些固有的自然局限性。例如:

  • 加速度计测量线性加速度,这是相对运动和地球重力合力的测量。如果您想知道计算机的倾斜度,您需要进行一些数学计算。
  • 磁力计测量磁场强度,它指示地球磁北极的位置。

这些测量值受固有漂移问题的影响,这可以通过使用陀螺仪的原始数据来纠正。这两种测量值都(按比例)取决于计算机相对于地平面的倾斜度。

如果您真的想知道计算机相对于地球真北极的航向(磁北极位置不同且随时间移动),您需要对此进行校正。

传感器融合(图 3)是指从多个物理传感器(特别是加速度计、陀螺仪和磁力计)获取原始数据,执行数学计算以纠正自然传感器局限性,计算更适合人类使用的数据,并将其表示为逻辑传感器抽象。应用程序开发人员必须实现必要的转换,将物理传感器数据转换为抽象传感器数据。如果您的系统设计包含传感器集线器,则融合操作将在微控制器固件内部进行。如果您的系统设计不包含传感器集线器,则融合操作必须在 IHV 和/或 OEM 提供的一个或多个设备驱动程序内部完成。

图 3:通过组合多个传感器的输出进行传感器融合

识别传感器

要操纵传感器,您需要一个系统来识别和引用它。Windows 传感器框架定义了传感器分组的许多类别。它还定义了大量的特定传感器类型。表 1 列出了可用于您的桌面应用程序的一些传感器。

生物识别 电气 环境 光线 Location 机械 运动 Orientation 扫描器
人体存在 电容 大气压 环境光 广播 布尔开关 加速度计 1D 指南针 1D 条形码
人体接近* Current 湿度   GPS* 布尔开关阵列 加速度计 2D 指南针 2D RFID
触摸 电能 温度   静态 加速度计 3D 指南针 3D  
  电感 风向     多值开关 陀螺仪 1D 设备方向*  
  电位计 风速     压力 陀螺仪 2D 距离 1D  
  电阻       应变 陀螺仪 3D 距离 2D  
  电压       权重 运动检测器 距离 3D  
            速度计 倾斜计 1D  
              倾斜计 2D  
              倾斜计 3D*  
表 1:传感器类型和类别

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}
表 2:一些常见传感器的常量和唯一的全局唯一 ID (GUID)

这些是最常用的 GUID——还有更多您可以探索。起初您可能会认为 GUID 愚蠢而繁琐,但使用它们有一个很好的理由:可扩展性。由于 API 不关心实际的传感器名称(它们只是传递 GUID),因此供应商可以为“增值”传感器发明新的 GUID。

生成新的 GUID

Microsoft 在 Visual Studio* 中提供了一个工具,允许任何人生成新的 GUID。图 4 显示了 Visual Studio 执行此操作的屏幕截图。供应商只需发布它们,就可以公开新功能,而无需更改 Microsoft API 或任何操作系统代码。

图 4:为增值传感器定义新的 GUID

使用传感器管理器对象

按类型查询

您的应用可以请求特定类型的传感器,例如 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 上,与大多数硬件设备一样,传感器被视为即插即用设备。起初您可能会说,“传感器是计算机主板上的硬连线设备,如果它们永远不会被插入或拔出,我们为什么要担心即插即用?” 有几种不同的情况会发生:

  1. 可能存在外部 USB 传感器并插入 USB 端口。
  2. 可以设想存在通过不可靠的无线接口(如蓝牙)或有线接口(如以太网)连接的传感器,其中会发生连接和断开。
  3. 如果 Windows Update 升级传感器的设备驱动程序,它们会表现为断开连接然后重新连接。
  4. 当 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
表 3:数据字段标识符常量

数据字段标识符与传感器 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;
        .
        .
        .
    }
}
代码:使用传感器的 SupportsDataField() 方法检查支持的数据字段

传感器属性

除了数据字段,传感器还具有可用于识别和配置的属性。表 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
表 4:常用传感器属性和 PID

设置传感器灵敏度

灵敏度设置可能是传感器最有用的属性。它可用于分配一个阈值,以控制或过滤发送到主机的 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;
}
代码:为传感器设置 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(在 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() 方法获取传感器类的实例。
  • 无法搜索任意传感器实例。如果计算机上存在多个特定传感器类型,您将只会看到“第一个”。
  • 无法通过 GUID 搜索任意传感器类型或类别。供应商增值扩展无法访问。
事件 应用程序只关心 DataUpdated 事件。
  • 应用程序无法访问 Enter、Leave、StatusChanged 或任意事件类型。供应商增值扩展无法访问。
传感器属性 应用程序只关心 ReportInterval 属性。
  • 应用程序无法访问其他属性,包括最有用的属性:Sensitivity。
  • 除了操作 ReportInterval 属性外,Windows UI 应用程序无法调整或控制数据报告的流速。
  • 应用程序无法通过 PROPERTYKEY 访问任意属性。供应商增值扩展无法访问。
数据报告属性 应用程序只关心每个传感器特有的少数预定义数据字段。
  • 应用程序无法访问其他数据字段。如果传感器在数据报告中“附加”了 Windows UI 应用程序预期之外的其他已知数据字段,则这些数据字段无法访问。
  • 应用程序无法通过 PROPERTYKEY 访问任意数据字段。供应商增值扩展无法访问。
  • 应用程序无法在运行时查询传感器支持哪些数据字段。它只能假定 API 预定义的数据字段。
表 5:Windows UI 应用程序传感器 API 的优缺点

摘要

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

© . All rights reserved.