建構 Matter 專用的 Android 應用程式

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 裝置。

事前準備

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

不必透過中樞裝置 (例如 Google Nest Hub (第 2 代)),透過範例應用程式領取及控制裝置。

2. 做好準備

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

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

程式碼研究室版本

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

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

依附元件

我們將引導您完成分享及佣金裝置所需的原始碼,但在開始之前,建議您先瞭解下列依附元件。請注意,這些依附元件是在 libs.versions.toml 檔案中宣告,以及 build.gradle.kts 檔案中指定的用法

原始碼

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

在本程式碼研究室中,我們將在下列檔案中加入 Matter 功能:

  • java/commissioning/AppCommissioningService:可讓您將裝置調試到開發架構
  • java/screens/home/HomeScreenjava/screens/home/HomeViewModel.kt:包含 Home Mobile SDK 佣金功能
  • java/screens/device/DeviceScreenjava/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 架構。

  1. 調試程序會從 commissionDevice() 函式開始。首先,已定義 CommissioningRequest。如果採用這項預設設定,裝置就只會交由本機 Android 織布使用。
  2. Matter 是 Home Mobile SDK 的進入點。在下一次呼叫中,.getCommissioningClient 會透過 this (Activity) 取得 CommissioningClient。
  3. .commissionDevice() 接受 CommissioningRequest
  4. 最後,系統會呼叫 .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

  1. java/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:觸發佣金裝置動作

在這個步驟中,使用者按一下主畫面右下方的「+」按鈕,即可觸發「佣金裝置」動作。接著,系統會呼叫 commissionDevice()

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

步驟 3:呼叫 API

  1. 還是位於 java/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. 建立 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

最後,服務包含匯入內容,以便支援 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 會使用這項中繼資料資訊,在適用於 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
    }
    
  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

我們可以利用這個類別取得裝置的開啟/關閉叢集,然後呼叫 .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 能將裝置調用至 Local Android 織布,以及範例應用程式的開發架構。這是多管理流程的一個例子,其中裝置可以被委託使用多個織布。

現在,您可能會想與更多布料共用裝置,特別是在住家裡,人們在應用程式和平台中都有自己的偏好。

Home Mobile SDK 在 ShareDeviceRequest API 中提供此功能,方便您:

  1. 開啟裝置的暫時調試視窗。
  2. 變更裝置的狀態,允許裝置採用其他織布。
  3. 透過其他應用程式和生態系統控制裝置。

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

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

與我們在開發架構下進行的調試活動啟動器類似,我們建立了共用裝置活動啟動器,用來處理 CommissioningClient API 中的 IntentSender

  1. java/screens/device/ 資料夾中開啟 DeviceScreen
  2. // 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

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

  1. java/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. 設定 CommissioningWindow參數
        .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 標記: