建構 Matter 專用的 Android 應用程式

1. 歡迎

Matter 的目標是整合 IoT 標準,並連結 Google Home、Zigbee、藍牙網狀網路、Z-Wave 等各種生態系統的智慧住宅裝置。

行動裝置是智慧住宅裝置的重要互動點。如果您想自行建構支援 Matter 裝置的 Android 應用程式,我們可以協助您快速上手。

Google Home 範例應用程式 (適用於 Matter 的 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/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 的佣金

如要控制裝置並允許裝置使用相同的織布互相通訊,必須先由 Commissioner 委託,在本範例中為 Google Home 範例應用程式 (本例中為 Matter 應用程式範例)。

請務必瞭解下列關於 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,簡化將裝置交給其他織布的流程。

接下來,我們將說明如何將裝置委託開發架構。

如需在調試過程中的使用者介面總覽,請參閱 Google Home 支援 Matter 的範例應用程式

4. 調試開發架構

你可以為裝置選購多種布料。如要管理信任的配對,裝置會儲存含有多位 FabricInfo 成員的 FabricTable,例如:

  • 織品識別
  • 織布指派給裝置的節點 ID
  • 供應商 ID
  • 織布 ID
  • 裝置作業憑證

管理網域管理員 (ADM) 會定義織布憑證。在先前的情境中,Google Play 服務是一個擔任信任根憑證授權單位 (CA) 的生態系統。將裝置委託給本地 Android 架構時,每部裝置都會含有同一組布料憑證和同一組 CA。

客製化調試服務

為了針對「本機 Android 架構」執行佣金,我們使用預設參數在 CommissioningClient API 中建構 CommissioningRequest

val request: CommissioningRequest = CommissioningRequest.builder().build()

如要透過應用程式控制及管理新裝置,您必須建立本機開發架構,並取得用於佣金裝置的作業憑證。在這種情況下,您的應用程式就會成為獨特的獨立生態系統,為裝置指派適當的節點憑證。

您可以讓 Home Mobile SDK 傳送自訂服務給 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/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:觸發佣金裝置動作

在這個步驟中,使用者會觸發「佣金裝置」按一下「+」按鈕。便會撥打 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 會使用這項中繼資料資訊,在 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」。此單例輔助程式會匯入下列程式庫,存取裝置資訊:

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

  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/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. 設定 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 裝置共享的 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」GHSAFM-TC標籤。

裝置現在支援三種布料:

  1. 本地 Android 布料。
  2. 您的開發架構 (這個應用程式)。
  3. 這是你剛與裝置共用的第三層布料。

8. 後續步驟

恭喜

恭喜!您已成功完成本程式碼研究室,並學會如何使用 Home Mobile SDK 佣金和共用裝置。

如果您在使用範例應用程式時遇到問題,請嘗試完成環境驗證步驟:

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

如需 Google 針對技術問題的官方指引,請使用智慧型住宅開發人員論壇:

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