建構 Matter 專用的 Android 應用程式

1. 歡迎

Matter 的目標是統一 IoT 標準,讓智慧住宅裝置能跨各種生態系統連線,例如 Google Home、Zigbee、藍牙網狀網路和 Z-Wave 等。

行動裝置是與智慧住宅裝置互動的中心點。如要自行建構支援 Matter 裝置的 Android 應用程式,我們很樂意協助您快速入門。

支援 Matter 的 Google Home 範例應用程式 (GHSA for Matter) 展示了 Home Mobile SDK API,可讓使用者委派及共用裝置。您也可以將範例應用程式做為學習工具,進一步瞭解重要的 Matter 概念,以及用來偵錯及排解與 Matter 裝置互動時的問題。

學習內容

在本程式碼研究室中,您將下載範例應用程式的原始碼,並瞭解如何使用 Home Mobile SDK 委派及共用裝置。您也會瞭解如何使用 Matter 存放區 (connectedhomeip) 中的委派和叢集程式庫。

下載範例應用程式後,我們將在 Android Studio 中檢查原始碼,並實作下列 Home Mobile SDK API

此外,您也會進一步瞭解委派概念、Matter 網狀架構,以及如何控制 Matter 裝置。

軟硬體需求

開始之前,請務必完成下列步驟:

您不需要中樞裝置 (例如 Google Nest Hub 第 2 代),即可透過範例應用程式委派及控制裝置。

2. 做好準備

程式碼研究室的範例應用程式位於 codelab 分支。如要開始使用程式碼研究室原始碼,可以下載 ZIP 檔案。

您將使用這個 codelab ZIP 檔案建構可運作的範例。

程式碼研究室版本

codelab 分支已標記範例應用程式的 2.0.0 版本。如要在完成每個步驟時比較更新內容,可以下載這個版本的完整原始碼。

如要複製 GitHub 存放區,請按照範例應用程式 README 中的操作說明進行。

依附元件

我們會逐步說明分享及委派裝置所需的原始碼,但建議您先瞭解下列依附元件,再開始操作。請注意,這些依附元件是在 libs.versions.toml 檔案中宣告,而用法則是在 build.gradle.kts 檔案中指定。

原始碼

系統已為您建立使用者介面和大部分功能。

在本程式碼研究室中,我們將在下列檔案中新增 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()

方便您快速找到程式碼研究室中的對應章節。

3. Google 佣金

如要控制裝置並允許裝置在同一架構內彼此通訊,必須由委派者 (在本例中為 Google Home Sample App for Matter 這個範例應用程式) 委派裝置。

請務必瞭解 Matter 委派的下列概念:

  • 裝置可透過 Fabric 互相通訊。
  • Fabric 會維護一組共用的專屬憑證。
  • 生態系統負責核發信任的根憑證、指派網狀架構 ID,以及指派不重複的節點 ID。生態系統是委派者的後端服務,例如 Google Home 生態系統的住家圖表。
  • 裝置可委派給多個網狀架構 (多位管理員功能)。

如要委派裝置,必須使用 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 網狀架構。

  1. 委派程序從 commissionDevice() 函式開始。首先,請定義 CommissioningRequest。使用這項預設設定時,裝置只會委派給本機 Android 網狀架構。
  2. Matter 是 Home Mobile SDK 的進入點。在下一次呼叫中,.getCommissioningClient 會透過 this (Activity) 取得 CommissioningClient。
  3. .commissionDevice() 接受 CommissioningRequest
  4. 最後,系統會呼叫 .addOnSuccessListener 來處理 CommissioningResult,並啟動 Google Play 服務 (GPS) 的「Commission Device」活動。
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 的程序。

接下來,您將瞭解如何將裝置委派給開發環境。

如要瞭解委派程序中的使用者介面,請參閱 Google Home Sample App for Matter 指南

4. 將開發 Fabric 委派給開發人員

裝置可委派給多個網狀架構。如要管理信任配對,裝置會儲存包含各種 FabricInfo 成員的 FabricTable,例如:

  • 布料識別
  • Fabric 指派給裝置的節點 ID
  • 供應商 ID
  • Fabric Id
  • 裝置運作憑證

管理網域管理員 (ADM) 會定義 Fabric 憑證。在先前的案例中,Google Play 服務是做為信任的根憑證授權單位 (CA) 的生態系統。將裝置委派給本機 Android Fabric 時,每個裝置都會包含同一組 Fabric 憑證和同一組 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() 函式,以便使用自訂服務。我們也會在「首頁」片段中新增 Activity Launcher,並使用 LiveData 物件管理 API 流程。

步驟 1:建立 GPS 活動啟動器

首先,請建立 Activity Launcher,處理 CommissioningClient API 的 IntentSender

  1. 開啟 java/com/google/homesampleapp/screens/home/ 資料夾中的 HomeScreen
  2. // 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:觸發委派裝置動作

在這個步驟中,使用者會點選主畫面右下角的「+」按鈕,觸發「Commission Device」動作。然後呼叫 commissionDevice()

val onCommissionDevice = {
  ...
  commissionDevice(activity!!.applicationContext, commissionDeviceLauncher)
}

步驟 3:呼叫 API

  1. 仍在 java/com/google/homesampleapp/screens/home 資料夾的 HomeScreen.kt 中。
  2. // CODELAB: commissionDevice 註解替換為下列 commissionDeviceRequestsetCommissioningService 會將 AppCommissioningService 繫結至 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 裝置並執行應用程式。在主畫面上輕觸「新增裝置」,然後完成裝置委派步驟。

完成委任後,裝置現在會參與兩個網狀架構:本機 Android 網狀架構和本機開發網狀架構。每個網狀架構都有自己的憑證組合和專屬的 64 位元網狀架構 ID。

6. 控制裝置

委派至開發網狀架構後,您就能使用 Matter 存放區 (connectedhomeip) 中的程式庫,透過範例應用程式控制裝置。

我們建立了一些輔助類別,方便您存取裝置叢集及傳送指令。如要瞭解詳情,請在 java/clusters中開啟 ClustersHelper。這個 Singleton 輔助程式會匯入下列程式庫,以存取裝置資訊:

import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipStructs

我們可以透過這個類別取得裝置的 On/Off 叢集,然後呼叫 .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 可將裝置委派給本機 Android 網狀架構,以及範例應用程式的開發網狀架構。這是多位管理員流程的範例,裝置可委派給多個網狀架構。

現在,你可能想與更多裝置共用應用程式,特別是如果家中的使用者對應用程式和平台有自己的偏好設定。

Home Mobile SDK 會透過 ShareDeviceRequest API 提供這項功能,讓您:

  1. 開啟裝置的暫時委派視窗。
  2. 變更裝置狀態,讓裝置委派給其他網狀架構。
  3. 從其他應用程式和生態系統控制裝置。

在後續步驟中,您將使用 Home Mobile SDK 分享裝置。

步驟 1:建立 GPS 活動啟動器

與委派至開發結構時建立的 Commissioning Activity Launcher 類似,我們也建立了 Share Device Activity Launcher,用來處理 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 會將這項事實傳達給 UI。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

現在可以啟動裝置共用工作。

  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. 設定 CommissioningWindowparameters
        .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 服務中的「分享裝置活動」。

  1. 如要完成 shareDevice 函式,請新增 addOnSuccessListeneraddOnFailureListener。成功後,系統會在 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

裝置現在可以加入以下三種網狀架構:

  1. 本機 Android 結構。
  2. 您的開發結構 (這個應用程式)。
  3. 你剛才與這個第三方家庭分享裝置。

8. 後續步驟

恭喜

恭喜!您已成功完成本程式碼研究室,並瞭解如何使用 Home Mobile SDK 委派及共用裝置。

如果範例應用程式發生問題,請嘗試完成下列步驟來驗證環境:

如果您對使用範例應用程式有任何疑問,或發現程式碼錯誤,可以將問題提交至 GitHub 存放區中的 Issue Tracker:

如要取得 Google 官方的技術問題指引,請使用智慧住宅開發人員論壇:

如要向社群尋求技術支援,請在 Stack Overflow 上使用 google-smart-home 標記: