1. 欢迎
Matter 旨在统一物联网标准,可将各种生态系统(如 Google Home、Zigbee、蓝牙网状网络、Z-Wave 等)中的智能家居设备连接起来。
移动设备是与智能家居设备互动的主要方式。如果您想构建自己的 Android 应用来支持 Matter 设备,我们可以帮助您快速入门。
Google Home Sample App for Matter (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 Matter 示例应用指南。
- 下载 Android Studio。
- 准备好一部搭载 Android O(8.1,API 级别 27)或更高版本的设备,以供测试。如需确保您的设备支持最新的 Matter,请参阅验证 Matter 模块和服务指南。
- 使用具有开/关功能的 Matter 设备。为了最大限度地减少环境问题,我们强烈建议您先使用 Matter 虚拟设备 (MVD)。如果您遇到任何问题,在 MVD 上使用示例应用会更容易进行调查。以下是一些其他选项:
- 使用
rootnode_dimmablelight_bCwGYSDpoe应用构建 Matter 虚拟设备。在智能家居开发者控制台中创建 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 Mobile 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 移动 SDK 调试功能java/com/google/homesampleapp/screens/device/DeviceScreen和java/com/google/homesampleapp/screens/device/DeviceViewModel:包含“共享设备”API 调用
每个文件都添加了注释,其中包含您将修改的代码块,例如:
// CODELAB: add commissioningFunction()
这样,您就可以快速找到 Codelab 中的相应部分。
3. 佣金到 Google
在您控制设备并允许它们在同一网状网络内相互通信之前,需要由调试器(在本例中为 Google Home Sample App for Matter 这个示例应用)对它们进行调试。
请务必了解以下有关 Matter 调试的概念:
- 功能块可让设备彼此通信。
- 功能块维护一组共享的唯一凭据。
- 生态系统负责颁发可信的根证书、分配功能块 ID 和分配唯一的节点 ID。生态系统是调试器的后端服务,例如 Google Home 生态系统的 Home Graph。
- 设备可以委托给多个功能区(多管理员功能)。
如需调试设备,您需要使用 CommissioningClient API。对 .commissionDevice() 的调用会返回一个 IntentSender,该 IntentSender 会在 Google Play 服务中启动相应的 activity:
interface CommissioningClient {
Task<IntentSender> commissionDevice(CommissioningRequest request);
}
在接下来的部分中,我们将介绍将设备委托给 Google Fabric 所需的最少代码。
第 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 Fabric。
- 调试过程从
commissionDevice()函数开始。首先,定义一个CommissioningRequest。在此默认配置下,设备仅委托给本地 Android 结构。 Matter是 Home Mobile SDK 的入口点。在下一次调用中,.getCommissioningClient通过this(Activity) 获取 CommissioningClient。.commissionDevice()接受CommissioningRequest。- 最后,调用
.addOnSuccessListener来处理CommissioningResult并启动 Google Play 服务 (GPS) Commission Device 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 简化将设备委托给其他 Fabric 的流程。
接下来,您将学习如何将设备调试到开发 Fabric。
如需了解调试过程中的界面概览,请参阅 Google Home Matter 示例应用指南。
4. 调试到开发结构
设备可以委托给多个功能区。为了管理可信配对,设备会存储一个包含各种 FabricInfo 成员的 FabricTable,例如:
- Fabric 识别
- 由光纤网分配给设备的节点 ID
- 供应商 ID
- Fabric ID
- 设备运行凭据
管理网域管理器 (ADM) 定义了光纤网凭据。在上述场景中,Google Play 服务是充当可信根证书授权机构 (CA) 的生态系统。将设备委托给本地 Android 结构时,每个设备都包含相同的结构凭据集和相同的证书授权机构 (CA) 集。
定制调试服务
为了委托给本地 Android 结构,我们使用默认参数在 CommissioningClient API 中构建了 CommissioningRequest:
val request: CommissioningRequest = CommissioningRequest.builder().build()
如果您想通过应用控制和管理新设备,则需要创建本地开发 Fabric 并获取操作凭据来调试设备。在此场景中,您的应用会成为一个独特的独立生态系统,可为设备分配相应的节点凭据。
您可以通过向 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 启动器
首先,我们来创建一个 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
- 仍位于
java/com/google/homesampleapp/screens/home文件夹中的HomeScreen.kt中。 - 将
// CODELAB: commissionDevice注释替换为以下commissionDeviceRequest。setCommissioningService将AppCommissioningService绑定到回调函数中返回的CommissioningService实例。当您传递自定义服务时,Home Mobile SDK 会先将设备委托给 Android 本地 Fabric,然后将初始配置载荷发送回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
如需将设备委托给应用的开发 Fabric,请完成以下步骤:
- 在
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 设备,然后运行该应用。在主屏幕上,点按添加设备,然后完成相应步骤来调试设备。
当调试完成时,设备现在参与到两个功能区中:本地 Android 功能区和本地开发功能区。每个 Fabric 都有自己的一组凭据和一个唯一的 64 位 Fabric ID。
6. 控制设备
调试到开发 Fabric 可让您使用 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 客户端会通过在设备调试期间建立的安全运行通道发送命令。
在设备上,系统会验证、解密数据包,然后通过回调进行调度。回调函数包含可从 attributePath 访问的 EndpointId、ClusterId 和 AttributeId。例如,以下代码可在 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 可用于将设备调试到本地 Android 结构,也可调试到示例应用的开发结构。这是一个多管理员流程的示例,其中设备可调试到多个结构。
现在,您可能希望与更多功能平台共享设备,尤其是在家庭成员对应用和平台有各自偏好的情况下。
Home Mobile SDK 在 ShareDeviceRequest API 中提供了此功能,可让您执行以下操作:
- 为设备打开临时调试窗口。
- 更改设备的状态,使其能够委托给其他功能区。
- 通过其他应用和生态系统控制设备。
在接下来的步骤中,您将使用 Home Mobile SDK 来共享设备。
第 1 步:创建 GPS Activity 启动器
与我们在调试到开发 Fabric 时创建的调试 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。
您的设备现在可以加入以下三种功能区:
- 本地 Android Fabric。
- 您的开发结构(此应用)。
- 您刚刚与之分享设备的第三个功能区。
8. 后续步骤
恭喜
恭喜,您已成功完成此 Codelab,并学习了如何使用 Home Mobile SDK 调试和共享设备。
如果您在运行示例应用时遇到问题,请尝试完成以下步骤来验证您的环境:
如果您在使用示例应用时遇到问题或发现代码 bug,可以向 GitHub 代码库中的问题跟踪器提交问题:
如需就技术问题获取 Google 的官方指导,请使用智能家居开发者论坛:
如需从社区获取技术支持,请在 Stack Overflow 上使用 google-smart-home 标记: