สร้างแอปบนอุปกรณ์เคลื่อนที่โดยใช้ Home API ใน Android

1. ก่อนเริ่มต้น

Google Home API มีชุดไลบรารีสำหรับนักพัฒนาแอป Android ในการเข้าถึงระบบนิเวศของ Google Home API ใหม่เหล่านี้ช่วยให้นักพัฒนาแอปสร้างแอปที่เปิดใช้งานและควบคุมอุปกรณ์สมาร์ทโฮมได้อย่างราบรื่น

Google มีแอปตัวอย่างสำหรับ Android สําหรับนักพัฒนาแอปที่ต้องการเข้าถึงตัวอย่างที่ใช้งานได้โดยใช้ Google Home API Codelab นี้อิงตามสาขาของแอปตัวอย่างซึ่งจะแนะนำวิธีใช้ Permissions, Commissioning, Device และ Structure API

ข้อกำหนดเบื้องต้น

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้างแอป Android โดยใช้ Google Home API ด้วยแนวทางปฏิบัติแนะนำ
  • วิธีใช้ Device and Structure API เพื่อแสดงและควบคุมสมาร์ทโฮม
  • วิธีใช้ Commissioning API เพื่อเพิ่มอุปกรณ์ไปยังระบบนิเวศของ Google Home

ไม่บังคับ: ตั้งค่าบ้าน

ก่อนใช้ Google Home API คุณจะต้องตั้งค่าบ้านในบัญชี Google โดยใช้แอป Google Home และเพิ่มอุปกรณ์ 2-3 เครื่อง ส่วนนี้จะอธิบายวิธีดำเนินการโดยใช้ Google Home Playground ซึ่งมีอุปกรณ์สมาร์ทโฮมเสมือนจริง

เปิด home-playground.withgoogle.com ในเว็บเบราว์เซอร์ ลงชื่อเข้าใช้ด้วยบัญชี Google แล้วดูว่าอุปกรณ์จำลองต่อไปนี้ปรากฏขึ้นหรือไม่

  • outlet1: ปลั๊กเปิด/ปิด
  • light2: หลอดไฟหรี่แสงได้
  • light3: เปิด/ปิดไฟ
  • ac3: เครื่องปรับอากาศ
  • blinds4: อุปกรณ์ปิดบังหน้าต่าง
  • washer5: เครื่องซักผ้าอัจฉริยะ

914d23a42b72df8f.png

เปิดแอป Google Home บนอุปกรณ์เคลื่อนที่ แตะปุ่มเพิ่ม แล้วเลือกใช้ได้กับ Google Home ค้นหา "playground" ในรายการ แล้วเลือกโปรเจ็กต์ "Google Home Playground" แล้วแตะต่อไป

e9ec257b0d9b1ed2.png29fd7416e274d216.pngd974573af0611fd8.png

Google Home Playground จะแสดงหน้าการให้สิทธิ์บัญชี แตะให้สิทธิ์หรือลงชื่อเข้าใช้ด้วย Google คุณจะเห็นอุปกรณ์ทั้งหมดที่กำหนดค่าจากเว็บแอปในแอปบนอุปกรณ์เคลื่อนที่

13108a3a15440151.png8791a6d33748f7c8.png

เลือกอุปกรณ์ทั้งหมดและทำตามขั้นตอนการตั้งค่าให้เสร็จสมบูรณ์ เมื่อกลับไปที่หน้าแรก คุณจะเห็นอุปกรณ์ทั้งหมดที่ใช้ได้

2b021202e6fd1750.png

อุปกรณ์ที่รองรับในรายการพร้อมใช้งานกับ Google Home API แล้ว

2. สร้างโปรเจ็กต์

แผนภาพต่อไปนี้แสดงสถาปัตยกรรมของแอป Home APIs

สถาปัตยกรรมของ Home API สําหรับแอป Android

  • โค้ดแอป: โค้ดหลักที่นักพัฒนาแอปใช้สร้างอินเทอร์เฟซผู้ใช้ของแอปและตรรกะในการโต้ตอบกับ SDK ของ Home APIs
  • Home APIs SDK: Home APIs SDK ที่ Google ให้บริการจะทํางานร่วมกับบริการ Home APIs ใน GMSCore เพื่อควบคุมอุปกรณ์สมาร์ทโฮม นักพัฒนาแอปสร้างแอปที่ทำงานร่วมกับ Home API ได้โดยรวมแอปเข้ากับ Home APIs SDK
  • GMSCore ใน Android: GMSCore หรือที่เรียกว่าบริการ Google Play เป็นแพลตฟอร์มของ Google ที่ให้บริการหลักของระบบ ซึ่งเปิดใช้ฟังก์ชันหลักในอุปกรณ์ Android ที่ผ่านการรับรองทั้งหมด โมดูล Home ของบริการ Google Play มีบริการที่โต้ตอบกับ Home API

ตั้งค่า Home SDK

ทําตามขั้นตอนที่ระบุไว้ในตั้งค่า SDK เพื่อรับ SDK เวอร์ชันล่าสุด

รับแอปตัวอย่าง

ซอร์สโค้ดสําหรับแอปตัวอย่างมีอยู่ใน GitHub Codelab นี้ใช้ตัวอย่างจากสาขา codelab-branch-1 ของแอปตัวอย่าง

ไปที่ตำแหน่งที่ต้องการบันทึกโปรเจ็กต์และโคลนสาขา codelab-branch-1 โดยทำดังนี้

$ git clone -b codelab-branch-1 https://github.com/google-home/google-home-api-sample-app-android.git

สร้างแอปตัวอย่าง

ทำตามขั้นตอนที่ 1-5 ในหัวข้อสร้างแอป

32f2b3c0cd80fcf1.png

เมื่อแอปทำงานบนโทรศัพท์เรียบร้อยแล้ว คุณจะเห็นหน้าหลักของแอปตัวอย่าง แต่คุณจะลงชื่อเข้าใช้ไม่ได้จนกว่าจะตั้งค่าการตรวจสอบสิทธิ์ OAuth และติดตั้งใช้งานส่วนต่างๆ ที่ขาดหายไปโดยใช้ Permission API

3. ตั้งค่าการตรวจสอบสิทธิ์

Home API ใช้ OAuth 2.0 เพื่อมอบสิทธิ์เข้าถึงอุปกรณ์ในโครงสร้าง OAuth ช่วยให้ผู้ใช้สามารถให้สิทธิ์แก่แอปหรือบริการได้โดยไม่ต้องเปิดเผยข้อมูลเข้าสู่ระบบ

ทําตามวิธีการในตั้งค่าความยินยอม OAuth เพื่อกําหนดค่าหน้าจอขอความยินยอม โปรดสร้างบัญชีทดสอบอย่างน้อย 1 บัญชี

จากนั้นทําตามวิธีการในตั้งค่าข้อมูลเข้าสู่ระบบ OAuth เพื่อสร้างข้อมูลเข้าสู่ระบบสําหรับแอป

4. สิทธิ์ในการเริ่มต้นและการจัดการ

ในส่วนนี้ คุณจะได้เรียนรู้วิธีเริ่มต้นใช้งาน SDK และจัดการสิทธิ์ของผู้ใช้โดยทำตามขั้นตอนที่ขาดหายไปโดยใช้ Permissions API

กําหนดประเภทและลักษณะที่รองรับ

เมื่อพัฒนาแอป คุณต้องระบุประเภทและลักษณะของอุปกรณ์ที่แอปจะรองรับอย่างชัดเจน ในแอปตัวอย่าง เราทําเช่นนี้โดยกําหนดรายการแบบคงที่ในออบเจ็กต์ที่ใช้ร่วมกันใน HomeApp.kt ซึ่งจะอ้างอิงได้ทั่วทั้งแอปตามต้องการ

companion object {

  // List of supported device types by this app:
  val supportedTypes: List<DeviceTypeFactory<out DeviceType>> = listOf(
    OnOffLightDevice,
    DimmableLightDevice,

  // ...
  )
  // List of supported device traits by this app:
  val supportedTraits: List<TraitFactory<out Trait>> = listOf(
  OnOff,
  LevelControl,
  // ...
  )
}

ดูประเภทอุปกรณ์ที่รองรับและดัชนีลักษณะใน Android เพื่อดูประเภทอุปกรณ์และลักษณะที่รองรับทั้งหมด

นำการกำกับเนื้อหาออกในขั้นตอนที่ 4.1.1 และ 4.1.2 ในไฟล์ต้นฉบับ HomeApp.kt เพื่อเปิดใช้ซอร์สโค้ดที่ขอสิทธิ์

companion object {
// List of supported device types by this app:
val supportedTypes: List<DeviceTypeFactory<out DeviceType>> = listOf(
// TODO: 4.1.1 - Non-registered device types will be unsupported
//             ContactSensorDevice,
//             ColorTemperatureLightDevice,
//             DimmableLightDevice,
//             ExtendedColorLightDevice,
//             GenericSwitchDevice,
//             GoogleDisplayDevice,
//             GoogleTVDevice,
//             OccupancySensorDevice,
//             OnOffLightDevice,
//             OnOffLightSwitchDevice,
//             OnOffPluginUnitDevice,
//             OnOffSensorDevice,
//             RootNodeDevice,
//             SpeakerDevice,
//             ThermostatDevice,
)
// List of supported device traits by this app:
val supportedTraits: List<TraitFactory<out Trait>> = listOf(
// TODO: 4.1.2 - Non-registered traits will be unsupported
//             AreaAttendanceState,
//             AreaPresenceState,
//             Assistant,
//             AssistantBroadcast,
//             AssistantFulfillment,
//             BasicInformation,
//             BooleanState,
//             OccupancySensing,
//             OnOff,
//             Notification,
//             LevelControl,
//             TemperatureControl,
//             TemperatureMeasurement,
//             Thermostat,
//             Time,
//             Volume,
        )
}

เริ่มต้นออบเจ็กต์ HomeClient

แอปทั้งหมดที่ใช้ Home API จะเริ่มต้นวัตถุ HomeClient ซึ่งเป็นอินเทอร์เฟซหลักในการโต้ตอบกับ API เราเตรียมออบเจ็กต์นี้ในตัวแปรเริ่มต้นของคลาส HomeApp (HomeApp.kt)

// Registry to record device types and traits used in this app:
val registry = FactoryRegistry(
  types = supportedTypes,
  traits = supportedTraits
)
// Configuration options for the HomeClient:
val config = HomeConfig(
  coroutineContext = Dispatchers.IO,
  factoryRegistry = registry
)
// Initialize the HomeClient, which is the primary object to use all Home APIs:
homeClient = Home.getClient(context = context, homeConfig = config)

ก่อนอื่น เราจะสร้าง FactoryRegistry โดยใช้ประเภทและลักษณะที่รองรับซึ่งเราได้กำหนดไว้ก่อนหน้านี้ จากนั้นใช้รีจิสทรีนี้เพื่อเริ่มต้น HomeConfig ซึ่งมีการกำหนดค่าที่จําเป็นสําหรับเรียกใช้ API ต่อไปเราจะใช้การเรียก Home.getClient(...) เพื่อรับอินสแตนซ์ HomeClient

การโต้ตอบกับ Home API ทั้งหมดจะผ่านออบเจ็กต์ HomeClient นี้

ใช้ Permissions API

การตรวจสอบสิทธิ์ผู้ใช้สําหรับ Home API ทำได้ผ่าน Permissions API ไฟล์ต้นฉบับ PermissionsManager.kt ของแอปตัวอย่างมีโค้ดสําหรับการตรวจสอบสิทธิ์ผู้ใช้ ยกเลิกการคอมเมนต์เนื้อหาของฟังก์ชัน checkPermissions(...) และ requestPermissions(...) เพื่อเปิดใช้สิทธิ์สําหรับแอปตัวอย่าง

การลงทะเบียน

homeClient.registerActivityResultCallerForPermissions(activity)

การเปิดตัว

try {
    val result: PermissionsResult
    result = homeClient.requestPermissions(forceLaunch = true)
    when (result.status) {
        PermissionsResultStatus.SUCCESS -> // Success Case
        PermissionsResultStatus.CANCELLED -> // User Cancelled
        PermissionsResultStatus.ERROR -> // Some Error
else -> // Unsupported Case
    }
}
catch (e: HomeException) { ... }

การตรวจสอบ

try {
    val state: PermissionsState
    state = homeClient.hasPermissions().first { state ->
        state != PermissionsState.PERMISSIONS_STATE_UNINITIALIZED
    }
    when (state) {
        PermissionsState.GRANTED -> // Signed In
        PermissionsState.NOT_GRANTED -> // Not Signed In
        PermissionsState.PERMISSIONS_STATE_UNAVAILABLE -> // ...
        PermissionsState.PERMISSIONS_STATE_UNINITIALIZED -> // ...
else -> // Unsupported case
    }
}
catch (e: HomeException) { ... }

การสมัครใช้บริการ

       homeClient.hasPermissions().collect( { state ->
// Track the changes on state
        } )

ยกเลิกการคอมเมนต์ขั้นตอนที่ 4.3.1 ใน PermissionsManager.kt เพื่อเปิดใช้โค้ดที่ขอสิทธิ์

fun requestPermissions() {
    scope.launch {
    try {
// TODO: 4.3.1 - Request the permissions from the Permissions API
//                 // Request permissions from the Permissions API and record the result:
//                 val result: PermissionsResult = client.requestPermissions(forceLaunch = true)
//                 // Adjust the sign-in status according to permission result:
//                 if (result.status == PermissionsResultStatus.SUCCESS)
//                     isSignedIn.emit(true)
//                 // Report the permission result:
//                 reportPermissionResult(result)
    }
    catch (e: HomeException) { MainActivity.showError(this, e.message.toString()) }
    }
}

จากนั้นเปิดแอปในโทรศัพท์โดยทำตามขั้นตอนและอนุญาตสิทธิ์ คุณควรเห็นขั้นตอนต่อไปนี้

c263dcee4e945bf1.png f518cfd1fdb8a9d8.png 59937372f28c472f.png 383073ae57d2ced4.png 89f774a2ba6898ae.png

ข้อความ "กำลังโหลด" จะไม่หายไปเนื่องจากเรายังไม่ได้ติดตั้งใช้งานโค้ดที่อ่านโครงสร้างและอุปกรณ์ เราจะพูดถึงเรื่องนี้ในส่วนถัดไป

5. ทําความเข้าใจรูปแบบข้อมูล

ใน Home API โมเดลข้อมูลประกอบด้วยรายการต่อไปนี้

  • Structure แสดงถึงบ้านที่มีห้องและอุปกรณ์
  • Room เป็นส่วนหนึ่งของโครงสร้างและมีอุปกรณ์
  • อุปกรณ์ (หมายถึง HomeDevice) สามารถกำหนดให้กับโครงสร้าง (หรือบ้าน) หรือห้องในโครงสร้างได้
  • อุปกรณ์ประกอบด้วยอินสแตนซ์ DeviceType อย่างน้อย 1 รายการ
  • DeviceType ประกอบด้วยอินสแตนซ์ Trait
  • Trait ประกอบด้วยอินสแตนซ์ Attribute (สําหรับการอ่าน/การเขียน) อินสแตนซ์ Command (สําหรับการควบคุมแอตทริบิวต์) และอินสแตนซ์ Event (สําหรับการอ่านหรือการติดตามบันทึกการเปลี่ยนแปลงที่ผ่านมา)
  • อินสแตนซ์ Automation เป็นส่วนหนึ่งของโครงสร้าง และใช้ข้อมูลเมตาและอุปกรณ์ในบ้านเพื่อทำงานต่างๆ ในบ้านแบบอัตโนมัติ

76d35b44d5a8035e.png

ในส่วนนี้ คุณจะได้เรียนรู้วิธีพัฒนาซอร์สโค้ดเพื่อแสดงวิธีใช้ Structure API เพื่อแยกวิเคราะห์และแสดงผลโครงสร้างบ้าน ห้อง อุปกรณ์ และอื่นๆ

อ่านโครงสร้าง

การออกแบบ Home API อิงตาม Kotlin Flow เพื่อสตรีมออบเจ็กต์โมเดลข้อมูล (เช่น Structure, HomeDevice และอื่นๆ) นักพัฒนาแอปสมัครใช้บริการ Flow เพื่อรับออบเจ็กต์ทั้งหมดที่อยู่ในออบเจ็กต์ (เช่น Structure, Room และอื่นๆ)

หากต้องการเรียกข้อมูลโครงสร้างทั้งหมด ให้เรียกใช้ฟังก์ชัน structures() ซึ่งจะแสดงผลลําดับโครงสร้าง จากนั้นเรียกใช้ฟังก์ชันรายการในโฟลว์เพื่อรับโครงสร้างทั้งหมดที่ผู้ใช้เป็นเจ้าของ

// Get the a snapshot of all structures from the current homeClient
val allStructures : Set<Structure> =
    homeClient.structures()   // HomeObjectsFlow<Structure>
    .list()                   // Set<Structure>

คู่มือสถาปัตยกรรมแอปขอแนะนําอย่างยิ่งให้ใช้แนวทางการเขียนโปรแกรมแบบรีแอกทีฟสมัยใหม่เพื่อปรับปรุงการไหลของข้อมูลแอปและการจัดการสถานะ

แอปตัวอย่างเป็นไปตามรูปแบบการเขียนโค้ดแบบรีแอ็กทีฟดังนี้

  • โมเดลมุมมอง (เช่น StructureViewModel และ DeviceViewModel ในฐานะผู้ถือสถานะ) จะสมัครรับข้อมูลเวิร์กโฟลว์จาก Home APIs SDK เพื่อรับการเปลี่ยนแปลงค่าและรักษาสถานะล่าสุด
  • มุมมอง (เช่น StructureView และ DeviceView) จะสมัครรับข้อมูลโมเดลมุมมองเพื่อรับสถานะและแสดงผล UI ให้สอดคล้องกับการเปลี่ยนแปลงเหล่านั้น
  • เมื่อผู้ใช้คลิกปุ่มในมุมมอง (เช่น ปุ่ม "เปิด" ของอุปกรณ์หลอดไฟ) เหตุการณ์จะทริกเกอร์ฟังก์ชันของโมเดลมุมมอง ซึ่งจะเรียกใช้ฟังก์ชัน Home API ที่ตอบสนอง (เช่น คำสั่ง On ของลักษณะ OnOff)

ในขั้นตอนที่ 5.1.1 ใน HomeAppViewModel.kt เราจะสมัครรับเหตุการณ์การเปลี่ยนแปลงโครงสร้างโดยการเรียกใช้ฟังก์ชัน collect() ยกเลิกการคอมเมนต์ส่วนที่มีการเรียกใช้ structureSet ที่แสดงผลโดย Structures API และส่งใน StructureViewModel's StateFlow ซึ่งจะช่วยให้แอปตรวจสอบการเปลี่ยนแปลงสถานะของโครงสร้างได้

   private suspend fun subscribeToStructures() {
// TODO: 5.1.1 - Subscribe the structure data changes
// // Subscribe to structures returned by the Structures API:
// homeApp.homeClient.structures().collect { structureSet ->
//     val structureVMList: MutableList<StructureViewModel> = mutableListOf()
//     // Store structures in container ViewModels:
//     for (structure in structureSet) {
//         structureVMList.add(StructureViewModel(structure))
//     }
//     // Store the ViewModels:
//     structureVMs.emit(structureVMList)
//
//     // If a structure isn't selected yet, select the first structure from the list:
//     if (selectedStructureVM.value == null && structureVMList.isNotEmpty())
//         selectedStructureVM.emit(structureVMList.first())
//
// }
}

ใน DevicesView.kt แอปจะสมัครรับ StructureViewModel'sStateFlow, ซึ่งจะทริกเกอร์การจัดองค์ประกอบ UI ใหม่เมื่อ Structured Data มีการเปลี่ยนแปลง นำความคิดเห็นออกจากซอร์สโค้ดในขั้นตอนที่ 5.1.2 เพื่อแสดงผลรายการโครงสร้างเป็นเมนูแบบเลื่อนลง

   val structureVMs: List<StructureViewModel> = homeAppVM.structureVMs.collectAsState().value
...
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
// TODO: 5.1.2 - Show list of structures in DropdownMenu
//  for (structure in structureVMs) {
//      DropdownMenuItem(
//          text = { Text(structure.name) },
//          onClick = {
//              scope.launch { homeAppVM.selectedStructureVM.emit(structure) }
//              expanded = false
//          }
//      )
//  }
}
...

เรียกใช้แอปอีกครั้ง คุณควรเห็นเมนูเมื่อแตะลูกศร

f1fc2be1cb6436b6.png

แยกวิเคราะห์โครงสร้าง

ขั้นตอนถัดไปคือการไปยังส่วนต่างๆ ของวัตถุบ้านในโครงสร้าง เรียกข้อมูลห้องจากโครงสร้าง

val rooms: Set<Room>
rooms = structure.rooms().list()

จากนั้นคุณจะสามารถไปยังห้องต่างๆ เพื่อเรียกข้อมูลอุปกรณ์ได้ ดังนี้

val devices: Set<HomeDevice>
devices = room.devices().list()

สำคัญ: ในโมเดลข้อมูล Home APIs โครงสร้างอาจมีอุปกรณ์ที่ไม่ได้กำหนดให้กับห้อง ดังนั้นอย่าลืมบันทึกอุปกรณ์ที่ไม่มีห้องในแอปด้วย

val devicesWithoutRooms: MutableSet<HomeDevice> = mutableSetOf()

for (device in structure.devices().list())
if (!device.isInRoom)
  devicesWithoutRooms.add(device)

อีกครั้ง ในโค้ดตัวอย่างที่มีอยู่ เราจะสมัครรับข้อมูลเพื่อรับรายการห้องและอุปกรณ์ล่าสุด ตรวจสอบโค้ดในขั้นตอนที่ 5.2.1 และ 5.2.2 ในไฟล์ต้นฉบับ StructureViewModel.kt แล้วยกเลิกการใส่ความคิดเห็นเพื่อเปิดใช้การสมัครใช้บริการข้อมูลห้อง

val roomVMs : MutableStateFlow<List<RoomViewModel>>
val deviceVMs : MutableStateFlow<List<DeviceViewModel>>
val deviceVMsWithoutRooms : MutableStateFlow<List<DeviceViewModel>>
private suspend fun subscribeToRooms() {
// TODO: 5.2.1 - Subscribe the room data changes
//   // Subscribe to changes on rooms:
//   structure.rooms().collect { roomSet ->
//       val roomVMs = mutableListOf<RoomViewModel>()
//       // Store rooms in container ViewModels:
//       for (room in roomSet) {
//           roomVMs.add(RoomViewModel(room))
//       }
//       // Store the ViewModels:
//       this.roomVMs.emit(roomVMs)
//   }
}
private suspend fun subscribeToDevices() {
// TODO: 5.2.2 - Subscribe the device data changes in a structure
//   // Subscribe to changes on devices:
//   structure.devices().collect { deviceSet ->
//       val deviceVMs = mutableListOf<DeviceViewModel>()
//       val deviceWithoutRoomVMs = mutableListOf<DeviceViewModel>()
//       // Store devices in container ViewModels:
//       for (device in deviceSet) {
//           val deviceVM = DeviceViewModel(device)
//           deviceVMs.add(deviceVM)
//           // For any device that's not in a room, additionally keep track of a separate list:
//           if (!device.isInRoom)
//               deviceWithoutRoomVMs.add(deviceVM)
//       }
//       // Store the ViewModels:
//       this.deviceVMs.emit(deviceVMs)
//       deviceVMsWithoutRooms.emit(deviceWithoutRoomVMs)
//   }
    }

นำการกำกับดูแลขั้นตอนที่ 5.2.3 และ 5.2.4 ในไฟล์ต้นฉบับ DevicesView.kt ออกเพื่อแสดงผลรายการห้องเป็นเมนู

val selectedRoomVMs: List<RoomViewModel> =
selectedStructureVM.roomVMs.collectAsState().value
...
for (roomVM in selectedRoomVMs) {
// TODO: 5.2.3 - Render the list of rooms
//   RoomListItem(roomVM)
// TODO: 5.2.4 - Render the list of devices in a room
//   val deviceVMsInRoom: List<DeviceViewModel> = roomVM.deviceVMs.collectAsState().value
//
//   for (deviceVM in deviceVMsInRoom) {
//       DeviceListItem(deviceVM, homeAppVM)
//   }
}

ตอนนี้คุณมีอุปกรณ์แล้ว เรามาเรียนรู้วิธีใช้งานกัน

e715ddda50e04839.png

6. ทำงานกับอุปกรณ์

Home API ใช้ออบเจ็กต์ HomeDevice เพื่อบันทึกอุปกรณ์และความสามารถของอุปกรณ์ นักพัฒนาแอปสามารถสมัครรับข้อมูลแอตทริบิวต์ของอุปกรณ์และใช้แอตทริบิวต์ดังกล่าวเพื่อแสดงอุปกรณ์สมาร์ทโฮมในแอป

อ่านสถานะอุปกรณ์

ออบเจ็กต์ HomeDevice จะแสดงชุดค่าคงที่ เช่น ชื่ออุปกรณ์หรือสถานะการเชื่อมต่อ ในฐานะนักพัฒนาแอป คุณจะดึงข้อมูลต่อไปนี้ได้ทันทีหลังจากได้รับอุปกรณ์จาก API

val id: String = device.id.id
val name: String = device.name
val connectivity: ConnectivityState =
    device.sourceConnectivity.connectivityState

หากต้องการดูความสามารถของอุปกรณ์ คุณต้องดึงข้อมูลประเภทและลักษณะจาก HomeDevice โดยคุณสามารถสมัครรับข้อมูลของประเภทอุปกรณ์ตามขั้นตอนต่อไปนี้ และดึงข้อมูลลักษณะจากประเภทอุปกรณ์

device.types().collect { typeSet ->
var primaryType : DeviceType = UnknownDeviceType()
for (typeInSet in typeSet)
if (typeInSet.metadata.isPrimaryType)
                    primaryType = typeInSet
            val traits: List<Trait> = mutableListOf()
for (trait in primaryType.traits())
if (trait.factory in myTraits)
                    traits.add(trait)
for (trait in traits)
                parseTrait(trait, primaryType)
        }

อุปกรณ์แต่ละเครื่องจะมีชุด DeviceType (ความสามารถที่รวมไว้) ที่รองรับ ซึ่งคุณเรียกดูได้โดยใช้ device.types() อุปกรณ์ประเภทเหล่านี้มีลักษณะที่ดึงข้อมูลได้โดยใช้ type.traits() อุปกรณ์ทุกเครื่องจะกำหนดประเภทใดประเภทหนึ่งเป็นประเภทหลัก (ซึ่งตรวจสอบได้โดยใช้ type.metadata.isPrimaryType) ที่คุณควรแสดงในแอป เราขอแนะนำให้เรียกดูประเภทที่แสดงผลทั้งหมดและผสานรวมลักษณะทั้งหมดที่คุณใช้ได้เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่สมบูรณ์

เมื่อดึงข้อมูลลักษณะแล้ว คุณสามารถแยกวิเคราะห์โดยใช้ฟังก์ชันต่อไปนี้เพื่อตีความค่า

fun <T : Trait?> parseTrait(trait : T, type: DeviceType) {
    val status : String = when (trait) {
        is OnOff -> { if (trait.onOff) "On" else "Off" }
        is LevelControl -> { trait.currentLevel.toString() }
        is BooleanState -> {
            when (type.factory) {
                ContactSensorDevice -> {
if (trait.stateValue) "Closed"
else "Open"
                }
else -> ...
            }
        }
else -> ...
    }
}

โปรดทราบว่าสิ่งที่ลักษณะแสดงอาจแตกต่างกันไปโดยขึ้นอยู่กับประเภทอุปกรณ์ที่แสดงลักษณะนั้น (ดู BooleanState ในตัวอย่างก่อนหน้านี้) คุณจึงต้องคำนึงถึงบริบทของอุปกรณ์แต่ละประเภทเพื่อให้เข้าใจสิ่งที่ลักษณะของอุปกรณ์แสดง

ยกเลิกการคอมเมนต์ขั้นตอนที่ 6.1.1 และ 6.1.2 ในไฟล์ต้นฉบับ DeviceViewModel.kt เพื่อเรียกข้อมูลสถานะ

private suspend fun subscribeToType() {
// Subscribe to changes on device type, and the traits/attributes within:
device.types().collect { typeSet ->
// Container for the primary type for this device:
var primaryType : DeviceType = UnknownDeviceType()
...
// TODO: 6.1.1 - Determine the primary type for this device
//       // Among all the types returned for this device, find the primary one:
//       for (typeInSet in typeSet)
//           if (typeInSet.metadata.isPrimaryType)
//               primaryType = typeInSet
//
//       // Optional: For devices with a single type that did not define a primary:
//       if (primaryType is UnknownDeviceType && typeSet.size == 1)
//           primaryType = typeSet.first()
// Container for list of supported traits present on the primary device type:
val supportedTraits: List<Trait> = getSupportedTraits(primaryType.traits())
...
}
fun getSupportedTraits(traits: Set<Trait>) : List<Trait> {
           val supportedTraits: MutableList<Trait> = mutableListOf()
// TODO: 6.1.2 - Get only the supported traits for this device
//   for (trait in traits)
//       if (trait.factory in HomeApp.supportedTraits)
//           supportedTraits.add(trait)
return supportedTraits
}

นำการคอมเมนต์ออกจากขั้นตอนที่ 6.1.3 ใน DeviceView.kt เพื่อแสดงผลแอตทริบิวต์ OnOff รวมถึงชื่อและสถานะเป็น String

Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
when (trait) {
is OnOff -> {
// TODO: 6.1.3 - Render controls based on the trait type
// Column (Modifier.fillMaxWidth()) {
//     Text(trait.factory.toString(), fontSize = 20.sp)
//     Text(DeviceViewModel.getTraitStatus(trait, type), fontSize = 16.sp)
// }
...
}
is LevelControl -> {
      ...
  }
   is BooleanState -> {
      ...
  }
   is OccupancySensing -> {
      ...
  }
  ...
}

หากคุณเรียกใช้แอปตอนนี้ด้วยอุปกรณ์ประเภทที่รองรับ (เช่น อุปกรณ์ Light) แอปควรแสดงสถานะล่าสุดของอุปกรณ์ทั้งหมด

1bd8b3b2796c4c7a.png

ใช้คำสั่งในอุปกรณ์

หากต้องการออกคำสั่งไปยังอุปกรณ์ Home API มีฟังก์ชันอำนวยความสะดวกในออบเจ็กต์ลักษณะ เช่น trait.on() หรือ trait.moveToLevel(...) ดังนี้

fun <T : Trait?> issueCommand(trait : T) {
     when (trait) {
         is OnOff -> {
// trait.on()
// trait.off()
   }
   is LevelControl -> {
// trait.moveToLevel(...)
// trait.moveToLevelWithOnOff(...)
        }
    }
}

เคล็ดลับ: เมื่อคุณระบุประเภทของแทร็กแล้ว ให้ใช้ฟีเจอร์เติมข้อความอัตโนมัติของ Android Studio เพื่อดูประเภทการดำเนินการที่ใช้โต้ตอบกับแทร็กได้

ยกเลิกการคอมเมนต์ขั้นตอนที่ 6.2.1 ใน DeviceView.kt เพื่อเพิ่มการควบคุมฟังก์ชันในแอป

Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
when (trait) {
is OnOff -> {
                ....
// TODO: 6.2.1 - Render controls based on the trait type
//   Switch (checked = (trait.onOff == true), modifier = Modifier.align(Alignment.CenterEnd),
//       onCheckedChange = { state ->
//           scope.launch { if (state) trait.on() else trait.off() }
//       },
//       enabled = isConnected
//   )
}

หากเรียกใช้แอปตอนนี้ คุณควรควบคุมอุปกรณ์จริงในชีวิตจริงได้

หากแตะตัวควบคุมเปิด/ปิดบนหลอดไฟ อุปกรณ์ก็ควรจะเปิดขึ้น

c8ed3ecf5031546e.png

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีควบคุมอุปกรณ์ได้ที่ควบคุมอุปกรณ์ใน Android

7. อุปกรณ์ค่าคอมมิชชัน

Commissioning API ช่วยให้นักพัฒนาแอปเพิ่มอุปกรณ์ลงในระบบนิเวศของ Google Home และทำให้อุปกรณ์พร้อมใช้งานโดยใช้ Home API รองรับเฉพาะอุปกรณ์ Matter เท่านั้น ในส่วนนี้ เราจะดูวิธีเปิดใช้การจัดเตรียมอุปกรณ์ในแอป

ก่อนเริ่มต้นส่วนนี้ โปรดตรวจสอบว่าคุณมีคุณสมบัติตรงตามข้อกําหนดเบื้องต้นต่อไปนี้

หากมีอุปกรณ์ Matter จริงที่มีคิวอาร์โค้ดสำหรับการเตรียมใช้งาน ให้ข้ามไปที่เปิดใช้ API สำหรับการเตรียมใช้งาน หรือจะข้ามไปที่ส่วนถัดไปก็ได้ ซึ่งเราจะพูดถึงวิธีใช้แอปอุปกรณ์เสมือน Matter (MVD) เพื่อสร้างอุปกรณ์เสมือนที่จะได้รับค่าคอมมิชชัน

ไม่บังคับ: เตรียมอุปกรณ์ Matter ที่เปิดใช้การเรียกเก็บเงิน

วิธีที่ง่ายที่สุดในการเตรียมอุปกรณ์ Matter ที่เรียกเก็บค่าคอมมิชชันได้คือการใช้อุปกรณ์จำลองที่แอป Matter Virtual Device (MVD) มีให้

หลังจากติดตั้ง MVD และตั้งค่าไฟร์วอลล์แล้ว ให้เรียกใช้ MVD โดยทำดังนี้

b20283893073ac1b.png

สร้างอุปกรณ์เปิด/ปิด โปรดทราบว่ายังไม่ได้สั่งทํา คุณจะสั่งทําได้ในภายหลังในโค้ดแล็บนี้

5f4855b808312898.png

เปิดใช้ Commissioning API

Commissioning API ทำงานนอกกิจกรรมของแอป ดังนั้นการจัดการการต่ออายุจึงต้องดำเนินการแตกต่างจาก Home API อื่นๆ คุณต้องมีตัวแปร 2 รายการเพื่อให้แอปพร้อมสำหรับการเตรียมใช้งาน

ตัวแปรหนึ่งคือ ActivityResultLauncher ซึ่งใช้เพื่อส่งความตั้งใจในการว่าจ้างและจัดการการเรียกกลับผลลัพธ์ ตัวแปรอีกตัวหนึ่งคือ CommissioningResult ซึ่งเป็นออบเจ็กต์ที่ใช้จัดเก็บผลการว่าจ้าง ดูตัวอย่างต่อไปนี้สำหรับวิธีตั้งค่าการจัดเตรียม

var launcher: ActivityResultLauncher<IntentSenderRequest>
lateinit var commissioningResult: CommissioningResult?
launcher = activity.registerForActivityResult(StartIntentSenderForResult()) { result ->
try {
  commissioningResult = CommissioningResult.fromIntentSenderResult(
      result.resultCode, result.data)
  } catch (exception: ApiException) {
// Catch any issues
 }
}

เมื่อตั้งค่าขั้นตอนการมอบหมายแล้ว คุณจะต้องสร้างความตั้งใจในการมอบหมาย และเปิดใช้งานโดยใช้ Launcher ที่เราสร้างในตัวอย่างก่อนหน้านี้ เราขอแนะนำให้วาง Intent และ Launcher ไว้ในฟังก์ชันเฉพาะ เช่น ต่อไปนี้ ฟังก์ชันเฉพาะสามารถเชื่อมโยงกับองค์ประกอบ UI (เช่น ปุ่ม +เพิ่มอุปกรณ์) และเรียกใช้ตามคำขอของผู้ใช้

fun requestCommissioning() {
// Retrieve the onboarding payload used when commissioning devices:
val payload = activity.intent?.getStringExtra(Matter.EXTRA_ONBOARDING_PAYLOAD)
  scope.launch {
    // Create a commissioning request to store the device in Google's Fabric:
    val request = CommissioningRequest.builder()
      .setStoreToGoogleFabric(true)
      .setOnboardingPayload(payload)
      .build()
    // Initialize client and sender for commissioning intent:
    val client: CommissioningClient = Matter.getCommissioningClient(context)
    val sender: IntentSender = client.commissionDevice(request).await()
    // Launch the commissioning intent on the launcher:
    launcher.launch(IntentSenderRequest.Builder(sender).build())
  }
}

ยกเลิกการคอมเมนต์ขั้นตอนที่ 7.1.1 ใน CommissioningManager.kt เพื่อเปิดใช้ความสามารถในการจัดเตรียมอุปกรณ์และทำให้ปุ่ม +เพิ่มอุปกรณ์ทำงานในแอปตัวอย่าง

// Called by +Add Device button in DeviceView.kt
fun requestCommissioning() {
// Retrieve the onboarding payload used when commissioning devices:
val payload = activity.intent?.getStringExtra(Matter.EXTRA_ONBOARDING_PAYLOAD)
// TODO: 7.1.1 - Launch the commissioning intent
// scope.launch {
//     // Create a commissioning request to store the device in Google's Fabric:
//     val request = CommissioningRequest.builder()
//         .setStoreToGoogleFabric(true)
//         .setOnboardingPayload(payload)
//         .build()
//     // Initialize client and sender for commissioning intent:
//     val client: CommissioningClient = Matter.getCommissioningClient(context)
//     val sender: IntentSender = client.commissionDevice(request).await()
//     // Launch the commissioning intent on the launcher:
//     launcher.launch(IntentSenderRequest.Builder(sender).build())
// }
}

การดำเนินการฟังก์ชันนี้จะเริ่มต้นขั้นตอนการเตรียมใช้งาน ซึ่งควรแสดงหน้าจอที่คล้ายกับภาพหน้าจอต่อไปนี้

baae45588f460664.png

ทําความเข้าใจขั้นตอนการเปิดใช้งาน

โฟลว์การจัดเตรียมประกอบด้วยชุดหน้าจอที่แนะนำผู้ใช้ในการเพิ่มอุปกรณ์ลงในบัญชี Google

2fb0404820d4a035.png 3cbfa8ff9cfd5ee4.png a177c197ee7a67bf.png 3fdef24672c77c0.png dec8e599f9aa119.png

ผู้ใช้จะเห็นเครื่องสแกนคิวอาร์โค้ดที่สามารถใช้สแกนคิวอาร์โค้ดจากอุปกรณ์ Matter จากนั้นขั้นตอนจะแสดงข้อตกลงของผู้ใช้ การค้นหาและการจัดเตรียมอุปกรณ์ และการตั้งชื่ออุปกรณ์ เมื่อดำเนินการเสร็จแล้ว ขั้นตอนจะเปลี่ยนโฟกัสกลับไปที่แอป และส่งผลลัพธ์การจัดเตรียมในฟังก์ชันการเรียกกลับที่เราร่างไว้ในส่วนก่อนหน้า

ประโยชน์อย่างหนึ่งของ Commissioning API คือ SDK จะจัดการขั้นตอน UX เพื่อให้นักพัฒนาแอปเริ่มต้นใช้งานได้อย่างรวดเร็ว นอกจากนี้ ยังช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่สอดคล้องกันเมื่อเพิ่มอุปกรณ์ในแอปต่างๆ

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Commissioning API ได้ที่ Commissioning API ใน Android

8. ยินดีด้วย

ยินดีด้วย คุณสร้างแอป Android โดยใช้ Google Home API เรียบร้อยแล้ว ตลอดทั้งโค้ดแล็บนี้ คุณได้สำรวจสิทธิ์ อุปกรณ์ โครงสร้าง และ API การจัดเตรียม ในโค้ดแล็บถัดไปอย่างสร้างการทำงานอัตโนมัติขั้นสูงโดยใช้ Home API ใน Android Codelab เราจะสำรวจ Automation และ Discovery API รวมถึงสร้างแอปให้เสร็จสมบูรณ์

เราหวังว่าคุณจะสนุกกับการสร้างแอปที่ควบคุมอุปกรณ์ภายในระบบนิเวศของ Google Home อย่างสร้างสรรค์

ขั้นตอนถัดไป