构建 Android Matter 应用

1. 欢迎

Matter 旨在统一 IoT 标准,可连接 Google Home、Zigbee、蓝牙网状网、Z-Wave 等各种生态系统中的智能家居设备。

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

适用于 Matter 的 Google Home 示例应用(适用于 Matter 的 GHSA)展示了 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 代码库,请按照示例应用自述文件中的说明进行操作。

依赖项

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

源代码

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

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

  • java/com/google/homesampleapp/commissioning/AppCommissioningService:用于调试设备到开发结构脉络
  • java/com/google/homesampleapp/screens/home/HomeScreenjava/com/google/homesampleapp/screens/home/HomeViewModel.kt:包含 Home Mobile SDK 调试功能
  • java/com/google/homesampleapp/screens/device/DeviceScreenjava/com/google/homesampleapp/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 设置利用 Local Android Fabric 来简化将设备调试到其他 Fabric 的过程。

接下来,您将学习如何调试设备到开发结构脉络。

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

4. 调试到开发结构脉络

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

  • 织物识别
  • 结构脉络分配给设备的节点 ID
  • 供应商 ID
  • Fabric 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/com/google/homesampleapp/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.kt 中的 java/com/google/homesampleapp/screens/home 文件夹中。
  2. // CODELAB: commissionDevice 注释替换为以下 commissionDeviceRequestsetCommissioningServiceAppCommissioningService 绑定到回调函数中返回的 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. 创建调试服务

commissionDevice() 函数中,我们请求从 CommissioningClient API 获取 CommissioningService。在此流程中, CommissioningClient API 会先调试设备到本地 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.homesampleapp.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会使用这些元数据信息在 GHSA for Matter 应用与您的设备之间创建安全通道。
    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 设备,然后运行应用。在主屏幕上,点按 Add Device(添加设备),然后完成设备调试步骤。

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

6. 控制设备

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

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

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/com/google/homesampleapp/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 = remember {
 {
   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/com/google/homesampleapp/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 的成功回调提供了 IntentSender,用于启动 Google Play 服务中分享设备 activity。

  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. Local Android 结构。
  2. 您的开发结构(此应用)。
  3. 您刚与其共用设备的第三个织物。

8. 后续步骤

恭喜

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

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

如果您在使用示例应用时遇到问题或发现代码 bug,可以向 GitHub 代码库中的问题跟踪器提交问题:

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

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