构建 Android Matter 应用

1. 欢迎

Matter 以统一 IoT 标准为目标而打造,可连接 Google Home、Zigbee、蓝牙网格、Z-Wave 等各种生态系统中的智能家居设备。

移动设备是与智能家居设备的中心互动点。如果您想构建自己的 Android 应用来支持 Matter 设备,我们可以帮助您快速上手。

Google Home 示例应用 (GHSA for Matter) 展示了 Home Mobile SDK API,让用户可以调试和共用设备。你还可以将示例应用用作学习工具,以便更好地了解关键 Matter 概念,还可将其用作调试与 Matter 设备互动问题和排查相关问题的工具。

您将执行的操作

在此 Codelab 中,您将下载示例应用的源代码,并了解如何使用 Home Mobile SDK 调试和共享设备。您还将了解如何使用 Matter 代码库 (connectedhomeip) 中的调试库和集群库。

在您下载示例应用后,我们将在 Android Studio 中查看源代码,并实现以下 Home Mobile SDK API

你还将详细了解调试概念、Matter 结构,以及如何控制 Matter 设备。

所需条件

在开始之前,请务必完成以下步骤:

您无需使用 hub(例如 Google Nest Hub(第 2 代))即可通过示例应用调试和控制设备。

2. 进行设置

此 Codelab 起始应用位于 codelab 分支中。如需开始使用此 Codelab 源代码,您可以下载 ZIP 文件。

您将使用此 codelab ZIP 文件来构建可正常工作的示例。

Codelab 版本

codelab 分支使用示例应用的 2.0.0 版本进行标记。如需比较您在完成每个步骤时所做的更新,您可以下载此版本的完整源代码。

如果您想克隆 GitHub 代码库,请按照示例应用 README 中的说明进行操作。

依赖项

我们将引导您查看共享和调试设备所需的源代码,但在开始之前了解以下依赖项可能对您有所帮助。请注意,文件 libs.versions.toml 中声明了这些依赖项,其用法在文件 build.gradle.kts 中指定

源代码

我们已为您创建了界面和大部分功能。

在此 Codelab 中,我们将向以下文件添加 Matter 功能:

  • java/commissioning/AppCommissioningService:允许您针对开发结构脉络调试设备
  • java/screens/home/HomeScreenjava/screens/home/HomeViewModel.kt:包含 Home Mobile SDK 调试功能
  • java/screens/device/DeviceScreenjava/screens/device/DeviceViewModel:包含 Share Device API 调用

每个文件都会使用您要修改的代码块进行注释,例如:

// CODELAB: add commissioningFunction()

这样,您就可以在 Codelab 中快速找到相应的部分。

3. 佣金给 Google

这些设备需接受专员(在本例中为 Matter 示例应用,即 Google Home 示例应用)的委托,您才能控制设备并允许它们在同一结构内相互通信。

请务必了解以下有关 Matter 调试的概念:

  • Fabric 可让设备相互通信。
  • Fabric 会维护一组共享的唯一凭据。
  • 生态系统负责颁发可信根证书、分配结构 ID 以及分配唯一节点 ID。生态系统是专员的后端服务,例如 Google Home 生态系统的 Home Graph。
  • 设备可以调试用于多个结构(多管理员功能)。

如需调试设备,您需要使用 CommissioningClient API。调用 .commissionDevice() 会返回 IntentSender,它会在 Google Play 服务中启动适当的 activity:

interface CommissioningClient {
  Task<IntentSender> commissionDevice(CommissioningRequest request);
}

在接下来的部分中,我们将介绍调试设备到 Google 结构体所需的最少代码。

第 1 步:activity 启动器

如需处理 CommissioningClient 中的 IntentSender,您可以使用 ActivityResultLauncher:

val commissioningLauncher = registerForActivityResult(
    StartIntentSenderForResult()
) { result: ActivityResult ->
    if (result.resultCode == RESULT_OK) {
        Timber.d(TAG, "Commissioning succeeded.")
    } else {
        Timber.d(TAG, "Commissioning failed. " + result.resultCode)
    }
}

第 2 步:调试函数

下面是一个基本示例,它使用 CommissioningClient API 调试设备到 Google 结构。

  1. 调试过程从 commissionDevice() 函数开始。首先,定义 CommissioningRequest。使用此默认配置时,设备仅限使用本地 Android 结构。
  2. Matter 是 Home Mobile SDK 的入口点。在下一次调用中,.getCommissioningClient 将通过 this (Activity) 获取 CommissioningClient。
  3. .commissionDevice() 接受 CommissioningRequest
  4. 最后,系统会调用 .addOnSuccessListener 来处理 CommissioningResult 并启动 Google Play 服务 (GPS) 佣金设备 Activity。
private fun commissionDevice() {
    val request: CommissioningRequest = CommissioningRequest.builder().build()
    Matter.getCommissioningClient(this)
        .commissionDevice(request)
        .addOnSuccessListener { result ->
            commissioningLauncher.launch(IntentSenderRequest.Builder(result).build())
        }
}

您可通过 Android 设置使用本地 Android Fabric 以简化改用其他结构的设备的调试流程。

接下来,您将学习如何调试设备以使用开发结构。

如需简要了解调试过程中的界面,请参阅 Google Home 适用于 Matter 的示例应用指南

4. 调试开发结构

设备可以调试用于多种结构。为了管理可信配对,设备会存储一个包含各种 FabricInfo 成员的 FabricTable,例如:

  • 织物识别
  • 由结构脉络分配给设备的节点 ID
  • 供应商 ID
  • 织物 ID
  • 设备操作凭据

管理员网域管理器 (ADM) 会定义架构凭据。在之前的场景中,Google Play 服务是一个充当可信根证书授权机构 (CA) 的生态系统。当您委托设备使用本地 Android 结构脉络时,每台设备都包含同一组结构凭据和同一组 CA。

定制调试服务

为了调试本地 Android 结构,我们使用默认参数在 CommissioningClient API 中构建 CommissioningRequest

val request: CommissioningRequest = CommissioningRequest.builder().build()

如果您想通过应用控制和管理新设备,则需要创建本地开发结构脉络并获取调试设备的操作凭据。在这种情况下,您的应用会成为一个独特、独立的生态系统,可为设备分配适当的节点凭据。

您可以向 CommissioningRequest 传递自定义服务,让 Home Mobile SDK 知道您要按自己的结构调试设备:

class CommissioningRequest {
  static CommissioningRequest.Builder builder();

  class Builder {
    Builder setCommissioningService(@Nullable ComponentName commissioningService);

    CommissioningRequest build();
  }
}

在接下来的步骤中,我们将修改 commissionDevice() 函数以使用自定义服务。我们还会向主屏幕 fragment 添加 activity 启动器,并使用 LiveData 对象管理 API 流程。

第 1 步:创建 GPS 活动启动器

首先,我们创建一个 activity 启动器来处理 CommissioningClient API 中的 IntentSender

  1. java/screens/home/ 文件夹中打开 HomeScreen
  2. // CODELAB: commissionDeviceLauncher definition 注释替换为以下代码,以注册和处理调试 activity 结果:
    val commissionDeviceLauncher =
      rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartIntentSenderForResult()
      ) { result ->
        // Commission Device Step 5.
        // The Commission Device activity in GPS (step 4) has completed.
        val resultCode = result.resultCode
        if (resultCode == Activity.RESULT_OK) {
          Timber.d("CommissionDevice: Success")
          // We let the ViewModel know that GPS commissioning has completed successfully.
          // The ViewModel knows that we still need to capture the device name and will\
          // update UI state to trigger the NewDeviceAlertDialog.
          homeViewModel.gpsCommissioningDeviceSucceeded(result)
        } else {
          homeViewModel.commissionDeviceFailed(resultCode)
        }
      }
    

第 2 步:触发佣金设备操作

在此步骤中,用户可以通过点击主屏幕右下角的“+”按钮来触发“佣金设备”操作。然后对 commissionDevice() 进行调用。

val onCommissionDevice = {
  ...
  commissionDevice(activity!!.applicationContext, commissionDeviceLauncher)
}

第 3 步:调用 API

  1. 仍在 HomeScreen.ktjava/screens/home/ 文件夹中。
  2. // CODELAB: commissionDevice 注释替换为以下 commissionDeviceRequestsetCommissioningService 会将 AppCommissioningService 绑定到回调函数中返回的 CommissioningService 实例。当您传递自定义服务时,Home Mobile SDK 会先将设备调试到 Android 本地结构脉络,然后再将初始配置载荷发送回 AppCommissioningService
    val commissionDeviceRequest =
        CommissioningRequest.builder()
            .setCommissioningService(ComponentName(
                context, AppCommissioningService::class.java))
            .build()
    
  3. 调用 .getCommissioningClient(),然后调用 .commissionDevice()
Matter.getCommissioningClient(context)
    .commissionDevice(commissionDeviceRequest)

如需完成 commissionDevice 函数,请添加 addOnSuccessListeneraddOnFailureListener

    .addOnSuccessListener { result ->
      commissionDeviceLauncher.launch(IntentSenderRequest.Builder(result).build())
    }
    .addOnFailureListener { error ->
      Timber.e(error)
    }

5. 创建 CommissioningService

commissionDevice() 函数中,我们请求从 CommissioningClient API 获取 CommissioningService。在此流程中, CommissioningClient API 会先将设备调试到 Local Android 结构,然后返回一个包含 CommissioningRequestMetadata 对象的回调:

public interface CommissioningService {
interface Callback {
    void onCommissioningRequested(CommissioningRequestMetadata metadata);
  }
}

现在,我们必须继承 CommissioningService.Callback,并提供为示例应用调试设备所需的功能。下面是一个基本 CommissioningService 实现的示例:

class MatterCommissioningService : Service(), CommissioningService.Callback {
   private val commissioningServiceDelegate =
     CommissioningService.Builder(this)
       .setCallback(this)
       .build()

   override fun onBind(intent: Intent) = commissioningServiceDelegate.asBinder()

   override fun onCommissioningRequested(metadata: CommissioningRequestMetadata) {
     // perform commissioning

     commissioningServiceDelegate
       .sendCommissioningComplete(CommissioningCompleteMetadata.builder().build())
   }
 }

第 1 步:探索自定义 AppCommissioningService

为帮助您着手使用,我们已经为自定义 CommissioningService 定义了基本类结构。下面简要介绍了这项服务的功能。如需按示例操作,请在 java/commissioning 中打开 AppCommissioningService

我们添加了针对 Home Mobile SDK API 的以下导入内容:

import com.google.android.gms.home.matter.commissioning.CommissioningCompleteMetadata
import com.google.android.gms.home.matter.commissioning.CommissioningRequestMetadata
import com.google.android.gms.home.matter.commissioning.CommissioningService

AppCommissioningService 还包含 Matter 代码库 (connectedhomeip) 中的库:

import com.google.home_sample_app_for_matter.chip.ChipClient

最后,该服务包含用于支持 HiltKotlin 协程的导入代码。

接下来,我们创建构造函数并设置一些内容,包括 commissioningServiceDelegate,我们将在调试完成时使用它通知 Google Play 服务。

private lateinit var commissioningServiceDelegate: CommissioningService
...
commissioningServiceDelegate = CommissioningService.Builder(this).setCallback(this).build()

现在该添加调试函数了。

第 2 步:替换 onCommissioningRequested

如需调试设备以使用该应用的开发结构,请完成以下步骤:

  1. java/commissioning中打开AppCommissioningService
  2. 找到 onCommissioningRequested() 函数。我们提供了一条输出 CommissioningRequestMetadata 的日志消息。替换 // CODELAB: onCommissioningRequested() 注释,以启动 serviceScope 协程并获取 deviceId
    // Perform commissioning on custom fabric for the sample app.
    serviceScope.launch {
      val deviceId = devicesRepository.incrementAndReturnLastDeviceId()
    
  3. 执行调试。在这一步中,我们可以传递 CommissioningRequestMetadata 对象中返回的设备信息。ChipClient 会使用这些元数据信息在适用于 Matter 的 GHSA 应用和您的设备之间创建一个安全通道。
    try {
      Timber.d(
          "Commissioning: App fabric -> ChipClient.establishPaseConnection(): deviceId [${deviceId}]")
      chipClient.awaitEstablishPaseConnection(
          deviceId,
          metadata.networkLocation.ipAddress.hostAddress!!,
          metadata.networkLocation.port,
          metadata.passcode)
      Timber.d(
          "Commissioning: App fabric -> ChipClient.commissionDevice(): deviceId [${deviceId}]")
      chipClient.awaitCommissionDevice(deviceId, null)
    } catch (e: Exception) {
      Timber.e(e, "onCommissioningRequested() failed")
      // No way to determine whether this was ATTESTATION_FAILED or DEVICE_UNREACHABLE.
      commissioningServiceDelegate
          .sendCommissioningError(CommissioningError.OTHER)
          .addOnSuccessListener {
            Timber.d(
                "Commissioning: commissioningServiceDelegate.sendCommissioningError() succeeded")
          }
          .addOnFailureListener { e2 ->
            Timber.e(e2, "Commissioning: commissioningServiceDelegate.sendCommissioningError() failed")
          }
      return@launch
    }
    
  4. 使用 commissioningServiceDelegate 告知 Google Play 服务已完成调试。在 .sendCommissioningComplete() 中,传递 CommissioningCompleteMetadata
    commissioningServiceDelegate
        .sendCommissioningComplete(
            CommissioningCompleteMetadata.builder().setToken(deviceId.toString()).build())
        .addOnSuccessListener {
          Timber.d("Commissioning: commissioningServiceDelegate.sendCommissioningComplete() succeeded")
        }
        .addOnFailureListener { e ->
          Timber.e(e, "Commissioning: commissioningServiceDelegate.sendCommissioningComplete() failed")
        }
    

运行应用

现在,调试我们本地结构所需的所有代码已准备就绪,是时候对其进行测试了。选择您的 Android 设备,然后运行该应用。在主屏幕上,点按添加设备,然后完成相应步骤以调试设备。

调试完成后,您的设备将采用两种结构脉络:本地 Android 结构脉络和你的本地开发结构脉络。每种结构都有自己的一组凭据和唯一的 64 位结构 ID。

6. 控制设备

通过调试开发结构,您可以使用 Matter 代码库 (connectedhomeip) 中的库来控制示例应用中的设备。

我们创建了一些辅助类,以便更轻松地访问设备集群和发送命令。如需了解详情,请在 java/clusters 中打开 ClustersHelper。此单例帮助程序会导入以下库以访问设备信息:

import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipStructs

我们可以使用此类来获取设备的开启/关闭集群,然后调用 .toggle

suspend fun toggleDeviceStateOnOffCluster(deviceId: Long, endpoint: Int) {
  Timber.d("toggleDeviceStateOnOffCluster())")
  val connectedDevicePtr =
      try {
        chipClient.getConnectedDevicePointer(deviceId)
      } catch (e: IllegalStateException) {
        Timber.e("Can't get connectedDevicePointer.")
        return
      }
  return suspendCoroutine { continuation ->
    getOnOffClusterForDevice(connectedDevicePtr, endpoint)
        .toggle(
            object : ChipClusters.DefaultClusterCallback {
              override fun onSuccess() {
                continuation.resume(Unit)
              }
              override fun onError(ex: Exception) {
                Timber.e("readOnOffAttribute command failure: $ex")
                continuation.resumeWithException(ex)
              }
            })
  }
}

开启/关闭设备

在您调试设备后,CommissioningResult 中返回的载荷会添加到 DataStore 中。这样一来,我们的应用就可以访问设备信息,供我们用来发送命令。

Matter 应用由事件驱动。在初始化 Matter 堆栈时,集群服务会监听传入的消息。设备调试后,Matter 客户端会通过在设备调试期间建立的安全操作通道发送命令。

在设备上,对数据包进行验证、解密,然后通过回调进行调度。回调函数包括 EndpointId、ClusterId 和 AttributeId,可从 attributePath 访问。例如,可以在 Matter 设备上实现以下代码:

void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t mask, uint8_t type,
                                       uint16_t size, uint8_t * value)
{
    // handle callback
    ClusterId clusterId     = attributePath.mClusterId;
    AttributeId attributeId = attributePath.mAttributeId;
}

在接下来的步骤中,您将使用 Matter SDK 和 ClustersHelper 来切换设备。

  1. 前往 java/screens/device 中的 DeviceViewModel
  2. 找到 updateDeviceStateOn 函数。
  3. // CODELAB: toggle 注释替换为调用 clustersHelper 的代码,然后更新设备仓库:
    Timber.d("Handling real device")
        try {
          clustersHelper.setOnOffDeviceStateOnOffCluster(deviceUiModel.device.deviceId, isOn, 1)
          devicesStateRepository.updateDeviceState(deviceUiModel.device.deviceId, true, isOn)
        } catch (e: Throwable) {
          Timber.e("Failed setting on/off state")
        }
    

此函数是从 DeviceScreen 调用的:

// On/Off Switch click.
val onOnOffClick: (value: Boolean) -> Unit = { value ->
  deviceViewModel.updateDeviceStateOn(deviceUiModel!!, value)
}

运行应用

运行应用以重新加载更新。在主屏幕上,开启和关闭设备。

7. 与其他生态系统共享设备

在 Matter 规范中,共用设备称为多管理员流程。

在前面的步骤中,我们了解到 Home Mobile SDK 可以调试设备到 Local Android 结构脉络以及示例应用的开发结构。这是多管理员流程的一个示例,其中设备可以调试到多个结构。

现在,您可能希望与更多织物共用设备,尤其是在家庭用户对于应用和平台有自己的偏好的情况下。

Home Mobile SDK 在 ShareDeviceRequest API 中提供了此功能,允许您:

  1. 打开设备的临时调试窗口。
  2. 更改设备的状态,使其可以调试到其他结构。
  3. 通过其他应用和生态系统控制您的设备。

在接下来的步骤中,您将使用 Home Mobile SDK 来共享设备。

第 1 步:创建 GPS 活动启动器

与我们在委托开发架构时创建的调试 Activity 启动器类似,我们也创建了一个共享设备 Activity 启动器来处理来自 CommissioningClient API 的 IntentSender

  1. java/screens/device/ 文件夹中打开 DeviceScreen
  2. // CODELAB: shareDeviceLauncher definition 注释替换为以下代码,以注册和处理 .shareDevice() Activity 结果:
    val shareDeviceLauncher = rememberLauncherForActivityResult(
      contract = ActivityResultContracts.StartIntentSenderForResult()
    ) { result ->
      // Commission Device Step 5.
      // The Share Device activity in GPS (step 4) has completed.
      val resultCode = result.resultCode
      if (resultCode == Activity.RESULT_OK) {
        deviceViewModel.shareDeviceSucceeded()
      } else {
        deviceViewModel.shareDeviceFailed(resultCode)
      }
    }
    

第 2 步:触发共用设备操作

在此步骤中,用户可以通过点击设备屏幕上的“共享”按钮来触发“共享设备”操作。然后,系统会向 deviceViewModel 发出调用,以打开用于共用设备的配对窗口。

// Share Device button click.
val onShareDevice: () -> Unit = {
 deviceViewModel.openPairingWindow(deviceUiModel!!.device.deviceId)
}

成功打开配对窗口后,deviceViewModel 会将该信息传达给界面。ViewModel 与界面之间的通信通过 StateFlow 对象完成。

// Communicate to the UI that the pairing window is open.
// UI can then launch the GPS activity for device sharing.
_pairingWindowOpenForDeviceSharing.value = true

看到 StateFlow 对象的更改时,DeviceScreen 进行以下调用:

shareDevice(activity!!.applicationContext, shareDeviceLauncher, deviceViewModel)

第 3 步:调用 API

现在该启动共用设备任务了。

  1. java/screens/device/ 文件夹中打开 DeviceScreen.kt
  2. 找到 shareDevice() 函数。将 // CODELAB: shareDevice 注释替换为 ShareDeviceRequestDeviceDescriptor 提供有关设备的具体信息,例如设备的供应商 ID、产品 ID 和 deviceType。在本示例中,我们对值进行硬编码。
    val shareDeviceRequest =
      ShareDeviceRequest.builder()
        .setDeviceDescriptor(DeviceDescriptor.builder().build())
        .setDeviceName("GHSAFM temp device name")
    
  3. 设置 CommissioningWindow参数
        .setCommissioningWindow(
            CommissioningWindow.builder()
                .setDiscriminator(Discriminator.forLongValue(DISCRIMINATOR))
                .setPasscode(SETUP_PIN_CODE)
                .setWindowOpenMillis(SystemClock.elapsedRealtime())
                .setDurationSeconds(OPEN_COMMISSIONING_WINDOW_DURATION_SECONDS.toLong())
                .build())
        .build()
    
  4. 调用 .getCommissioningClient(),仅限这一次,使用 .shareDevice() API。
    Matter.getCommissioningClient(context)
        .shareDevice(shareDeviceRequest)
    

commissioningClient.shareDevice() API 的成功回调会提供用于在 Google Play 服务中启动共享设备 Activity 的 IntentSender。

  1. 如需完成 shareDevice 函数,请添加 addOnSuccessListeneraddOnFailureListener。成功后,会在 shareDeviceLauncher 上调用 launch,以启动用于共享设备的 GPS activity。
        .addOnSuccessListener { result ->
          Timber.d("ShareDevice: Success getting the IntentSender: result [${result}]")
          shareDeviceLauncher.launch(IntentSenderRequest.Builder(result).build())
        }
        .addOnFailureListener { error ->
          Timber.e(error)
          deviceViewModel.showMsgDialog("Share device failed", error.toString())
        }
    

运行应用

如需与其他生态系统共享 Matter 设备,你需要在 Android 设备上安装其他平台。我们已经创建了示例应用的另一个实例,您可以将其用作目标调试器。

在 Android 设备上安装目标调试器后,验证您是否可以共用 Matter 设备。目标调试器应用带有 GHSAFM-TC 标签。

您的设备现在可以采用三种结构:

  1. 本地 Android 结构。
  2. 您的开发结构(此应用)。
  3. 这是您刚刚与之共享设备的第三个结构。

8. 后续步骤

恭喜

恭喜,您已成功完成此 Codelab,并学习了如何使用 Home Mobile SDK 调试和分享设备。

如果您在使用示例应用时遇到问题,请尝试完成以下步骤来验证您的环境:

如果您对使用示例应用有疑问或发现了代码 bug,可以将问题提交到 GitHub 代码库中的问题跟踪器:

如需获得 Google 关于技术问题的官方指导,请访问智能家居开发者论坛:

如需从社区获取技术支持,请在 Stack Overflow 上使用 google-smart-home 标签: