1. 歡迎
Matter 旨在統合 IoT 標準,用以整合 Google Home、Zigbee、藍牙網格、Z-Wave 等多個生態系統的智慧住宅裝置。
行動裝置是智慧型住宅裝置的中心互動點。如想建構能支援 Matter 裝置的 Android 應用程式,我們可以協助您快速上手。
Matter 適用的 Google Home 範例應用程式 (GHSA) 介紹了 Home Mobile SDK API,可讓使用者採用並共用裝置。此外,您也可以將範例應用程式當做學習工具,進一步瞭解 Matter 的重要概念,以及這項工具可用來偵錯及排解與 Matter 裝置的互動情形。
要執行的步驟
在本程式碼研究室中,您將下載範例應用程式的原始碼,並瞭解如何使用 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 虛擬裝置 (MVD)。如果遇到問題,會更容易調查範例應用程式是否用於 MVD。以下是其他幾個選項:
- 使用
rootnode_dimmablelight_bCwGYSDpoe
應用程式建立 Matter 虛擬裝置。在 Google Home 開發人員控制台中建立 Matter 整合時,請使用0xFFF1
做為供應商 ID,並使用0x8000
做為產品 ID。 - 使用
all-clusters-app
建構 Espressif 裝置。在 Google Home 開發人員控制台中建立 Matter 整合時,請使用0xFFF1
做為供應商 ID,並使用0x8001
做為產品 ID。
- 使用
- 瞭解如何設定 Google Play 服務。
不必透過中樞裝置 (例如 Google Nest Hub (第 2 代)),透過範例應用程式領取及控制裝置。
2. 做好準備
程式碼研究室的範例應用程式位於 codelab
分支版本中。如要開始使用程式碼研究室的原始碼,您可以下載 ZIP 檔案。
您將使用這個 codelab
ZIP 檔案建構可正常運作的範例。
程式碼研究室版本
codelab
分支版本已標記範例應用程式的 2.0.0 版。如要在執行每個步驟時比較更新內容,您可以下載這個版本的已完成原始碼。
如要複製 GitHub 存放區,請按照範例應用程式 README 中的指示操作。
依附元件
我們將引導您完成分享及佣金裝置所需的原始碼,但在開始之前,建議您先瞭解下列依附元件。請注意,這些依附元件是在 libs.versions.toml 檔案中宣告,以及 build.gradle.kts 檔案中指定的用法
- Home Mobile SDK
- Matter SDK 程式庫。
- Jetpack Compose。此為使用 Compose 完整實作的 UI。
- 質感設計詳情請參閱「MDC-103 Android:包含顏色、高度和類型的 Material Design 主題設定」和「質感設計主題建構工具」。
- Proto DataStore 用於保存應用程式資料。Datastore 存放區和序列化程式會儲存在
java/data
中,包括裝置和使用者偏好設定的結構定義。如要進一步瞭解 DataStore,請參閱「使用 Proto DataStore」一文。 - Hilt 可保存資料,並支援依附元件插入。
原始碼
系統已為您建立使用者介面和大部分功能。
在本程式碼研究室中,我們將在下列檔案中加入 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()
這可讓您快速找到程式碼研究室中的對應部分。
3. Google 委託支付
這部裝置必須經過委託員委託,才能控制並允許這些裝置透過同一織布網路互相通訊,在本例中,這是 Matter 適用的 Google Home 範例應用程式。
請務必瞭解下列有關 Matter 執行的概念:
- Fabrics 可讓裝置互相通訊。
- Fabrics 會維護一組共用的唯一憑證。
- 生態系統負責核發信任的根憑證、指派架構 ID,並指派專屬的節點 ID。生態系統是指委員的後端服務,例如 Google Home 生態系統的 Home Graph。
- 裝置可委託使用多種織布布料 (多系統管理功能)。
如要調試裝置,必須使用 CommissioningClient API。對 .commissionDevice()
的呼叫會傳回 IntentSender,該函式會在 Google Play 服務中啟動適當的活動:
interface CommissioningClient { Task<IntentSender> commissionDevice(CommissioningRequest request); }
在接下來的章節中,我們會說明向 Google 布料採購裝置時,所需的最低程式碼。
步驟 1:活動啟動器
如要處理 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) 佣金裝置活動。
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,簡化將裝置交給其他布料的流程。
接下來,您將瞭解如何委託裝置採用開發架構。
如需調試程序期間的使用者介面總覽,請參閱 Matter 適用的 Google Home 範例應用程式指南。
4. 委託開發架構
裝置可以採用多種織布裝置。如要管理信任的配對,裝置會儲存包含各種 FabricInfo
成員的 FabricTable
,例如:
- 布料識別
- 架構指派給裝置的節點 ID
- 供應商 ID
- 織品 ID
- 裝置運作憑證
管理網域管理員 (ADM) 會定義架構憑證。在先前的情境中,Google Play 服務是做為信任的根憑證授權單位 (CA) 生態系統。將裝置委託到當地 Android 布料時,每部裝置將包含同一組架構憑證和同一組 CA。
客製化調試服務
為配合 Local 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()
函式以使用自訂服務。我們也會將活動啟動器新增至主畫面片段,並使用 LiveData 物件來管理 API 流程。
步驟 1:建立 GPS 活動啟動器
首先,請建立活動啟動器,處理 CommissioningClient API 中的 IntentSender
。
- 在
java/screens/home/
資料夾中開啟HomeScreen
。 - 將
// CODELAB: commissionDeviceLauncher definition
註解替換為下列程式碼,即可註冊及處理佣金活動結果: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/screens/home/
資料夾的HomeScreen.kt
中。 - 將
// 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
」。這個 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 活動啟動器
與我們在開發架構下進行的調試活動啟動器類似,我們建立了共用裝置活動啟動器,用來處理 CommissioningClient API 中的 IntentSender
。
- 在
java/screens/device/
資料夾中開啟DeviceScreen
。 - 將
// CODELAB: shareDeviceLauncher definition
註解替換為下列程式碼,以註冊及處理.shareDevice()
活動結果: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
和 UI 之間的通訊是透過 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 成功回呼後,會提供 IntentSender,可用來在 Google Play 服務中啟動「共用裝置活動」。
- 如要完成
shareDevice
函式,請新增addOnSuccessListener
和addOnFailureListener
。成功時,系統會在shareDeviceLauncher
上呼叫launch
,以啟動用於裝置共享的 GPS 活動。.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. 後續步驟
恭喜
恭喜!您已成功完成本程式碼研究室,並學會如何使用 Home Mobile SDK 收取及共用裝置。
如果範例應用程式發生問題,請嘗試完成相關環境驗證步驟:
如果您對範例應用程式有任何疑問,或是發現程式碼錯誤,則可將問題提交至 GitHub 存放區中的 Issue Tracker:
如要取得 Google 正式提供的技術問題指引,請使用智慧型住宅開發人員論壇:
如需社群提供的技術支援,請使用 Stack Overflow 上的 google-smart-home
標記: