Wi-Fi 位置情報: RTT を使用したレンジング(距離測定)

Wi-Fi RTT(ラウンドトリップ時間)API が提供する Wi-Fi 位置情報機能を使用して、付近にある RTT 対応の Wi-Fi アクセス ポイントや Wi-Fi Aware ピア デバイスとの距離を測定できます。

3 つ以上のアクセス ポイントまでの距離を測定する場合は、マルチラテレーション アルゴリズムを使用して、測定に最適なデバイスの位置を推定できます。結果の精度は通常 1~2 メートルの誤差の範囲内です。

この精度を確保できることによって、屋内ナビゲーション、明確な音声操作(例: 「このライトをつけて」)、位置情報をベースとする情報(例: 「このサービスに関してスペシャル オファーはありますか?」)などの精度の高い、位置情報を利用したサービスを開発できます。

リクエスト元のデバイスは、Wi-Fi RTT で距離を測定するためにアクセス ポイントに接続する必要はありません。プライバシーを保護するために、アクセス ポイントまでの距離を判断できるのはリクエスト元のデバイスのみです。アクセス ポイントにはこの情報がありません。Wi-Fi RTT オペレーションでは、フォアグラウンド アプリに対する制限はありませんが、バックグラウンド アプリに対しては制限が課されます。

Wi-Fi RTT と関連する Fine-Time-Measurement(FTM)機能は、IEEE 802.11-2016 規格で規定されています。Wi-Fi RTT では、パケットがデバイス間を往復する時間を測定し、測定された時間に光速を乗じることによって 2 つのデバイス間の距離を計算するため、FTM による正確な時間測定が必要です。

Android のバージョンに基づく実装の違い

Wi-Fi RTT は Android 9(API レベル 28)で導入されました。Android 9 搭載デバイスの場合、このプロトコルを使用してマルチラテレーションでデバイスの位置を特定するには、アプリが事前定義されたアクセス ポイント(AP)の位置情報データにアクセスする必要があります。このデータの保存方法や取得方法は任意に決定できます。

Android 10(API レベル 29)以降を搭載するデバイスでは、AP の位置情報データは緯度、経度、高度を含む ResponderLocation オブジェクトとして表すことができます。Location Configuration Information / Location Civic Report(LCI / LCR データ)をサポートする Wi-Fi RTT AP の場合、プロトコルは距離測定プロセス中に ResponderLocation オブジェクトを返します。

この機能を使用すると、アプリは事前にこの情報を保存する代わりに、AP に直接クエリを実行して位置を問い合わせることができます。そのため、ユーザーがある建物に初めて入ったときなど、AP を事前に把握できていない場合でも、アプリは AP を見つけてその位置を特定できます。

要件

  • 距離測定をリクエストするデバイスのハードウェアは、802.11-2016 FTM 規格を実装する必要があります。
  • 距離測定のリクエストを行うデバイスは、Android 9(API レベル 28)以降を搭載している必要があります。
  • 距離測定のリクエストを行うデバイスでは、[設定] > [位置情報] で位置情報サービスを有効にして Wi-Fi スキャンをオンにする必要があります。
  • 距離測定リクエストを行うアプリが Android 13(API レベル 33)以降をターゲットとしている場合は、NEARBY_WIFI_DEVICES 権限が必要です。このようなアプリが以前のバージョンの Android をターゲットにしている場合は、代わりに ACCESS_FINE_LOCATION 権限が必要です。
  • アプリは表示中またはフォアグラウンドでの実行中に、アクセス ポイントの距離を照会する必要があります。アプリはバックグラウンドからは位置情報にアクセスできません。
  • アクセス ポイントは、IEEE 802.11-2016 FTM 規格を実装する必要があります。

セットアップ

Wi-Fi RTT を使用するようにアプリを設定するには、次の手順を行います。

1. 権限をリクエストする

アプリのマニフェストで次の権限をリクエストします。

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- If your app targets Android 13 (API level 33)
     or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                 <!-- If your app derives location information from Wi-Fi APIs,
                      don't include the "usesPermissionFlags" attribute. -->
                 android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                 <!-- If any feature in your app relies on precise location
                      information, don't include the "maxSdkVersion"
                      attribute. -->
                 android:maxSdkVersion="32" />

NEARBY_WIFI_DEVICES 権限と ACCESS_FINE_LOCATION 権限は危険な権限であるため、ユーザーが RTT スキャン オペレーションを実行するたびに実行時にリクエストする必要があります。権限がまだ付与されていない場合、アプリはユーザーに権限をリクエストする必要があります。実行時の権限の詳細については、アプリの権限をリクエストするをご覧ください。

2. デバイスが Wi-Fi RTT に対応しているかどうかを確認する

デバイスが Wi-Fi RTT に対応しているかどうかを確認するには、PackageManager API を使用します。

Kotlin

context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)

Java

context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);

3. Wi-Fi RTT が利用可能かどうかを確認する

デバイスに Wi-Fi RTT があっても、ユーザーが無効にしているために Wi-Fi RTT が現在利用できない場合があります。ハードウェアとファームウェアの機能によっては、SoftAP、またはテザリングが使用されている場合に、一部のデバイスで Wi-Fi RTT がサポートされない可能性があります。Wi-Fi RTT が現在利用可能かどうかを確認するには、isAvailable() を呼び出します。

Wi-Fi RTT の有効な状態は随時変わる可能性があります。アプリは BroadcastReceiver を登録して ACTION_WIFI_RTT_STATE_CHANGED を受け取る必要があります。これは、有効な状態が変化した場合に送信されます。ブロードキャスト インテントを受け取ったら、アプリは現在の状態を確認し、それに応じて動作を調整する必要があります。

次に例を示します。

Kotlin

val filter = IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED)
val myReceiver = object: BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (wifiRttManager.isAvailable) {
            …
        } else {
            …
        }
    }
}
context.registerReceiver(myReceiver, filter)

Java

IntentFilter filter =
    new IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
BroadcastReceiver myReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (wifiRttManager.isAvailable()) {
            …
        } else {
            …
        }
    }
};
context.registerReceiver(myReceiver, filter);

詳細については、ブロードキャストをご覧ください。

距離測定のリクエストを作成する

距離測定のリクエスト(RangingRequest)は、距離をリクエストする対象の AP または Wi-Fi Aware ピアのリストを指定して作成します。1 つの距離測定リクエストで複数のアクセス ポイントまたは Wi-Fi Aware ピアを指定すると、すべてのデバイスまでの距離が測定され、返されます。

たとえば、次のように、リクエストで addAccessPoint() メソッドを使用して、距離を測定する対象のアクセス ポイントを指定できます。

Kotlin

val req: RangingRequest = RangingRequest.Builder().run {
    addAccessPoint(ap1ScanResult)
    addAccessPoint(ap2ScanResult)
    build()
}

Java

RangingRequest.Builder builder = new RangingRequest.Builder();
builder.addAccessPoint(ap1ScanResult);
builder.addAccessPoint(ap2ScanResult);

RangingRequest req = builder.build();

アクセス ポイントは、ScanResult オブジェクトによって識別されます。このオブジェクトは、WifiManager.getScanResults() を呼び出すことで取得できます。addAccessPoints(List) を使用すると、複数のアクセス ポイントを一括で追加できます。

同様に、距離測定のリクエストで MAC アドレスまたは PeerHandle を使用して Wi-Fi Aware ピアを追加する場合は、addWifiAwarePeer(MacAddress peer) メソッドまたは addWifiAwarePeer(PeerHandle peer) メソッドをそれぞれ使用できます。Wi-Fi Aware ピアの検出について詳しくは、Wi-Fi Aware のドキュメントをご覧ください。

距離測定をリクエストする

アプリは、WifiRttManager.startRanging() メソッドを使用して距離測定のリクエストを発行します。操作を表す RangingRequest、コールバック コンテキストを表す Executor、結果を受け取る RangingResultCallback を指定します。

次に例を示します。

Kotlin

val mgr = context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as WifiRttManager
val request: RangingRequest = myRequest
mgr.startRanging(request, executor, object : RangingResultCallback() {

    override fun onRangingResults(results: List<RangingResult>) { … }

    override fun onRangingFailure(code: Int) { … }
})

Java

WifiRttManager mgr =
      (WifiRttManager) Context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE);

RangingRequest request ...;
mgr.startRanging(request, executor, new RangingResultCallback() {

  @Override
  public void onRangingFailure(int code) { … }

  @Override
  public void onRangingResults(List<RangingResult> results) { … }
});

距離測定オペレーションは非同期で実行され、距離測定の結果は RangingResultCallback のコールバックの 1 つで返されます。

  • 距離測定オペレーション全体が失敗した場合、RangingResultCallback で説明されているステータス コードで onRangingFailure コールバックがトリガーされます。 このような失敗は、Wi-Fi が無効になっている、アプリがリクエストした距離測定オペレーションが多すぎるため制限が課されている、または権限に問題があるなどの理由により、サービスで距離測定オペレーションが実行できない場合に発生します。
  • 距離測定オペレーションが完了すると、onRangingResults コールバックが、リクエストのリストに一致する結果(各リクエストに対して 1 つの結果)のリストでトリガーされます。結果の順序は、リクエストの順序と一致しない場合があります。距離測定オペレーションが完了した場合でも、各結果が依然として特定の測定の失敗を示している可能性がある点に留意してください。

距離測定の結果を解釈する

onRangingResults コールバックによって返される各結果は、RangingResult オブジェクトによって指定されます。各リクエストで、次の操作を行います。

1. リクエストを特定する

RangingRequest の作成時に提供された情報(ほとんどの場合は、ScanResult で提供され、アクセス ポイントを特定する MAC アドレス)に基づいてリクエストを特定します。MAC アドレスは、getMacAddress() メソッドを使用して距離測定の結果から取得できます。

距離測定の結果のリストにおける並び順は、距離測定リクエストで指定されたピア(アクセス ポイント)とは異なる場合があるため、結果の順序ではなく MAC アドレスを使用してピアを特定する必要があります。

2. 各測定が成功したかどうかを判断する

測定が成功したかどうかを判断するには、getStatus() メソッドを使用します。STATUS_SUCCESS 以外の値は失敗を示します。失敗した場合、この結果の他のすべてのフィールド(上記のリクエスト ID を除く)は無効になり、対応する get* メソッドは IllegalStateException 例外で失敗します。

3. 成功した各測定の結果を取得する

成功した測定ごとに、それぞれの get メソッドを使用して結果の値を取得できます。

Wi-Fi-RTT 対応の Android デバイス

以下の表に、Wi-Fi-RTT に対応しているスマートフォンアクセス ポイント小売店、倉庫、配送センターのデバイスを示します。これらはごく一部を示したものです。RTT 対応プロダクトをこちらに記載するには、Google にお問い合わせいただくことをおすすめします。

アクセス ポイント

メーカーとモデル サポート日
Google Nest Wifi Pro (Wi-Fi 6E) 対応
Compulab WILD AP 対応
Google WiFi 対応
Google Nest Wifi ルーター 対応
Google Nest Wifi 拡張ポイント 対応
アルバ AP-635 対応
Cisco 9130 対応
Cisco 9136 対応
Cisco 9166 対応
Cisco 9164 対応
Aruba AP-505 対応
Aruba AP-515 対応
アルバ AP-575 対応
アルバ AP-518 対応
Aruba AP-505H 対応
アルバ AP-565 対応
アルバ AP-535 対応

スマートフォン

メーカーとモデル Android バージョン
Google Pixel 6 9.0+
Google Pixel 6 Pro 9.0+
Google Pixel 5 9.0+
Google Pixel 5a 9.0+
Google Pixel 5a (5G) 9.0+
Xiaomi Mi 10 Pro 9.0+
Xiaomi Mi 10 9.0+
Xiaomi Redmi Mi 9T Pro 9.0+
Xiaomi Mi 9T 9.0+
Xiaomi Mi 9 9.0+
Xiaomi Mi Note 10 9.0+
Xiaomi Mi Note 10 Lite 9.0+
Xiaomi Redmi Note 9S 9.0+
Xiaomi Redmi Note 9 Pro 9.0+
Xiaomi Redmi Note 8T 9.0+
Xiaomi Redmi Note 8 9.0+
Xiaomi Redmi K30 Pro 9.0+
Xiaomi Redmi K20 Pro 9.0+
Xiaomi Redmi K20 9.0+
Xiaomi Redmi Note 5 Pro 9.0+
Xiaomi Mi CC9 Pro 9.0+
LG G8X ThinQ 9.0+
LG V50S ThinQ 9.0+
LG V60 ThinQ 9.0+
LG V30 9.0+
Samsung Galaxy Note 10+ 5G 9.0+
Samsung Galaxy S20+ 5G 9.0+
Samsung Galaxy S20 以降 9.0+
Samsung Galaxy S20 5G 9.0+
Samsung Galaxy S20 Ultra 5G 9.0+
Samsung Galaxy S20 9.0+
Samsung Galaxy Note 10 以降 9.0+
Samsung Galaxy Note 10 5G 9.0+
Samsung Galaxy Note10 9.0+
Samsung A9 Pro 9.0+
Google Pixel 4 XL 9.0+
Google Pixel 4 9.0+
Google Pixel 4a 9.0+
Google Pixel 3 XL 9.0+
Google Pixel 3 9.0+
Google Pixel 3a XL 9.0+
Google Pixel 3a 9.0+
Google Pixel 2 XL 9.0+
Google Pixel 2 9.0+
Google Pixel 1 XL 9.0+
Google Pixel 1 9.0+
Poco X2 9.0+
シャープ Aquos R3 SH-04L 9.0+

小売、倉庫、流通センターのデバイス

メーカーとモデル Android バージョン
ゼブラ PS20 10.0 以降
Zebra TC52、TC52HC 10.0 以降
ゼブラ TC57 10.0 以降
ゼブラ TC72 10.0 以降
ゼブラ TC77 10.0 以降
ゼブラ MC93 10.0 以降
Zebra TC8300 10.0 以降
ゼブラ VC8300 10.0 以降
Zebra EC30 10.0 以降
ゼブラ ET51 10.0 以降
ゼブラ ET56 10.0 以降
ゼブラ L10 10.0 以降
Zebra CC600、CC6000 10.0 以降
ゼブラ MC3300X 10.0 以降
ゼブラ MC330X 10.0 以降
Zebra TC52X 10.0 以降
Zebra TC57X 10.0 以降
Zebra EC50(LAN、HC) 10.0 以降
Zebra EC55(WAN) 10.0 以降
ゼブラ WT6300 10.0 以降
Skorpio X5 10.0 以降