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 设备。
所需条件
在开始之前,请务必完成以下步骤:
- 查看 Google Home 示例应用 for Matter 指南。
- 下载 Android Studio。
- 拥有可用于测试的 Android O(8.1,API 级别 27)或更高版本的设备。为了确保你的设备支持最新的 Matter 支持,请参阅验证 Matter 模块和服务指南。
- 使用具有开启/关闭功能的 Matter 设备。为了最大限度地减少环境问题,我们强烈建议您从 Matter Virtual Device (MVD) 入手。如果您遇到问题,如果针对 MVD 使用了示例应用,调查起来会容易得多。下面是其他一些选项:
- 使用
rootnode_dimmablelight_bCwGYSDpoe
应用构建 Matter 虚拟设备。当您在 Home Developer Console 中创建 Matter 集成时,请使用0xFFF1
作为供应商 ID,使用0x8000
作为产品 ID。 - 使用
all-clusters-app
构建 Espressif 设备。当您在 Home Developer Console 中创建 Matter 集成时,请使用0xFFF1
作为供应商 ID,使用0x8001
作为产品 ID。
- 使用
- 查看如何设置 Google Play 服务。
您无需使用 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 中指定
- Home 移动 SDK
- Matter SDK 库。
- Jetpack Compose。界面是使用 Compose 完全实现的。
- Material Design。如需了解详情,请参阅 MDC-103 Android:使用颜色、高度和类型设置 Material 主题 (Kotlin) 和 Material 主题构建器。
- Proto DataStore,用于保留应用数据。Datastore 代码库和序列化器存储在
java/data
中,包括设备和用户偏好设置的架构。如需详细了解 DataStore,请参阅使用 Proto DataStore。 - Hilt 可持久保留数据并支持依赖项注入。
源代码
我们已为您创建了界面和大部分功能。
在此 Codelab 中,我们将向以下文件添加 Matter 功能:
java/commissioning/AppCommissioningService
:允许您针对开发结构脉络调试设备java/screens/home/HomeScreen
和java/screens/home/HomeViewModel.kt
:包含 Home Mobile SDK 调试功能java/screens/device/DeviceScreen
和java/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 结构。
- 调试过程从
commissionDevice()
函数开始。首先,定义CommissioningRequest
。使用此默认配置时,设备仅限使用本地 Android 结构。 Matter
是 Home Mobile SDK 的入口点。在下一次调用中,.getCommissioningClient
将通过this
(Activity) 获取 CommissioningClient。.commissionDevice()
接受CommissioningRequest
。- 最后,系统会调用
.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
。
- 在
java/screens/home/
文件夹中打开HomeScreen
。 - 将
// 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
- 仍在
HomeScreen.kt
的java/screens/home/
文件夹中。 - 将
// CODELAB: commissionDevice
注释替换为以下commissionDeviceRequest
。setCommissioningService
会将AppCommissioningService
绑定到回调函数中返回的CommissioningService
实例。当您传递自定义服务时,Home Mobile SDK 会先将设备调试到 Android 本地结构脉络,然后再将初始配置载荷发送回AppCommissioningService
。val commissionDeviceRequest = CommissioningRequest.builder() .setCommissioningService(ComponentName( context, AppCommissioningService::class.java)) .build()
- 调用
.getCommissioningClient()
,然后调用.commissionDevice()
。
Matter.getCommissioningClient(context) .commissionDevice(commissionDeviceRequest)
如需完成 commissionDevice
函数,请添加 addOnSuccessListener
和 addOnFailureListener
:
.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
最后,该服务包含用于支持 Hilt 和 Kotlin 协程的导入代码。
接下来,我们创建构造函数并设置一些内容,包括 commissioningServiceDelegate
,我们将在调试完成时使用它通知 Google Play 服务。
private lateinit var commissioningServiceDelegate: CommissioningService ... commissioningServiceDelegate = CommissioningService.Builder(this).setCallback(this).build()
现在该添加调试函数了。
第 2 步:替换 onCommissioningRequested
如需调试设备以使用该应用的开发结构,请完成以下步骤:
- 在
java/commissioning
中打开AppCommissioningService
。 - 找到
onCommissioningRequested()
函数。我们提供了一条输出CommissioningRequestMetadata
的日志消息。替换// CODELAB: onCommissioningRequested()
注释,以启动serviceScope
协程并获取deviceId
。// Perform commissioning on custom fabric for the sample app. serviceScope.launch { val deviceId = devicesRepository.incrementAndReturnLastDeviceId()
- 执行调试。在这一步中,我们可以传递 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 }
- 使用
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
来切换设备。
- 前往
java/screens/device
中的DeviceViewModel
。 - 找到
updateDeviceStateOn
函数。 - 将
// 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 中提供了此功能,允许您:
- 打开设备的临时调试窗口。
- 更改设备的状态,使其可以调试到其他结构。
- 通过其他应用和生态系统控制您的设备。
在接下来的步骤中,您将使用 Home Mobile SDK 来共享设备。
第 1 步:创建 GPS 活动启动器
与我们在委托开发架构时创建的调试 Activity 启动器类似,我们也创建了一个共享设备 Activity 启动器来处理来自 CommissioningClient API 的 IntentSender
。
- 在
java/screens/device/
文件夹中打开DeviceScreen
。 - 将
// 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
现在该启动共用设备任务了。
- 在
java/screens/device/
文件夹中打开DeviceScreen.kt
。 - 找到
shareDevice()
函数。将// CODELAB: shareDevice
注释替换为ShareDeviceRequest
。DeviceDescriptor
提供有关设备的具体信息,例如设备的供应商 ID、产品 ID 和 deviceType。在本示例中,我们对值进行硬编码。val shareDeviceRequest = ShareDeviceRequest.builder() .setDeviceDescriptor(DeviceDescriptor.builder().build()) .setDeviceName("GHSAFM temp device name")
- 设置 CommissioningWindow 和参数。
.setCommissioningWindow( CommissioningWindow.builder() .setDiscriminator(Discriminator.forLongValue(DISCRIMINATOR)) .setPasscode(SETUP_PIN_CODE) .setWindowOpenMillis(SystemClock.elapsedRealtime()) .setDurationSeconds(OPEN_COMMISSIONING_WINDOW_DURATION_SECONDS.toLong()) .build()) .build()
- 调用
.getCommissioningClient()
,仅限这一次,使用.shareDevice()
API。Matter.getCommissioningClient(context) .shareDevice(shareDeviceRequest)
commissioningClient.shareDevice()
API 的成功回调会提供用于在 Google Play 服务中启动共享设备 Activity 的 IntentSender。
- 如需完成
shareDevice
函数,请添加addOnSuccessListener
和addOnFailureListener
。成功后,会在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 标签。
您的设备现在可以采用三种结构:
- 本地 Android 结构。
- 您的开发结构(此应用)。
- 这是您刚刚与之共享设备的第三个结构。
8. 后续步骤
恭喜
恭喜,您已成功完成此 Codelab,并学习了如何使用 Home Mobile SDK 调试和分享设备。
如果您在使用示例应用时遇到问题,请尝试完成以下步骤来验证您的环境:
如果您对使用示例应用有疑问或发现了代码 bug,可以将问题提交到 GitHub 代码库中的问题跟踪器:
如需获得 Google 关于技术问题的官方指导,请访问智能家居开发者论坛:
如需从社区获取技术支持,请在 Stack Overflow 上使用 google-smart-home
标签: