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 设备。
所需条件
在开始之前,请务必完成以下步骤:
- 查看适用于 Matter 的 Google Home 示例应用指南。
- 下载 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 代码库,请按照示例应用自述文件中的说明进行操作。
依赖项
我们将指导您完成共享和调试设备所需的源代码,但在开始之前,了解以下依赖项可能会有所帮助。请注意,这些依赖项在文件 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/com/google/homesampleapp/commissioning/AppCommissioningService
:用于调试设备到开发结构脉络java/com/google/homesampleapp/screens/home/HomeScreen
和java/com/google/homesampleapp/screens/home/HomeViewModel.kt
:包含 Home Mobile SDK 调试功能java/com/google/homesampleapp/screens/device/DeviceScreen
和java/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 结构。
- 调试流程从
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 设置利用 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
。
- 打开
java/com/google/homesampleapp/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/com/google/homesampleapp/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. 创建调试服务
在 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
最后,该服务包含支持 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
会使用这些元数据信息在 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 }
- 使用
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
来切换设备。
- 转到
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/com/google/homesampleapp/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 = 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
现在该启动共享设备任务了。
- 打开
java/com/google/homesampleapp/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 的成功回调提供了 IntentSender,用于启动 Google Play 服务中分享设备 activity。
- 如需完成
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 标签。
您的设备现在可以参与三种结构:
- Local Android 结构。
- 您的开发结构(此应用)。
- 您刚与其共用设备的第三个织物。
8. 后续步骤
恭喜
恭喜,您已成功完成此 Codelab,并学习了如何使用 Home Mobile SDK 调试和共享设备。
如果您在使用示例应用时遇到问题,请尝试完成以下步骤来验证您的环境:
如果您在使用示例应用时遇到问题或发现代码 bug,可以向 GitHub 代码库中的问题跟踪器提交问题:
如需获得 Google 针对技术问题的官方指导,请访问智能家居开发者论坛:
如需从社区获取技术支持,请在 Stack Overflow 上使用 google-smart-home
标记: