Ultrabook™ およびタブレット向け Windows* 8 センサーの開発ガイド

同カテゴリーの次の記事

Ultrabook™ デバイスおよびタッチ対応デスクトップ・アプリケーションの設計

この記事は、インテル® デベロッパー・ゾーンに掲載されている「Ultrabook™ and Tablet Windows* 8 Sensors Development Guide」の日本語参考訳です。


はじめに

この記事では、Windows* 8 のデスクトップ・モードで利用可能な各種センサー機能に注目して、Windows* 8 デスクトップおよび Windows* ストア・アプリケーション向け Sensor アプリケーション・プログラミング・インターフェイス (API) の概要を説明します。加速度計、磁力計、ジャイロスコープなど、Windows* 8 で利用できる一般的なセンサーを用いてインタラクティブなアプリケーションを作成する API を紹介します。

Windows* 8 プログラミングにおける選択肢

Windows* 8 のセンサーを用いたプログラミングには、さまざまな API が提供されています。図 1 の左側は “Windows* ストアアプリ” と呼ばれる新しいタッチ・フレンドリーなアプリケーション環境です。Windows* ストアアプリでは、Windows 8 で新たに導入された WinRT (Windows* Runtime) と呼ばれる API ライブラリーのみ利用できます。WinRT の Sensor API は、WinRT ライブラリーに含まれています。詳細は、MSDN の Sensor API ライブラリーの説明 (http://msdn.microsoft.com/ja-jp/library/windows/apps/windows.devices.sensors.aspx) を参照してください。

従来の Win フォームや MFC 形式のアプリケーションは、デスクトップ・ウィンドウ・マネージャー環境で動作するため、「デスクトップ・アプリケーション」と呼ばれるようになりました。デスクトップ・アプリケーションは、ネイティブ Win32*/COM API、.NET API、または WinRT API のサブセットのいずれかを使用できます。


図 1 Windows* 8 の Windows* ストアアプリおよびデスクトップ・アプリのセンサー・フレームワーク

次に、デスクトップ・アプリケーションで利用可能な 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 クリップボードへのアクセスと監視)

デスクトップ・アプリケーションと Windows* ストア・アプリケーションのいずれの場合も、API は Windows* Sensor Framework と呼ばれる Windows* のミドルウェア・コンポーネントを利用します。Windows* Sensor Framework はセンサー・オブジェクト・モデルを定義します。オブジェクト・モデルへのバインド方法は API ごとに多少異なります。

デスクトップ・アプリケーションと Windows* ストア・アプリケーションの開発における相違点は後述します。ここでは、デスクトップ・アプリケーションの開発について説明します。Windows* ストア・アプリケーションの開発については、http://msdn.microsoft.com/library/windows/apps/br211369 を参照してください。

センサー

さまざまなセンサーがありますが、ここでは Windows* 8 で利用可能な加速度計、ジャイロスコープ、環境光センサー、コンパス、および GPS に注目します。Windows* 8 は、物理センサーをオブジェクト指向の抽象化によって表現します。センサーを操作するには、API を通じてオブジェクトを利用します。次の表は、Windows* 8 デスクトップ・アプリケーションおよび Windows* ストア・アプリケーションからセンサーにアクセスする方法を示します。

表 1 Windows* 8 開発環境の機能一覧

Windows* 8 デスクトップ・アプリ Windows* ストアアプリ
機能/ツールセット C++ C#/VB JavaScript/HTML5 Unity* C++、C#、VB と XAML、JavaScript/HTML5 Unity*
傾きセンサー (加速度計、傾斜計、ジャイロメーター) 対応予定
光センサー 対応予定
NFC
GPS

次の図 2 から、ハードウェアよりも多くのオブジェクトが存在することが分かります。Windows* では、複数の物理センサーからのデータを組み合わせて「論理センサー」オブジェクトを定義しています。これは「センサー・フュージョン」と呼ばれます。


図 2 Windows* 8 でサポートされている各種センサー

センサー・フュージョン

物理センサーチップには、固有の制約があります。次に例を示します。

  • 加速度計は線形加速度 (相対運動と重力の組み合わせ) を計測します。マシンの傾きを知りたい場合は、数値計算を行う必要があります。
  • 磁力計は磁力の大きさ (磁北極の位置) を計測します。

これらの計測ではドリフト (飛散) の問題が発生しますが、これはジャイロのローデータを用いて補正できます。どちらの計測も地表に対するマシンの傾きに依存します。例えば、真北極 (磁北極の位置は真北極とは異なり、時とともに移動しています) に対するマシンの方位を取得するには、補正する必要があります。

センサー・フュージョン (図 3) とは、複数の物理センサー (特に加速度計、ジャイロ、磁力計) からローデータを取得し、センサー固有の制約を補正する数値計算を行い、さらに人が使用できるデータに変換して論理センサーを抽象化します。アプリケーション開発者は、物理センサーデータから抽象化センサーデータへ変換する処理を実装しなければなりません。システムに SensorHub がある場合、フュージョン操作はマイクロコントローラー・ファームウェア内で行われますが、ない場合は IHV や OEM により提供されるデバイスドライバー内で行われます。


図 3 複数のセンサーのデータを組み合わせたセンサー・フュージョン

センサーの識別

センサーを操作するには、センサーを識別し、参照する必要があります。Windows* Sensor Framework は、センサーの分類カテゴリーと特定のセンサータイプを定義しています。表 2 は、デスクトップ・アプリケーションで利用可能なセンサーの一例です。

表 2 センサーのタイプとカテゴリー 

Windows* 8 の必須のセンサータイプは太字で示しています。

  • 加速度計、ジャイロ、コンパス、環境光は、”実在/物理” センサーです。
  • デバイスの向きと傾斜計は “仮想/フュージョン” センサーです (コンパスには、フュージョンにより拡張されたデータ/傾き補正済みデータも含まれます)。
  • GPS はワイアレス WAN が存在する場合のみ必須で、そうでない場合はオプションです。
  • 人感センサーは必須ではありませんが、今後必須になる可能性が高いでしょう。

すべてのセンサー名 (定数) にはグローバル一意識別子 (GUID) があります。表 3 に Win32*/COM および .NET のセンサーのカテゴリー、タイプ、名前 (定数)、GUID を示します。

表 3 センサー名とグローバル一意識別子 (GUID) の例

上記は、最もよく使用される GUID で、このほかにも多数の GUID があります。GUID を利用するのは面倒に思われがちですが、「拡張性」という利点があります。API にとって実際のセンサー名は関係ないため (GUID のみを引き渡すため)、ベンダーは「付加価値」センサーに新しい GUID を容易に割り当てることができます。

新しい GUID の生成

Microsoft* Visual Studio* は、新しい GUID を生成するツールを提供しています。図 4 にそのスクリーンショットを示します。ベンダーは GUID を発行するだけで、Microsoft* の API やオペレーティング・システムのコードを変更しなくても新しい機能を利用できます。


図 4 追加センサー用の新しい GUID の定義

SensorManager オブジェクトの使用

アプリケーションでセンサーを使用するには、Microsoft* Sensor Framework のオブジェクトをハードウェアへ「バインド」する必要があります。これは、SensorManager と呼ばれる特別なオブジェクトを用いて、プラグアンドプレイ機能によって行われます。

タイプの照会

Gyrometer3D のように特定のセンサータイプを照会することができます。SensorManager は、マシン上のセンサー・ハードウェアのリストを参照し、ハードウェアにバインドされている、対応するオブジェクトのコレクションを返します。センサー・コレクションに含まれるオブジェクトの数はゼロ、1、またはそれ以上になる可能性がありますが、多くの場合は 1 つです。以下の C++ サンプルコードは、SensorManager オブジェクトの GetSensorsByType メソッドを使用して 3 軸のジャイロを検索し、その結果を含むセンサー・コレクションを返します。最初に、SensorManager オブジェクト用の ::CoCreateInstance() を作成する必要があります。


// センサー用の追加のインクルード
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>

// SensorManager オブジェクトの COM インターフェイスを作成する
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;
}

// コンピューターの 3 軸のジャイロの値を取得する
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;
}


カテゴリーの照会

すべてのモーションセンサーのように特定のカテゴリーのセンサーを照会することができます。SensorManager は、マシン上のセンサー・ハードウェアのリストを参照し、ハードウェアにバインドされているモーション・オブジェクトのコレクションを返します。センサー・コレクションにはゼロ、1、またはそれ以上のオブジェクトが含まれます。ほとんどのマシンには、Accelerometer3D と Gyrometer3D の 2 つのモーション・オブジェクトがあります。

以下の C++ サンプルコードは、SensorManager オブジェクトの GetSensorsByCategory メソッドを使用してモーションセンサーを検索し、その結果を含むセンサー・コレクションを返します。

// センサー用の追加のインクルード
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>

// SensorManager オブジェクトの COM インターフェイスを作成する
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;
}

// コンピューターの 3 軸のジャイロの値を取得する
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;
}

カテゴリー “All” の照会

実際には、マシン上のすべてのセンサーを一度に照会するのが最も効率的です。SensorManager は、マシン上のセンサー・ハードウェアのリストを参照し、ハードウェアにバインドされているすべてのオブジェクトのコレクションを返します。センサー・コレクションには 0、1 またはそれ以上のオブジェクトが含まれます。ほとんどのマシンには、7 つ以上のオブジェクトがあります。

C++ には GetAllSensors がないため、代わりに以下のサンプルコードに示す

GetSensorsByCategory(SENSOR_CATEGORY_ALL, …) を使用します。


// センサー用のインクルードを追加
#include <InitGuid.h>
#include <SensorsApi.h>
#include <Sensors.h>

// SensorManager オブジェクトの COM インターフェイスを作成する
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;
}

// コンピューターの 3 軸のジャイロの値を取得する
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. 不安定なワイヤレス・インターフェイス (Bluetooth* など) や有線インターフェイス (イーサネットなど) を利用して接続されるセンサーの場合。このような場合、センサーの接続/切断が一般的に行われます。
  3. Windows* Update により、センサーのデバイスドライバーがアップグレードされた場合。センサーは切断され、再度接続されたかのように見えます。
  4. Windows* がサスペンドもしくはハイバネーションに移行した場合 (S4 または S5 状態に移行)。センサーは切断されたかのように見えます。

センサーにおけるプラグアンドプレイの接続は Enter イベント、切断は Leave イベントと呼ばれます。アプリケーションはどちらにも対応しなければいけません。

Enter イベントのコールバック

センサーが接続されたときすでにアプリケーションが実行中の場合、SensorManager は Enter イベントを報告します。一方、すでにセンサーが接続されている状態でアプリケーションを開始した場合、Enter イベントは発生しません。C++/COM では、SetEventSink メソッドを使用してコールバックをフックする必要があります。コールバックは、ISensorManagerEvents からすべてのクラスを継承し、IUnknown を実装する必要があります。さらに、ISensorManagerEvents インターフェイスは、
STDMETHODIMP OnSensorEnter(ISensor *pSensor, SensorState state); に対するコールバック関数を実装しなければなりません。


// 任意の SensorEnter イベントに SensorManager をフックする
pSensorManagerEventClass = new SensorManagerEventSink();  // C++ クラスのインスタンスを作成する

// ISensorManagerEvents COM インターフェイス・ポインターを取得する
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;
}

// クラスの COM インターフェイスを SensorManager イベントにフックする
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;
}


コード: Enter イベントのコールバックをフックする

以下は、Enter イベントのコールバックに相当する C++/COM コードです。メインループのすべての初期化はこの関数で行われます。Enter イベントをシミュレーションするため、メインループでは OnSensorEnter の呼び出しのみを行うようにリファクタリングするとより効率的です。


STDMETHODIMP SensorManagerEventSink::OnSensorEnter(ISensor *pSensor, SensorState state)
{
    // SupportsDataField が 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)
    {
        // 探しているセンサーではない
        return -1;
    }
    ISensor *pAls = pSensor;  // ALS のようなので記録する
    ::MessageBox(NULL, _T(“Ambient Light Sensor has entered.”),
        _T(“Sensor C++ Sample”), MB_OK | MB_ICONINFORMATION);
    .
    .
    .
    return hr;
}


コード: Enter イベントのコールバック

Leave イベント

Leave イベントが発生すると、(SensorManager ではなく) 個々のセンサーが報告します。以下のコードは、Enter イベントのコールバックをフックするコードと同じです。


// 任意の DataUpdated、Leave、または StateChanged イベントにセンサーをフックする
SensorEventSink* pSensorEventClass = new SensorEventSink();  // C++ クラスのインスタンスを作成する
ISensorEvents* pSensorEvents = NULL;

// ISensorEvents COM インターフェイス・ポインターを取得する
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); // クラスの COM インターフェイスをセンサーにフックする
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;
}


コード: Leave イベントのコールバックをフックする

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);
    // 終了するセンサーで必要な処理を実行する
    // 例えば、センサーへのポインターがある場合は
    // 解放して、NULL に設定する
    return hr;
}


コード: Leave イベントのコールバック

アプリケーションで使用するセンサーの選択

センサーのタイプにより、得られる情報は異なります。Microsoft* ではこの情報をデータフィールドと呼び、SensorDataReport でグループ化しています。マシンには、アプリケーションで利用可能なセンサーのタイプが複数あるかもしれません。アプリケーションにとって、情報が取得できれば、それがどのセンサーからのものかは重要ではありません。

表 4 データフィールド名

データフィールド ID は、PROPERTYKEY と呼ばれるデータ型を使用する点でセンサー ID とは異なります。PROPERTYKEY は、GUID (センサーの GUID に似たもの) と “PID” (プロパティー ID) と呼ばれる追加の番号で構成されます。PROPERTYKEY の GUID 部分は、同じカテゴリーのセンサーで共通です。データフィールドのすべての値は、boolean、unsigned char、int、float、double などネイティブのデータ型です。

Win32*/COM では、データフィールドの値は PROPVARIANT と呼ばれるポリモーフィックなデータ型で格納されます。.NET では、これに相当する “object” と呼ばれる CLR (共通言語ランタイム) データ型があります。ポリモーフィックなデータ型は、照会して “想定される”/”定義されている” データ型にキャストする必要があります。

データフィールドのセンサーをチェックするには、センサーの SupportsDataField() メソッドを使用すべきです。これはセンサーの選択に使用される最も一般的なプログラミング表現です。アプリケーションが利用するモデルによっては、データフィールドのサブセットのみ必要な場合もあるかもしれません。必要なデータフィールドに対応したセンサーを選択します。ベースクラスのセンサーからサブクラスのメンバー変数を割り当てる場合、型キャストが必要になります。


ISensor* m_pAls;
ISensor* m_pAccel;
ISensor* m_pTilt;

// コレクションから必要なセンサーを探す
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 です。読み込みのみ許可されるデータフィールドと異なり、プロパティーは読み込み/書き込みが可能です。書き込みを拒否するかどうかは個々のセンサーの判断に任されています。書き込みに失敗しても例外がスローされないため、書き込み-読み込みを確認する必要があります。

表 5 よく使用されるセンサーのプロパティーと PID

ID (Win32*/COM)ID (.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 の数を制御したり、フィルターすることができます。これを利用して、必要な DataUpdated イベントのみホスト CPU に送信し、通信量を減らすことができます。Microsoft* は Sensitivity プロパティーを、Win32*/COM では IPortableDeviceValues と呼ばれるコンテナー型として、.NET では SensorPortableDeviceValues と呼ばれるコンテナー型として定義しています。このコンテナーは、データフィールドの PROPERTYKEY とその感度の値からなるタプルのコレクションを保持します。感度の計測単位とデータ型は、常に対応するデータフィールドと同じです。


// 感度を設定する
// <Data Field, Sensitivity> タプルを保持する IPortableDeviceValues コンテナーを作成する
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;
}

// ここで IPortableDeviceValues コンテナーにコンテンツを格納する: X、Y、Z、ぞれぞれの軸の感度を 0.1G にする
PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_R8; // (double) の COM 型
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);

// <SENSOR_PROPERTY_CHANGE_SENSITIVITY, pInSensitivityValues> タプルを保持するための IPortableDeviceValues コンテナーを作成する
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;
}

// コンテンツを格納する
pInValues->SetIPortableDeviceValuesValue(SENSOR_PROPERTY_CHANGE_SENSITIVITY, pInSensitivityValues);

// 感度を設定する
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;
}

// 設定要求が失敗したかを確認する
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);


センサーへのアクセス許可の要求

センサーにより提供される一部の情報には個人情報が含まれている可能性があります。マシンの位置 (例えば、緯度と経度) のようなデータフィールドは、ユーザーの追跡に使用することができます。そのため、Windows* では、アプリケーションはセンサーへアクセスする許可をエンドユーザーから得なければなりません。必要に応じて、センサーの State プロパティーと SensorManager の RequestPermissions() メソッドを使用します。

RequestPermissions() メソッドは、引数としてセンサーの配列を受け取るため、アプリケーションは一度に複数のセンサーへのアクセス許可を要求することができます。以下に C++/COM コードを示します。RequestPermissions() への引数として (ISensorCollection *) を提供する必要があります。


// センサーの状態を取得する
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;
}

// アクセス権を確認し、必要に応じて権限を要求する
if (state == SENSOR_STATE_ACCESS_DENIED)
{
    // アクセス権が必要なセンサーだけで SensorCollection を作成する
    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); // アクセス権を要求する 1 つ以上のセンサーを追加する

    // SensorManager に許可を求めるプロンプトを表示させる
    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 の更新に用いられるメッセージポンプ・スレッドとは異なるスレッドで発生します。そのため、GUI スレッドのコンテキストでイベントハンドラー (Als_DataUpdate) から別のハンドラー (Als_UpdateGUI) へ SensorDataReport を引き渡す必要があります。.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);


// 任意の DataUpdated、Leave、または StateChanged イベントにセンサーをフックする
SensorEventSink* pSensorEventClass = new SensorEventSink();  // C++ クラスのインスタンスを作成する
ISensorEvents* pSensorEvents = NULL;

// ISensorEvents COM インターフェイス・ポインターを取得する
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); // クラスの COM インターフェイスをセンサーにフックする
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 スレッドは、呼び出しキューに追加されたデリゲート関数に引数を渡し実行します。デリゲート関数は、想定されるサブクラスの型へ 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) // PROPVARIANT の値が float 型であることを確認する
        {
            // lux 値を取得する
            fLux = pv.fltVal;

            // 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 の Sensor API は各センサーに共通のテンプレートを持っています。

  • 通常、ReadingChanged と呼ばれる 1 つのイベントがあります。このイベントは、実際のデータを保持する Reading オブジェクトが格納された xxxReadingChangedEventArgs 型の引数とともにコールバックを呼び出します。例外として、加速度計には Shaken イベントもあります。
  • センサークラスのハードウェアにバインドされたインスタンスは、GetDefault() メソッドを使用して取得されます。
  • ポーリングは、通常 GetCurrentReading() メソッドを使用して行われます。

Windows* ストアアプリは、通常 JavaScript* または C# で記述されています。言語ごとに API へのバインド方法が異なるため、各言語の API 名の大文字・小文字の表記とイベントの処理方法は多少異なります。単純な API は使いやすく、表 6 のような長所と短所があります。

表 6 Metro スタイルアプリの Sensor API の長所と短所

機能長所短所
SensorManager SensorManager がありません。アプリケーションは、GetDefault() メソッドによりセンサークラスのインスタンスを取得します。
  • センサー・インスタンスを検索することはできません。マシン上に特定のセンサータイプが複数ある場合、”最初” のセンサータイプにのみアクセスできます。
  • GUID を使用してセンサータイプやカテゴリーを検索することはできません。ベンダーによる付加価値拡張にはアクセスできません。
イベント アプリケーションは、DataUpdated イベントにのみアクセスできます。
  • Enter、Leave、StatusChanged、任意のイベントにはアクセスできません。ベンダーによる付加価値拡張にはアクセスできません。
センサーのプロパティー アプリケーションは、ReportInterval プロパティーにのみアクセスできます。
  • 最も便利な Sensitivity プロパティーを含め、その他のプロパティーにはアクセスできません
  • ReportInterval プロパティーを操作する以外、Windows UI アプリケーションがデータのレポート頻度を調整または制御する方法はありません。
  • アプリケーションは、PROPERTYKEY を使用して任意のプロパティーにアクセスすることはできません。ベンダーによる付加価値拡張にはアクセスできません。
データレポートのプロパティー アプリケーションは、各センサーに固有の事前定義されたデータフィールドにのみアクセスできます。
  • その他のデータフィールドにはアクセスできません。センサーが Windows* UI アプリケーションで想定されている以外の一般的なデータレポートのデータフィールドを “ピギーバッグ” しても、それらにアクセスすることはできません。
  • アプリケーションは、PROPERTYKEY を使用して任意のデータフィールドにアクセスすることはできません。ベンダーによる付加価値拡張にはアクセスできません。
  • アプリケーションは、実行時にセンサーがサポートしているデータフィールドを照会することはできません。API により事前定義されているデータフィールドのみ想定することができます。

まとめ

Windows* 8 API により、開発者はさまざまなプラットフォームで利用可能なセンサーを、デスクトップ・モードおよび Windows* ストアアプリ・インターフェイスの両方で利用することができます。ここでは、Windows* 8 アプリケーション開発で利用可能な Sensor API の概要について、デスクトップ・アプリケーション用の API とサンプルコードに注目して説明しました。

付録

さまざまなフォームファクターの座標系

Windows* API は、HTML5 標準規格 (および Android*) と互換性のある形式で X 軸、Y 軸、Z 軸をレポートします。これは、X 軸が仮想上の “東 (East)”、Y 軸が “北 (North)”、Z 軸が “上 (Up)” を指しているため、”ENU” 座標系とも呼ばれます。

回転の向きは、”右手の法則” を使用して求めることができます。

* 右手の親指で軸の方向を指します。

* ほかの指の向きが回転方向になります。

上の図は、タブレット PC または携帯電話 (左) と、クラムシェル PC (右) の X 軸、Y 軸、Z 軸を示しています。より複雑なフォームファクター (例えば、タブレットに変換可能なクラムシェルなど) の場合、タブレットの状態にしたときが “標準” の向きになります。

ナビゲーション・アプリケーション (3D ゲームなど) の開発では、プログラムで “ENU” 座標系の変換が必要になります。これは行列の乗算を使用して行えます。Direct3D* や OpenGL* のようなグラフィックス・ライブラリーにはそのための API があります。

関連情報

Windows* 7 の Sensor API: http://msdn.microsoft.com/library/windows/desktop/dd318953(VS.85).aspx

Sensor API プログラミング・ガイド: http://msdn.microsoft.com/ja-jp/library/dd318964(v=vs.85).aspx

モーション センサーと方位センサーの統合: http://msdn.microsoft.com/ja-jp/library/windows/hardware/br259127.aspx

著者紹介

Gael Hofemeier はインテル社のデベロッパー・リレーション部門のビジネス・クライアント・テクノロジー担当のソフトウェア・エンジニアです。ニューメキシコ大学で数学の学士号と MBA を取得しています。趣味は、ハイキング、サイクリング、写真です。

Deepak Vembar, PhD

Deepak Vembar は、インテル・ラボのインタラクションおよびエクスペリエンス研究 (IXR) グループの研究員です。リアルタイム・グラフィックス、バーチャル・リアリティー、ハプティクス、アイ・トラッキング、ユーザー・インタラクションの分野を含む、コンピューター・グラフィックスと、人間とコンピューターの相互作用のかかわりについて研究しています。前職はインテルのソフトウェア & サービスグループ (SSG) のソフトウェア・エンジニアであり、ゲーム開発者と協力してインテル® プラットフォーム向けにゲームの最適化を行ったり、ヘテロジニアス・プラットフォームの最適化に関するコースやチュートリアルを担当したり、教材用にデモゲームを使って学部課程の課題の作成に取り組んできました。

Intel、インテル、Intel ロゴ、Ultrabook は、アメリカ合衆国およびその他の国における Intel Corporation の商標です。

© 2012 Intel Corporation. 無断での引用、転載を禁じます。

* その他の社名、製品名などは、一般に各社の表示、商標または登録商標です。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事