สร้างการทำงานอัตโนมัติโดยใช้ Home API ใน Android

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

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

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

วิดีโอนี้จะอธิบายขั้นตอนสั้นๆ เกี่ยวกับการทำงานอัตโนมัติที่คุณจะสร้าง ดังนั้นโปรดดูวิดีโอไปพร้อมๆ กับการทำ Codelab

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

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

  • วิธีสร้างการทำงานอัตโนมัติสำหรับอุปกรณ์สมาร์ทโฮมโดยใช้ Home API
  • วิธีใช้ Discovery API เพื่อสำรวจความสามารถของอุปกรณ์ที่รองรับ
  • วิธีใช้แนวทางปฏิบัติแนะนำเมื่อสร้างแอปด้วย Home API

2. การตั้งค่าโปรเจ็กต์

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

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

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

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

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

ดาวน์โหลดแอปตัวอย่าง

ซอร์สโค้ดสำหรับแอปตัวอย่างพร้อมให้บริการบน GitHub ในที่เก็บ google-home/google-home-api-sample-app-android

Codelab นี้ใช้ตัวอย่างจากสาขา codelab-branch-2 ของแอปตัวอย่าง

ไปที่ตำแหน่งที่ต้องการบันทึกโปรเจ็กต์ แล้วโคลนกิ่ง codelab-branch-2

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

โปรดทราบว่านี่เป็นสาขาที่แตกต่างจากที่ใช้ในสร้างแอปบนอุปกรณ์เคลื่อนที่โดยใช้ Home API ใน Android โค้ดเบสสาขานี้สร้างขึ้นจากจุดที่ Codelab แรกสิ้นสุดลง คราวนี้ตัวอย่างจะแนะนำวิธีสร้างการทำงานอัตโนมัติ หากคุณทำ Codelab ก่อนหน้าเสร็จแล้วและสามารถทำให้ฟังก์ชันการทำงานทั้งหมดทำงานได้ คุณอาจเลือกใช้โปรเจ็กต์ Android Studio เดียวกันเพื่อทำ Codelab นี้ให้เสร็จแทนการใช้ codelab-branch-2

เมื่อคอมไพล์ซอร์สโค้ดและพร้อมที่จะเรียกใช้บนอุปกรณ์เคลื่อนที่แล้ว ให้ไปที่ส่วนถัดไป

3. ดูข้อมูลเกี่ยวกับการทำงานอัตโนมัติ

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

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

  1. เงื่อนไขเริ่มต้น - กำหนดเงื่อนไขเริ่มต้นที่เปิดใช้งานการทำงานอัตโนมัติ เช่น การเปลี่ยนแปลงค่าลักษณะ การทำงานอัตโนมัติต้องมีเงื่อนไขเริ่มต้น
  2. เงื่อนไข - ข้อจำกัดเพิ่มเติมใดๆ ที่จะประเมินหลังจากทริกเกอร์การทำงานอัตโนมัติ นิพจน์ในเงื่อนไขต้องประเมินค่าเป็นจริงเพื่อให้การดำเนินการของการทำงานอัตโนมัติทำงาน
  3. การดำเนินการ - คำสั่งหรือการอัปเดตสถานะที่จะดำเนินการเมื่อเป็นไปตามเงื่อนไขทั้งหมด

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

  • Starter - มีการเปิด/ปิดสวิตช์ในห้อง
  • เงื่อนไข - สถานะเปิด/ปิดทีวีได้รับการประเมินเป็น "เปิด"
  • การดำเนินการ - ไฟในห้องเดียวกับสวิตช์จะหรี่ลง

โดย Automation Engine จะประเมินโหนดเหล่านี้แบบอนุกรมหรือแบบขนาน

image5.png

โฟลว์แบบลำดับประกอบด้วยโหนดที่ทำงานตามลำดับ โดยปกติแล้ว องค์ประกอบเหล่านี้จะเป็นเงื่อนไขเริ่มต้น เงื่อนไข และการดำเนินการ

image6.png

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

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

image13.png

นักพัฒนาแอปจะระบุโหนดเหล่านี้ให้กับ Automation Engine โดยใช้ภาษาเฉพาะโดเมน (DSL) ที่สร้างขึ้นสําหรับการทำงานอัตโนมัติของ Google Home โดยเฉพาะ

สำรวจ DSL ของการทำงานอัตโนมัติ

ภาษาเฉพาะโดเมน (DSL) คือภาษาที่ใช้เพื่อบันทึกพฤติกรรมของระบบในโค้ด คอมไพเลอร์จะสร้างคลาสข้อมูลที่ได้รับการซีเรียลไลซ์เป็น JSON ของ Protocol Buffer และใช้เพื่อทำการเรียกไปยังบริการอัตโนมัติของ Google

DSL จะมองหาสคีมาต่อไปนี้

automation {
name = "AutomationName"
  description = "An example automation description."
  isActive = true
    sequential {
    val onOffTrait = starter<_>(device1, OnOffLightDevice, OnOff)
    condition() { expression = onOffTrait.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

การทำงานอัตโนมัติในตัวอย่างก่อนหน้าจะซิงค์หลอดไฟ 2 หลอด เมื่อสถานะ OnOff ของ device1 เปลี่ยนเป็น On (onOffTrait.onOff equals true) ระบบจะเปลี่ยนสถานะ OnOff ของ device2 เป็น On (command(OnOff.on())

เมื่อใช้การทำงานอัตโนมัติ โปรดทราบว่ามีขีดจำกัดของทรัพยากร

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

4. สร้างเครื่องมือแก้ไขการทำงานอัตโนมัติ

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

img11-01.png img11-02.png img11-03.png img11-04.png

ตั้งค่าเงื่อนไขเริ่มต้น

เงื่อนไขเริ่มต้นสำหรับการทำงานอัตโนมัติคือจุดแรกเข้าสำหรับการทำงานอัตโนมัติ เงื่อนไขเริ่มต้นจะทริกเกอร์การทำงานอัตโนมัติเมื่อเกิดเหตุการณ์ที่กำหนด ในแอปตัวอย่าง เราจะบันทึกตัวเริ่มต้นการทำงานอัตโนมัติโดยใช้คลาส StarterViewModel ซึ่งอยู่ในไฟล์ต้นฉบับ StarterViewModel.kt และแสดงมุมมองของเอดิเตอร์โดยใช้ StarterView (StarterView.kt)

โหนดเริ่มต้นต้องมีองค์ประกอบต่อไปนี้

  • อุปกรณ์
  • ลักษณะ
  • การดำเนินการ
  • ค่า

คุณเลือกอุปกรณ์และลักษณะจากออบเจ็กต์ที่ Devices API แสดงผลได้ คำสั่งและพารามิเตอร์สำหรับอุปกรณ์ที่รองรับแต่ละเครื่องเป็นเรื่องที่ซับซ้อนกว่าและต้องจัดการแยกกัน

แอปจะกำหนดรายการการดำเนินการที่ตั้งค่าไว้ล่วงหน้าดังนี้

   // List of operations available when creating automation starters:
enum class Operation {
  EQUALS,
  NOT_EQUALS,
  GREATER_THAN,
  GREATER_THAN_OR_EQUALS,
  LESS_THAN,
  LESS_THAN_OR_EQUALS
    }

จากนั้นสำหรับลักษณะที่รองรับแต่ละรายการ ให้ติดตามการดำเนินการที่รองรับ

// List of operations available when comparing booleans:
 object BooleanOperations : Operations(listOf(
     Operation.EQUALS,
     Operation.NOT_EQUALS
 ))
// List of operations available when comparing values:
object LevelOperations : Operations(listOf(
    Operation.GREATER_THAN,
    Operation.GREATER_THAN_OR_EQUALS,
    Operation.LESS_THAN,
    Operation.LESS_THAN_OR_EQUALS
))

ในทำนองเดียวกัน แอปตัวอย่างจะติดตามค่าที่กำหนดให้กับลักษณะดังนี้

enum class OnOffValue {
   On,
   Off,
}
enum class ThermostatValue {
  Heat,
  Cool,
  Off,
}

และติดตามการแมประหว่างค่าที่แอปกำหนดกับค่าที่ API กำหนด

val valuesOnOff: Map<OnOffValue, Boolean> = mapOf(
  OnOffValue.On to true,
  OnOffValue.Off to false,
)
val valuesThermostat: Map<ThermostatValue, ThermostatTrait.SystemModeEnum> = mapOf(
  ThermostatValue.Heat to ThermostatTrait.SystemModeEnum.Heat,
  ThermostatValue.Cool to ThermostatTrait.SystemModeEnum.Cool,
  ThermostatValue.Off to ThermostatTrait.SystemModeEnum.Off,
)

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

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.1.1 ในไฟล์ StarterView.kt เพื่อแสดงอุปกรณ์เริ่มต้นทั้งหมด และใช้การเรียกกลับเมื่อคลิกใน DropdownMenu

val deviceVMs: List<DeviceViewModel> = structureVM.deviceVMs.collectAsState().value
...
DropdownMenu(expanded = expandedDeviceSelection, onDismissRequest = { expandedDeviceSelection = false }) {
// TODO: 4.1.1 - Starter device selection dropdown
// for (deviceVM in deviceVMs) {
//     DropdownMenuItem(
//         text = { Text(deviceVM.name) },
//         onClick = {
//             scope.launch {
//                 starterDeviceVM.value = deviceVM
//                 starterType.value = deviceVM.type.value
//                 starterTrait.value = null
//                 starterOperation.value = null
//             }
//             expandedDeviceSelection = false
//         }
//     )
// }
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.1.2 ในไฟล์ StarterView.kt เพื่อแสดงลักษณะทั้งหมดของอุปกรณ์เริ่มต้นและใช้การเรียกกลับเมื่อคลิกใน DropdownMenu

// Selected starter attributes for StarterView on screen:
val starterDeviceVM: MutableState<DeviceViewModel?> = remember {
mutableStateOf(starterVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.1.2 - Starter device traits selection dropdown
// val deviceTraits = starterDeviceVM.value?.traits?.collectAsState()?.value!!
// for (trait in deviceTraits) {
//     DropdownMenuItem(
//         text = { Text(trait.factory.toString()) },
//         onClick = {
//             scope.launch {
//                 starterTrait.value = trait.factory
//                 starterOperation.value = null
//             }
//             expandedTraitSelection = false
//         }
//     )
}
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.1.3 ในไฟล์ StarterView.kt เพื่อแสดงผลการดำเนินการทั้งหมดของลักษณะที่เลือก และใช้การเรียกกลับเมื่อคลิกใน DropdownMenu

val starterOperation: MutableState<StarterViewModel.Operation?> = remember {
  mutableStateOf(starterVM.operation.value) }
  ...
  DropdownMenu(expanded = expandedOperationSelection, onDismissRequest = { expandedOperationSelection = false }) {
    // ...
    if (!StarterViewModel.starterOperations.containsKey(starterTrait.value))
    return@DropdownMenu
    // TODO: 4.1.3 - Starter device trait operations selection dropdown
      // val operations: List<StarterViewModel.Operation> = StarterViewModel.starterOperations.get(starterTrait.value ?: OnOff)?.operations!!
    //  for (operation in operations) {
    //      DropdownMenuItem(
    //          text = { Text(operation.toString()) },
    //          onClick = {
    //              scope.launch {
    //                  starterOperation.value = operation
    //              }
    //              expandedOperationSelection = false
    //          }
    //      )
    //  }
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.1.4 ในไฟล์ StarterView.kt เพื่อแสดงค่าทั้งหมดของลักษณะที่เลือก และใช้การเรียกกลับการคลิกใน DropdownMenu

when (starterTrait.value) {
  OnOff -> {
        ...
    DropdownMenu(expanded = expandedBooleanSelection, onDismissRequest = { expandedBooleanSelection = false }) {
// TODO: 4.1.4 - Starter device trait values selection dropdown
//             for (value in StarterViewModel.valuesOnOff.keys) {
//                 DropdownMenuItem(
//                     text = { Text(value.toString()) },
//                     onClick = {
//                         scope.launch {
//                             starterValueOnOff.value = StarterViewModel.valuesOnOff.get(value)
//                         }
//                         expandedBooleanSelection = false
//                     }
//                 )
//             }
             }
              ...
          }
           LevelControl -> {
              ...
      }
   }

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.1.5 ในไฟล์ StarterView.kt เพื่อจัดเก็บตัวแปรเริ่มต้นทั้งหมดของ ViewModel ไว้ในตัวแปรเริ่มต้นของฉบับร่างการทำงานอัตโนมัติ ViewModel (draftVM.starterVMs)

val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
// Save starter button:
Button(
enabled = isOptionsSelected && isValueProvided,
onClick = {
  scope.launch {
  // TODO: 4.1.5 - store all starter ViewModel variables into draft ViewModel
  // starterVM.deviceVM.emit(starterDeviceVM.value)
  // starterVM.trait.emit(starterTrait.value)
  // starterVM.operation.emit(starterOperation.value)
  // starterVM.valueOnOff.emit(starterValueOnOff.value!!)
  // starterVM.valueLevel.emit(starterValueLevel.value!!)
  // starterVM.valueBooleanState.emit(starterValueBooleanState.value!!)
  // starterVM.valueOccupancy.emit(starterValueOccupancy.value!!)
  // starterVM.valueThermostat.emit(starterValueThermostat.value!!)
  //
  // draftVM.starterVMs.value.add(starterVM)
  // draftVM.selectedStarterVM.emit(null)
  }
})
{ Text(stringResource(R.string.starter_button_create)) }

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

79beb3b581ec71ec.png

แอปตัวอย่างรองรับเฉพาะการเริ่มต้นที่อิงตามลักษณะของอุปกรณ์

ตั้งค่าการดำเนินการ

การดำเนินการอัตโนมัติแสดงให้เห็นวัตถุประสงค์หลักของการทำงานอัตโนมัติ ซึ่งก็คือวิธีที่การทำงานอัตโนมัติทำให้เกิดการเปลี่ยนแปลงในโลกจริง ในแอปตัวอย่าง เราจะบันทึกการดำเนินการอัตโนมัติโดยใช้คลาส ActionViewModel และแสดงมุมมองเอดิเตอร์โดยใช้คลาส ActionView

แอปตัวอย่างใช้เอนทิตี Home API ต่อไปนี้เพื่อกำหนดโหนดการดำเนินการอัตโนมัติ

  • อุปกรณ์
  • ลักษณะ
  • คำสั่ง
  • ค่า (ไม่บังคับ)

การดำเนินการคำสั่งอุปกรณ์แต่ละรายการจะใช้คำสั่ง แต่บางรายการจะต้องมีค่าพารามิเตอร์ที่เชื่อมโยงด้วย เช่น MoveToLevel() และเปอร์เซ็นต์เป้าหมาย

คุณเลือกอุปกรณ์และลักษณะจากออบเจ็กต์ที่ Devices API แสดงผลได้

แอปจะกำหนดรายการคำสั่งที่กำหนดไว้ล่วงหน้าดังนี้

   // List of operations available when creating automation starters:
enum class Action {
  ON,
  OFF,
  MOVE_TO_LEVEL,
  MODE_HEAT,
  MODE_COOL,
  MODE_OFF,
}

แอปจะติดตามการดำเนินการที่รองรับสำหรับลักษณะที่รองรับแต่ละรายการ

 // List of operations available when comparing booleans:
object OnOffActions : Actions(listOf(
    Action.ON,
    Action.OFF,
))
// List of operations available when comparing booleans:
object LevelActions : Actions(listOf(
    Action.MOVE_TO_LEVEL
))
// List of operations available when comparing booleans:
object ThermostatActions : Actions(listOf(
    Action.MODE_HEAT,
    Action.MODE_COOL,
    Action.MODE_OFF,
))
// Map traits and the comparison operations they support:
val actionActions: Map<TraitFactory<out Trait>, Actions> = mapOf(
    OnOff to OnOffActions,
    LevelControl to LevelActions,
 // BooleanState - No Actions
 // OccupancySensing - No Actions
    Thermostat to ThermostatActions,
)

สำหรับคำสั่งที่ใช้พารามิเตอร์อย่างน้อย 1 รายการ จะมีตัวแปรดังนี้

   val valueLevel: MutableStateFlow<UByte?>

API จะแสดงชุดองค์ประกอบมุมมองที่ผู้ใช้ใช้เพื่อเลือกช่องที่ต้องการได้

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.2.1 ในไฟล์ ActionView.kt เพื่อแสดงอุปกรณ์การดำเนินการทั้งหมด และใช้การเรียกกลับการคลิกใน DropdownMenu เพื่อตั้งค่า actionDeviceVM

val deviceVMs = structureVM.deviceVMs.collectAsState().value
...
DropdownMenu(expanded = expandedDeviceSelection, onDismissRequest = { expandedDeviceSelection = false }) {
// TODO: 4.2.1 - Action device selection dropdown
// for (deviceVM in deviceVMs) {
//     DropdownMenuItem(
//         text = { Text(deviceVM.name) },
//         onClick = {
//             scope.launch {
//                 actionDeviceVM.value = deviceVM
//                 actionTrait.value = null
//                 actionAction.value = null
//             }
//             expandedDeviceSelection = false
//         }
//     )
// }
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.2.2 ในไฟล์ ActionView.kt เพื่อแสดงลักษณะทั้งหมดของ actionDeviceVM และใช้การเรียกกลับการคลิกใน DropdownMenu เพื่อตั้งค่า actionTrait ซึ่งแสดงลักษณะที่คำสั่งเป็นของ

val actionDeviceVM: MutableState<DeviceViewModel?> = remember {
mutableStateOf(actionVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.2.2 - Action device traits selection dropdown
// val deviceTraits: List<Trait> = actionDeviceVM.value?.traits?.collectAsState()?.value!!
// for (trait in deviceTraits) {
//     DropdownMenuItem(
//         text = { Text(trait.factory.toString()) },
//         onClick = {
//             scope.launch {
//                 actionTrait.value = trait
//                 actionAction.value = null
//             }
//             expandedTraitSelection = false
//         }
//     )
// }
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.2.3 ในไฟล์ ActionView.kt เพื่อแสดงการดำเนินการทั้งหมดที่ใช้ได้ของ actionTrait และใช้การเรียกกลับการคลิกใน DropdownMenu เพื่อตั้งค่า actionAction ซึ่งแสดงถึงการดำเนินการอัตโนมัติที่เลือก

DropdownMenu(expanded = expandedActionSelection, onDismissRequest = { expandedActionSelection = false }) {
// ...
if (!ActionViewModel.actionActions.containsKey(actionTrait.value?.factory))
return@DropdownMenu
// TODO: 4.2.3 - Action device trait actions (commands) selection dropdown
// val actions: List<ActionViewModel.Action> = ActionViewModel.actionActions.get(actionTrait.value?.factory)?.actions!!
// for (action in actions) {
//     DropdownMenuItem(
//         text = { Text(action.toString()) },
//         onClick = {
//             scope.launch {
//                 actionAction.value = action
//             }
//             expandedActionSelection = false
//         }
//     )
// }
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.2.4 ในไฟล์ ActionView.kt เพื่อแสดงค่าที่ใช้ได้ของการดำเนินการลักษณะ (คำสั่ง) และจัดเก็บค่าไว้ใน actionValueLevel ในการเรียกกลับการเปลี่ยนแปลงค่า

when (actionTrait.value?.factory) {
LevelControl -> {
// TODO: 4.2.4 - Action device trait action(command) values selection widget
// Column (Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth()) {
//   Text(stringResource(R.string.action_title_value), fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
//  }
//
//  Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
//      LevelSlider(value = actionValueLevel.value?.toFloat()!!, low = 0f, high = 254f, steps = 0,
//          modifier = Modifier.padding(top = 16.dp),
//          onValueChange = { value : Float -> actionValueLevel.value = value.toUInt().toUByte() }
//          isEnabled = true
//      )
//  }
...
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.2.5 ในไฟล์ ActionView.kt เพื่อจัดเก็บตัวแปรของการดำเนินการทั้งหมด ViewModel ในการดำเนินการของระบบอัตโนมัติฉบับร่าง ViewModel (draftVM.actionVMs)

val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
// Save action button:
Button(
  enabled = isOptionsSelected,
  onClick = {
  scope.launch {
  // TODO: 4.2.5 - store all action ViewModel variables into draft ViewModel
  // actionVM.deviceVM.emit(actionDeviceVM.value)
  // actionVM.trait.emit(actionTrait.value)
  // actionVM.action.emit(actionAction.value)
  // actionVM.valueLevel.emit(actionValueLevel.value)
  //
  // draftVM.actionVMs.value.add(actionVM)
  // draftVM.selectedActionVM.emit(null)
  }
})
{ Text(stringResource(R.string.action_button_create)) }

การเรียกใช้แอปและการเลือกการทำงานอัตโนมัติและการดำเนินการใหม่ควรส่งผลให้ได้มุมมองดังต่อไปนี้

6efa3c7cafd3e595.png

เรารองรับเฉพาะการดำเนินการตามลักษณะของอุปกรณ์ในแอปตัวอย่าง

แสดงการทำงานอัตโนมัติฉบับร่าง

เมื่อ DraftViewModel เสร็จสมบูรณ์แล้ว HomeAppView.kt จะแสดงผลได้

fun HomeAppView (homeAppVM: HomeAppViewModel) {
  ...
  // If a draft automation is selected, show the draft editor:
  if (selectedDraftVM != null) {
    DraftView(homeAppVM)
  }
  ...
}

ใน DraftView.kt

fun DraftView (homeAppVM: HomeAppViewModel) {
   val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
    ...
// Draft Starters:
   DraftStarterList(draftVM)
// Draft Actions:
   DraftActionList(draftVM)
}

สร้างการทำงานอัตโนมัติ

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

การเตรียมการทำงานอัตโนมัติฉบับร่างจะเกิดขึ้นในคลาส DraftViewModel ในแอปตัวอย่าง ดูฟังก์ชัน getDraftAutomation() เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่เราจัดโครงสร้างฉบับร่างการทำงานอัตโนมัติโดยใช้ตัวแปรเริ่มต้นและการดำเนินการในส่วนก่อนหน้า

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.4.1 ในไฟล์ DraftViewModel.kt เพื่อสร้างนิพจน์ "เลือก" ที่จำเป็นต่อการสร้างกราฟการทำงานอัตโนมัติเมื่อลักษณะเริ่มต้นเป็น OnOff

val starterVMs: List<StarterViewModel> = starterVMs.value
val actionVMs: List<ActionViewModel> = actionVMs.value
    ...
fun getDraftAutomation() : DraftAutomation {
    ...
  val starterVMs: List<StarterViewModel> = starterVMs.value
    ...
  return automation {
    this.name = name
    this.description = description
    this.isActive = true
    // The sequential block wrapping all nodes:
    sequential {
    // The select block wrapping all starters:
      select {
    // Iterate through the selected starters:
        for (starterVM in starterVMs) {
        // The sequential block for each starter (should wrap the Starter Expression!)
          sequential {
              ...
              val starterTrait: TraitFactory<out Trait> = starterVM.trait.value!!
              ...
              when (starterTrait) {
                  OnOff -> {
        // TODO: 4.4.1 - Set starter expressions according to trait type
        //   val onOffValue: Boolean = starterVM.valueOnOff.value
        //   val onOffExpression: TypedExpression<out OnOff> =
        //       starterExpression as TypedExpression<out OnOff>
        //   when (starterOperation) {
        //       StarterViewModel.Operation.EQUALS ->
        //           condition { expression = onOffExpression.onOff equals onOffValue }
        //       StarterViewModel.Operation.NOT_EQUALS ->
        //           condition { expression = onOffExpression.onOff notEquals onOffValue }
        //       else -> { MainActivity.showError(this, "Unexpected operation for OnOf
        //   }
        }
   LevelControl -> {
     ...
// Function to allow manual execution of the automation:
manualStarter()
     ...
}

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.4.2 ในไฟล์ DraftViewModel.kt เพื่อสร้างนิพจน์คู่ขนานที่จำเป็นต่อการสร้างกราฟการทำงานอัตโนมัติเมื่อลักษณะการทำงานที่เลือกคือ LevelControl และการทำงานที่เลือกคือ MOVE_TO_LEVEL

val starterVMs: List<StarterViewModel> = starterVMs.value
val actionVMs: List<ActionViewModel> = actionVMs.value
    ...
fun getDraftAutomation() : DraftAutomation {
      ...
  return automation {
    this.name = name
    this.description = description
    this.isActive = true
    // The sequential block wrapping all nodes:
    sequential {
          ...
    // Parallel block wrapping all actions:
      parallel {
        // Iterate through the selected actions:
        for (actionVM in actionVMs) {
          val actionDeviceVM: DeviceViewModel = actionVM.deviceVM.value!!
        // Action Expression that the DSL will check for:
          action(actionDeviceVM.device, actionDeviceVM.type.value.factory) {
            val actionCommand: Command = when (actionVM.action.value) {
                  ActionViewModel.Action.ON -> { OnOff.on() }
                  ActionViewModel.Action.OFF -> { OnOff.off() }
    // TODO: 4.4.2 - Set starter expressions according to trait type
    // ActionViewModel.Action.MOVE_TO_LEVEL -> {
    //     LevelControl.moveToLevelWithOnOff(
    //         actionVM.valueLevel.value!!,
    //         0u,
    //         LevelControlTrait.OptionsBitmap(),
    //         LevelControlTrait.OptionsBitmap()
    //     )
    // }
      ActionViewModel.Action.MODE_HEAT -> { SimplifiedThermostat
      .setSystemMode(SimplifiedThermostatTrait.SystemModeEnum.Heat) }
          ...
}

ขั้นตอนสุดท้ายในการทําการทำงานอัตโนมัติให้เสร็จสมบูรณ์คือการใช้ฟังก์ชัน getDraftAutomation เพื่อสร้าง AutomationDraft.

ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 4.4.3 ในไฟล์ HomeAppViewModel.kt เพื่อสร้างการทำงานอัตโนมัติโดยการเรียกใช้ Home API และจัดการข้อยกเว้น

fun createAutomation(isPending: MutableState<Boolean>) {
  viewModelScope.launch {
    val structure : Structure = selectedStructureVM.value?.structure!!
    val draft : DraftAutomation = selectedDraftVM.value?.getDraftAutomation()!!
    isPending.value = true
    // TODO: 4.4.3 - Call the Home API to create automation and handle exceptions
    // // Call Automation API to create an automation from a draft:
    // try {
    //     structure.createAutomation(draft)
    // }
    // catch (e: Exception) {
    //     MainActivity.showError(this, e.toString())
    //     isPending.value = false
    //     return@launch
    // }
    // Scrap the draft and automation candidates used in the process:
    selectedCandidateVMs.emit(null)
    selectedDraftVM.emit(null)
    isPending.value = false
  }
}

ตอนนี้ให้เรียกใช้แอปและดูการเปลี่ยนแปลงในอุปกรณ์

เมื่อเลือกเงื่อนไขเริ่มต้นและการดำเนินการแล้ว คุณก็พร้อมที่จะสร้างการทำงานอัตโนมัติได้โดยทำดังนี้

ec551405f8b07b8e.png

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

8eebc32cd3755618.png

แตะการทำงานอัตโนมัติที่คุณเพิ่งสร้างขึ้น แล้วดูว่า API ส่งคืนการทำงานอัตโนมัตินั้นอย่างไร

931dba7c325d6ef7.png

โปรดทราบว่า API จะแสดงค่าที่ระบุว่าการทำงานอัตโนมัติถูกต้องและใช้งานอยู่หรือไม่ คุณสร้างการทำงานอัตโนมัติที่ไม่ผ่านการตรวจสอบได้เมื่อมีการแยกวิเคราะห์ในฝั่งเซิร์ฟเวอร์ หากการแยกวิเคราะห์การทำงานอัตโนมัติไม่ผ่านการตรวจสอบ ระบบจะตั้งค่า isValid เป็น false ซึ่งบ่งชี้ว่าการทำงานอัตโนมัติไม่ถูกต้องและไม่ได้ใช้งาน หากการทำงานอัตโนมัติไม่ถูกต้อง ให้ดูรายละเอียดในช่องautomation.validationIssues

ตรวจสอบว่าการทำงานอัตโนมัติได้รับการตั้งค่าเป็น "ใช้งานได้" และ "ใช้งานอยู่" จากนั้นคุณจะลองใช้การทำงานอัตโนมัติได้

ลองใช้การทำงานอัตโนมัติ

การทำงานอัตโนมัติสามารถดำเนินการได้ 2 วิธี ดังนี้

  1. ด้วยเหตุการณ์เงื่อนไขเริ่มต้น หากตรงตามเงื่อนไข ระบบจะทริกเกอร์การดำเนินการที่คุณตั้งค่าไว้ในการทำงานอัตโนมัติ
  2. ด้วยการเรียก API การดำเนินการด้วยตนเอง

หากการทำงานอัตโนมัติฉบับร่างมี manualStarter() ที่กำหนดไว้ในบล็อก DSL ของการทำงานอัตโนมัติฉบับร่าง เครื่องมือการทำงานอัตโนมัติจะรองรับการเรียกใช้ด้วยตนเองสำหรับการทำงานอัตโนมัตินั้น ซึ่งมีอยู่ในตัวอย่างโค้ดในแอปตัวอย่างอยู่แล้ว

เนื่องจากคุณยังอยู่ในหน้าจอมุมมองการทำงานอัตโนมัติบนอุปกรณ์เคลื่อนที่ ให้แตะปุ่มเรียกใช้ด้วยตนเอง ซึ่งควรเรียกใช้ automation.execute() ซึ่งจะเรียกใช้คำสั่งการดำเนินการในอุปกรณ์ที่คุณเลือกเมื่อตั้งค่าการทำงานอัตโนมัติ

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

ไปที่แท็บอุปกรณ์ เลือกอุปกรณ์การดำเนินการและลักษณะ จากนั้นตั้งค่าเป็นค่าอื่น (เช่น ตั้งค่า light2's LevelControl (ความสว่าง) เป็น 50% ดังที่แสดงในภาพหน้าจอต่อไปนี้

d0357ec71325d1a8.png

ตอนนี้เราจะลองทริกเกอร์การทำงานอัตโนมัติโดยใช้อุปกรณ์เริ่มต้น เลือกอุปกรณ์เริ่มต้นที่คุณเลือกเมื่อสร้างการทำงานอัตโนมัติ สลับลักษณะที่คุณเลือก (เช่น ตั้งค่า starter outlet1OnOff เป็น On)

230c78cd71c95564.png

คุณจะเห็นว่าการดำเนินการนี้ยังเรียกใช้การทำงานอัตโนมัติและตั้งค่าลักษณะ LevelControl ของอุปกรณ์การดำเนินการ light2 เป็นค่าเดิม ซึ่งก็คือ 100% ด้วย

1f00292128bde1c2.png

ขอแสดงความยินดี คุณใช้ Home API เพื่อสร้างการทำงานอัตโนมัติเรียบร้อยแล้ว

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

5. ค้นพบความสามารถ

Home API มี API เฉพาะที่เรียกว่า Discovery API ซึ่งนักพัฒนาแอปสามารถใช้เพื่อค้นหาว่าอุปกรณ์ใดรองรับลักษณะที่เปิดใช้การทำงานอัตโนมัติ แอปตัวอย่างมีตัวอย่างที่คุณใช้ API นี้เพื่อดูว่ามีคำสั่งใดบ้างที่พร้อมใช้งาน

ค้นพบคำสั่ง

ในส่วนนี้ เราจะพูดถึงวิธีค้นหา CommandCandidates ที่รองรับและวิธีสร้างการทำงานอัตโนมัติตามโหนดผู้สมัครที่ค้นพบ

ในแอปตัวอย่าง เราเรียกใช้ device.candidates() เพื่อรับรายชื่อผู้สมัคร ซึ่งอาจรวมถึงอินสแตนซ์ของ CommandCandidate, EventCandidate หรือ TraitAttributesCandidate

ไปที่HomeAppViewModel.kt แล้วยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 5.1.1 เพื่อดึงรายการผู้สมัครและกรองด้วยประเภท Candidate

   fun showCandidates() {

   ...
// TODO: 5.1.1 - Retrieve automation candidates, filtering to include CommandCandidate types only
// // Retrieve a set of initial automation candidates from the device:
// val candidates: Set<NodeCandidate> = deviceVM.device.candidates().first()
//
// for (candidate in candidates) {
//     // Check whether the candidate trait is supported:
//     if(candidate.trait !in HomeApp.supportedTraits)
//         continue
//     // Check whether the candidate type is supported:
//     when (candidate) {
//         // Command candidate type:
//         is CommandCandidate -> {
//             // Check whether the command candidate has a supported command:
//             if (candidate.commandDescriptor !in ActionViewModel.commandMap)
//                 continue
//         }
//         // Other candidate types are currently unsupported:
//         else -> { continue }
//     }
//
//     candidateVMList.add(CandidateViewModel(candidate, deviceVM))
// }
...
           // Store the ViewModels:
selectedCandidateVMs.emit(candidateVMList)
}

ดูวิธีที่ฟิลเตอร์ทำงานกับ CommandCandidate. ผู้สมัครที่ API แสดงผลมีหลายประเภท แอปตัวอย่างรองรับ CommandCandidate ยกเลิกการแสดงความคิดเห็นในขั้นตอนที่ 5.1.2 ใน commandMap ที่กำหนดไว้ใน ActionViewModel.kt เพื่อตั้งค่าลักษณะที่รองรับต่อไปนี้

    // Map of supported commands from Discovery API:
val commandMap: Map<CommandDescriptor, Action> = mapOf(
    // TODO: 5.1.2 - Set current supported commands
    // OnOffTrait.OnCommand to Action.ON,
    // OnOffTrait.OffCommand to Action.OFF,
    // LevelControlTrait.MoveToLevelWithOnOffCommand to Action.MOVE_TO_LEVEL
)

ตอนนี้เราสามารถเรียกใช้ Discovery API และกรองผลลัพธ์ที่เรารองรับในแอปตัวอย่างได้แล้ว เราจะมาพูดถึงวิธีผสานรวม API นี้เข้ากับเครื่องมือแก้ไขของเรา

8a2f0e8940f7056a.png

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Discovery API ได้ที่ใช้ประโยชน์จากการค้นหาอุปกรณ์ใน Android

ผสานรวมตัวแก้ไข

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

CandidatesView.kt ไฟล์มีคลาสมุมมองที่แสดงผู้สมัครที่ค้นพบ Uncomment ขั้นตอนที่ 5.2.1 เพื่อเปิดใช้ฟังก์ชัน .clickable{} ของ CandidateListItem ซึ่งจะตั้งค่า homeAppVM.selectedDraftVM เป็น candidateVM

fun CandidateListItem (candidateVM: CandidateViewModel, homeAppVM: HomeAppViewModel) {
    val scope: CoroutineScope = rememberCoroutineScope()
    Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
        Column (Modifier.fillMaxWidth().clickable {
        // TODO: 5.2.1 - Set the selectedDraftVM to the selected candidate
        // scope.launch { homeAppVM.selectedDraftVM.emit(DraftViewModel(candidateVM)) }
        }) {
            ...
        }
    }
}

เช่นเดียวกับขั้นตอนที่ 4.3 ใน HomeAppView.kt เมื่อตั้งค่า selectedDraftVM แล้ว ระบบจะแสดงผล DraftView(...) in DraftView.kt`:

fun HomeAppView (homeAppVM: HomeAppViewModel) {
   ...
  val selectedDraftVM: DraftViewModel? by homeAppVM.selectedDraftVM.collectAsState()
...
  // If a draft automation is selected, show the draft editor:
  if (selectedDraftVM != null) {
  DraftView(homeAppVM)
  }
   ...
}

ลองอีกครั้งโดยแตะ light2 - MOVE_TO_LEVEL ที่แสดงในส่วนก่อนหน้า ซึ่งจะแจ้งให้คุณสร้างการทำงานอัตโนมัติใหม่ตามคำสั่งของผู้สมัคร

15e67763a9241000.png

ตอนนี้คุณคุ้นเคยกับการสร้างการทำงานอัตโนมัติในแอปตัวอย่างแล้ว คุณก็สามารถผสานรวมการทำงานอัตโนมัติในแอปของคุณได้

6. ตัวอย่างการทำงานอัตโนมัติขั้นสูง

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

เวลาของวันเป็นตัวเริ่มต้น

นอกจากลักษณะของอุปกรณ์แล้ว Google Home API ยังมีลักษณะตามโครงสร้าง เช่น Time คุณสร้างการทำงานอัตโนมัติที่มีตัวเริ่มต้นตามเวลาได้ เช่น

automation {
  name = "AutomationName"
  description = "An example automation description."
  isActive = true
  description = "Do ... actions when time is up."
  sequential {
    // starter
    val starter = starter<_>(structure, Time.ScheduledTimeEvent) {
      parameter(
        Time.ScheduledTimeEvent.clockTime(
          LocalTime.of(hour, min, sec, 0)
        )
      )
    }
        // action
  ...
  }
}

การออกอากาศของ Assistant เป็นการดำเนินการ

ลักษณะ AssistantBroadcast จะพร้อมใช้งานเป็นลักษณะระดับอุปกรณ์ใน SpeakerDevice (หากลำโพงรองรับ) หรือเป็นลักษณะระดับโครงสร้าง (เนื่องจากลำโพง Google และอุปกรณ์เคลื่อนที่ Android สามารถเล่นการออกอากาศของ Assistant ได้) เช่น

automation {
  name = "AutomationName"
  description = "An example automation description."
  isActive = true
  description = "Broadcast in Speaker when ..."
  sequential {
    // starter
      ...
    // action
    action(structure) {
      command(
      AssistantBroadcast.broadcast("Time is up!!")
      )
    }
  }
}

ใช้ DelayFor และ suppressFor

นอกจากนี้ Automation API ยังมีตัวดำเนินการขั้นสูง เช่น delayFor ซึ่งใช้สำหรับหน่วงเวลาคำสั่ง และ suppressFor ซึ่งสามารถป้องกันไม่ให้การทำงานอัตโนมัติถูกทริกเกอร์โดยเหตุการณ์เดียวกันภายในช่วงเวลาที่กำหนด ตัวอย่างการใช้ตัวดำเนินการเหล่านี้มีดังนี้

sequential {
  val starterNode = starter<_>(device, OccupancySensorDevice, MotionDetection)
  // only proceed if there is currently motion taking place
  condition { starterNode.motionDetectionEventInProgress equals true }
   // ignore the starter for one minute after it was last triggered
    suppressFor(Duration.ofMinutes(1))
  
    // make announcements three seconds apart
    action(device, SpeakerDevice) {
      command(AssistantBroadcast.broadcast("Intruder detected!"))
    }
    delayFor(Duration.ofSeconds(3))
    action(device, SpeakerDevice) {
    command(AssistantBroadcast.broadcast("Intruder detected!"))
  }
    ...
}

ใช้ AreaPresenceState ใน Starter

AreaPresenceState เป็นลักษณะระดับโครงสร้างที่ตรวจหาว่ามีคนอยู่บ้านหรือไม่

ตัวอย่างเช่น ตัวอย่างต่อไปนี้แสดงการล็อกประตูโดยอัตโนมัติเมื่อมีคนอยู่บ้านหลัง 22:00 น.

automation {
  name = "Lock the doors when someone is home after 10pm"
  description = "1 starter, 2 actions"
  sequential {
    val unused =
      starter(structure, event = Time.ScheduledTimeEvent) {
        parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(22, 0, 0, 0)))
      }
    val stateReaderNode = stateReader<_>(structure, AreaPresenceState)
    condition {
      expression =
        stateReaderNode.presenceState equals
          AreaPresenceStateTrait.PresenceState.PresenceStateOccupied
    }
    action(structure) { command(AssistantBroadcast.broadcast("Locks are being applied")) }
    for (lockDevice in lockDevices) {
      action(lockDevice, DoorLockDevice) {
        command(Command(DoorLock, DoorLockTrait.LockDoorCommand.requestId.toString(), mapOf()))
      }
    }
  }

ตอนนี้คุณคุ้นเคยกับความสามารถในการทำงานอัตโนมัติขั้นสูงเหล่านี้แล้ว ไปสร้างแอปที่ยอดเยี่ยมกันเลย

7. ยินดีด้วย

ยินดีด้วย คุณพัฒนาแอป Android โดยใช้ Google Home API ส่วนที่ 2 เสร็จสมบูรณ์แล้ว ใน Codelab นี้ คุณได้สำรวจ Automation API และ Discovery API

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

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

  • อ่านการแก้ปัญหาเพื่อดูวิธีแก้ไขข้อบกพร่องของแอปอย่างมีประสิทธิภาพและแก้ปัญหาที่เกี่ยวข้องกับ Home API
  • คุณติดต่อเราเพื่อส่งคำแนะนำหรือรายงานปัญหาผ่านเครื่องมือติดตามปัญหาในหัวข้อการสนับสนุนสมาร์ทโฮมได้