超宽带 (UWB) 通信

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

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

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

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

测距参数

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

测距参数包括本地地址、复杂信道和会话密钥等。请注意,这些参数可能在测距会话结束后发生轮换或更改,需要重新进行通信才能重新开始测距。

后台测距

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

应用在后台运行时不会接收测距报告;应用在切换到前台时才会接收测距报告。

STS 配置

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

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

对于提供的 STS

  1. RangingParameters 中使用支持提供 STS 的 uwbConfigType

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

对于静态 STS

  1. RangingParameters 中使用支持静态 STS 的 uwbConfigType

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

步骤

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

  1. 确保 Android 设备运行 Android 12 或更高版本,并使用 PackageManager#hasSystemFeature("android.hardware.uwb") 支持 UWB。
  2. 如果针对 IoT 设备进行测距,请确保它们符合 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 库

供应商 设备型号
谷歌 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 设备兼容,物联网供应商需要修改您的实现以避免反转这些字段的字节顺序。