超宽带 (UWB) 通信

超宽带通信是一种无线电技术,专注于设备之间精确测距(将位置测量到 10 厘米的精度)。这种无线电技术可以使用低能量密度进行短距离测量,并在无线电频谱的大部分区域执行高带宽信号传输。UWB 的带宽大于 500 MHz(或超过 20% 的分数带宽)。

控制器/发起方与被控方/响应方

UWB 通信发生在两个设备之间,其中一个设备是控制器,另一个设备是被控方。控制器确定两个设备将共享的复杂信道 (UwbComplexChannel),并作为发起方,而被控方则作为响应方。

一个控制器可以处理多个被控方,但一个被控方只能订阅一个控制器。控制器/发起方和被控方/响应方配置均受支持。

测距参数

控制器和被控方需要相互识别并传达测距参数才能开始测距。此交换由应用程序使用他们选择的安全的带外 (OOB) 机制来实现,例如 蓝牙低功耗 (BLE)。

测距参数 包括本地地址、复杂信道和会话密钥等。请注意,这些参数可能会在测距会话结束后旋转或以其他方式更改,需要重新传达以重新启动测距。

后台测距

如果设备支持,则在后台运行的应用程序可以启动 UWB 测距会话。要检查您的设备功能,请参阅 RangingCapabilities.

应用程序在后台运行时不会接收测距报告;应用程序在移至前台时会接收测距报告。

STS 配置

应用程序或服务使用混淆时间戳序列 (STS) 为每个会话配置会话密钥。已配置的 STS 比静态 STS 配置更安全。所有运行 Android 14 或更高版本的支持 UWB 的设备都支持已配置的 STS。

威胁类别 静态 STS 已配置的 STS
空中:被动观察者 已缓解 已缓解
空中:信号放大 已缓解 已缓解
空中:重放/中继攻击 易受攻击 已缓解

对于已配置的 STS

  1. 使用 uwbConfigTypeRangingParameters 中支持预配的 STS。

  2. sessionKeyInfo 字段中提供 16 字节的密钥。

对于静态 STS

  1. 使用 uwbConfigTypeRangingParameters 中支持静态 STS。

  2. sessionKeyInfo 字段中提供 8 字节的密钥。

步骤

要使用 UWB API,请按照以下步骤操作

  1. 确保 Android 设备运行在 Android 12 或更高版本上,并使用 PackageManager#hasSystemFeature("android.hardware.uwb") 支持 UWB。
  2. 如果要对物联网设备进行测距,请确保它们符合 FiRa MAC 1.3 标准。
  3. 使用您选择的 OOB 机制(例如 BluetoothLeScanner)发现支持 UWB 的对等设备。
  4. 使用您选择的安全 OOB 机制(例如 BluetoothGatt)交换测距参数。
  5. 如果用户想要停止会话,请取消会话的作用域。

使用限制

以下限制适用于使用 UWB API

  1. 启动新的 UWB 测距会话的应用程序必须是前台应用程序或服务,除非如前所述支持后台测距。
  2. 当应用程序转到后台(在会话进行时),应用程序可能不再接收测距报告。但是,UWB 会话将在较低层继续维护。当应用程序返回到前台时,测距报告将恢复。

代码示例

示例应用程序

有关如何使用 UWB Jetpack 库 的端到端示例,请查看我们在 GitHub 上的示例应用程序。此示例应用程序涵盖了验证 Android 设备上的 UWB 兼容性、使用 OOB 机制启用发现过程以及在两个支持 UWB 的设备之间设置 UWB 测距。该示例还涵盖了设备控制和媒体共享用例。

UWB 测距

此代码示例为受控设备启动和终止 UWB 测距

// The coroutineScope responsible for handling uwb ranging.
// This will be initialized when startRanging is called.
var job: Job?

// A code snippet that initiates uwb ranging for a Controlee.
suspend fun startRanging() {

    // Get the ranging parameter of a partnering Controller using an OOB mechanism of choice.
    val partnerAddress : Pair<UwbAddress, UwbComplexChannel> = listenForPartnersAddress()

    // Create the ranging parameters.
    val partnerParameters = RangingParameters(
        uwbConfigType = UwbRangingParameters.UWB_CONFIG_ID_1,
        // SessionKeyInfo is used to encrypt the ranging session.
        sessionKeyInfo = null,
        complexChannel = partnerAddress.second,
        peerDevices = listOf(UwbDevice.createForAddress(partnerAddress.first)),
        updateRateType = UwbRangingParameters.RANGING_UPDATE_RATE_AUTOMATIC
    )

    // Initiate a session that will be valid for a single ranging session.
    val clientSession = uwbManager.clientSessionScope()

    // Share the localAddress of the current session to the partner device.
    broadcastMyParameters(clientSession.localAddress)

    val sessionFlow = clientSession.prepareSession(partnerParameters)

    // Start a coroutine scope that initiates ranging.
    CoroutineScope(Dispatchers.Main.immediate).launch {
        sessionFlow.collect {
            when(it) {
                is RangingResultPosition -> doSomethingWithPosition(it.position)
                is RangingResultPeerDisconnected -> peerDisconnected(it)
            }
        }
    }
}

// A code snippet that cancels uwb ranging.
fun cancelRanging() {

    // Canceling the CoroutineScope will stop the ranging.
    job?.let {
        it.cancel()
    }
}

RxJava3 支持

Rxjava3 支持现已可用,可帮助实现与 Java 客户端的互操作性。该库提供了一种方法,可以将测距结果作为 Observable 或 Flowable 流获取,并将 UwbClientSessionScope 作为 Single 对象检索。

private final UwbManager uwbManager;

// Retrieve uwbManager.clientSessionScope as a Single object
Single<UwbClientSessionScope> clientSessionScopeSingle =
                UwbManagerRx.clientSessionScopeSingle(uwbManager);
UwbClientSessionScope uwbClientSessionScope = clientSessionScopeSingle.blockingGet();

// Retrieve uwbClientSessionScope.prepareSession Flow as an Observable object
Observable<RangingResult> rangingResultObservable =
                UwbClientSessionScopeRx.rangingResultsObservable(clientSessionScope,
                        rangingParameters);

// Consume ranging results from Observable
rangingResultObservable.subscribe(
   rangingResult -> doSomethingWithRangingResult(result), // onNext
   (error) -> doSomethingWithError(error), // onError
   () -> doSomethingOnResultEventsCompleted(), //onCompleted
);
// Unsubscribe
rangingResultObservable.unsubscribe();
   

// Retrieve uwbClientSessionScope.prepareSession Flow as a Flowable object
Flowable<RangingResult> rangingResultFlowable =
                UwbClientSessionScopeRx.rangingResultsFlowable(clientSessionScope,
                        rangingParameters);

// Consume ranging results from Flowable using Disposable
Disposable disposable = rangingResultFlowable
   .delay(1, TimeUnit.SECONDS)
   .subscribeWith(new DisposableSubscriber<RangingResult> () {
      @Override public void onStart() {
          request(1);
      }
      
      @Override public void onNext(RangingResult rangingResult) {
             doSomethingWithRangingResult(rangingResult);
             request(1);
      }


      @Override public void onError(Throwable t) {
             t.printStackTrace();
      }


         @Override public void onComplete() {
            doSomethingOnEventsCompleted();
         }
   });

// Stop subscription
disposable.dispose();

生态系统支持

以下是支持的合作伙伴设备和第三方 SDK。

支持 UWB 的移动设备

截至 2024 年 3 月,这些设备支持 Android UWB Jetpack 库

供应商 设备型号
Google Pixel 6 Pro、7 Pro、8 Pro、Fold、平板电脑
三星 Galaxy Note 20、S21+、S22+、S23+、S24+ Z Fold 2、3、4、5

第三方 SDK

截至 2023 年 4 月,这些合作伙伴解决方案与 当前的 Jetpack 库 兼容。

已知问题:MAC 地址和静态 STS 供应商 ID 字段的字节顺序颠倒

在 Android 13 及更低版本上,Android UWB 堆栈错误地颠倒了以下字段的字节顺序

  • 设备 MAC 地址
  • 目标 MAC 地址
  • 静态 STS 供应商 ID

字节顺序颠倒是因为 Android 堆栈将这些字段视为值,而不是数组。我们正在与 FiRa 合作更新 UCI 规范 (CR-1112) 以明确说明这些字段应被视为数组。

此问题将在 2320XXXX 版本的 GMS Core 更新中修复。为了与从那时起发布的 Android 设备兼容,物联网供应商需要修改您的实现以避免颠倒这些字段的字节顺序。