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

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

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


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

โครงสร้างประกอบด้วยอินเทอร์เฟซ HasAutomations ที่มีเมธอดเฉพาะการทำงานอัตโนมัติดังนี้

API คำอธิบาย
automations() แสดงรายการการทำงานอัตโนมัติทั้งหมดที่เป็นของโครงสร้าง ระบบจะแสดงเฉพาะการทำงานอัตโนมัติ ที่คุณสร้างผ่าน Home API
createAutomation(automation) สร้างอินสแตนซ์การทำงานอัตโนมัติสำหรับบ้าน
deleteAutomation(automationId) ลบอินสแตนซ์การทำงานอัตโนมัติด้วยรหัส

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

หลังจากสร้างอินสแตนซ์ของบ้านและได้รับสิทธิ์จากผู้ใช้แล้ว ให้รับ โครงสร้างและอุปกรณ์

val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!

จากนั้นกำหนดตรรกะของการทำงานอัตโนมัติโดยใช้ DSL การทำงานอัตโนมัติ ใน Home API การทำงานอัตโนมัติจะแสดงด้วยอินเทอร์เฟซ Automation อินเทอร์เฟซนี้ มีชุดพร็อพเพอร์ตี้ต่อไปนี้

  • ข้อมูลเมตา เช่น ชื่อและคำอธิบาย
  • Flag ที่ระบุ เช่น การทำงานอัตโนมัติสามารถ ดำเนินการได้หรือไม่
  • รายการโหนดที่มีตรรกะของการทำงานอัตโนมัติ ซึ่งเรียกว่ากราฟการทำงานอัตโนมัติ ซึ่งแสดงโดยพร็อพเพอร์ตี้ automationGraph

โดยค่าเริ่มต้น automationGraph จะเป็นประเภท SequentialFlow ซึ่งเป็นคลาส ที่มีรายการโหนดที่ดำเนินการตามลำดับ แต่ละโหนด แสดงถึงองค์ประกอบของการทำงานอัตโนมัติ เช่น เงื่อนไขเริ่มต้น เงื่อนไข หรือ การดำเนินการ

กำหนดnameและdescriptionให้กับการทำงานอัตโนมัติ

การสร้างการทำงานอัตโนมัติจะตั้งค่าสถานะ isActive เป็น true โดยค่าเริ่มต้น ดังนั้นคุณจึงไม่จำเป็นต้องตั้งค่าสถานะนี้อย่างชัดเจน เว้นแต่คุณต้องการให้การทำงานอัตโนมัติปิดใช้ตั้งแต่แรก ในกรณีดังกล่าว ให้ตั้งค่าแฟล็กเป็น false ในระหว่าง การสร้าง

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

import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Condition
import com.google.home.automation.DraftAutomation
import com.google.home.automation.Equals
import com.google.home.automation.Node
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.matter.standard.OnOff
import com.google.home.Structure

...

val automation: DraftAutomation = automation {
  name = "MyFirstAutomation"
  description = "Turn on a device when another device is turned on."
  sequential {
    val starterNode = starter<_>(device1, OnOffLightDevice, trait=OnOff)
    condition() { expression = stateReaderNode.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

เมื่อกำหนด DSL การทำงานอัตโนมัติแล้ว ให้ส่งไปยังเมธอด createAutomation() เพื่อสร้างอินสแตนซ์ DraftAutomation

val createdAutomation = structure.createAutomation(automation)

จากที่นี่ คุณสามารถใช้วิธีการทำงานอัตโนมัติอื่นๆ ทั้งหมดในการทำงานอัตโนมัติ เช่น execute(), stop() และ update()

ข้อผิดพลาดเกี่ยวกับการตรวจสอบความถูกต้อง

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

ตัวอย่างโค้ด

ในที่นี้ เราจะแสดงตัวอย่างโค้ดบางส่วนที่อาจใช้เพื่อนำส่วนต่างๆ ของ การทำงานอัตโนมัติสมมติที่อธิบายไว้ในหน้า ออกแบบการทำงานอัตโนมัติใน Android ไปใช้

การทำงานอัตโนมัติอย่างง่าย

การทำงานอัตโนมัติที่เปิดม่านตอน 08:00 น. อาจมีการติดตั้งใช้งานดังนี้

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// determine whether a scheduled automation can be constructed
val isSchedulingSupported =
  allCandidates.any {
    it is EventCandidate &&
      it.eventFactory == Time.ScheduledTimeEvent &&
      it.unsupportedReasons.isEmpty()
  }
// get the blinds present in the structure
val blinds =
  allCandidates
    .filter {
      it is CommandCandidate &&
        it.commandDescriptor == WindowCoveringTrait.UpOrOpenCommand &&
        it.unsupportedReasons.isEmpty()
    }
    .map { it.entity }
    .filterIsInstance<HomeDevice>()
    .filter { it.has(WindowCoveringDevice) }
 if (isSchedulingSupported && blinds.isNotEmpty()) {
  // Proceed to create automation
  val automation: DraftAutomation = automation {
    name = "Day time open blinds"
    description = "Open all blinds at 8AM everyday"
    isActive = true
    sequential {
      // At 8:00am local time....
      val unused =
        starter(structure, Time.ScheduledTimeEvent) {
          parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(8, 0, 0, 0)))
        }
        // ...open all the blinds
       parallel {
        for (blind in blinds) {
          action(blind, WindowCoveringDevice) { command(WindowCovering.upOrOpen()) }
        }
      }
    }
  }
   val createdAutomation = structure.createAutomation(automation)
} else if (!isSchedulingSupported) {
  // Cannot create automation.
  // Set up your address on the structure, then try again.
} else {
  // You don't have any WindowCoveringDevices.
  // Try again after adding some blinds to your structure.
}

การทำงานอัตโนมัติที่ซับซ้อน

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

import com.google.home.Home
import com.google.home.HomeClient
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.equals
import com.google.home.automation.parallel
import com.google.home.automation.starter
import com.google.home.google.AssistantBroadcast
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.toggle
import com.google.home.matter.standard.OnOffLightDevice
import java.time.Duration

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()

// get the lights present in the structure
val availableLights = allCandidates.filter {
   it is CommandCandidate &&
   it.commandDescriptor == OnOffTrait.OnCommand
}.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter {it.has(OnOffLightDevice) ||
         it.has(ColorTemperatureLightDevice) ||
         it.has(DimmableLightDevice) ||
         it.has(ExtendedColorLightDevice)}

val selectedLights = ... // user selects one or more lights from availableLights

automation {
isActive = true

sequential {
   // If the presence state changes...
   val starterNode = starter<_>(structure, AreaPresenceState)
   // ...and if the area is occupied...
   condition() {
      expression = starterNode.presenceState equals PresenceState.PresenceStateOccupied
   }
   // "blink" the light(s)
   parallel {
            for(light in selectedLights) {
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle())}
         }
      }
   }
}

เลือกอุปกรณ์แบบไดนามิกด้วยตัวกรองเอนทิตี

เมื่อเขียนการทำงานอัตโนมัติ คุณไม่จำเป็นต้องระบุอุปกรณ์ที่เฉพาะเจาะจง ฟีเจอร์ที่เรียกว่าตัวกรองเอนทิตีช่วยให้ระบบอัตโนมัติเลือกอุปกรณ์ได้ใน รันไทม์โดยอิงตามเกณฑ์ต่างๆ

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

  • อุปกรณ์ทั้งหมดของอุปกรณ์ประเภทหนึ่งๆ
  • อุปกรณ์ทั้งหมดในห้องที่เฉพาะเจาะจง
  • อุปกรณ์ทั้งหมดของอุปกรณ์ประเภทหนึ่งๆ ในห้องหนึ่งๆ
  • อุปกรณ์ทั้งหมดที่เปิดอยู่
  • อุปกรณ์ทั้งหมดที่เปิดอยู่ในห้องหนึ่งๆ

วิธีใช้ตัวกรองเอนทิตี

  1. โทรหา atExecutionTime() ที่ Structure หรือ Room ซึ่งจะแสดงผลเป็น TypedExpression<TypedEntity<StructureType>>
  2. ในออบเจ็กต์นี้ ให้เรียกใช้ getDevicesOfType() โดยส่ง DeviceType ไปให้

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

ตัวอย่างเช่น หากต้องการให้หลอดไฟเปิด/ปิดใดๆ ทริกเกอร์การทำงานอัตโนมัติจากเงื่อนไขเริ่มต้น ให้ทำดังนี้

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

หากต้องการบันทึกOnOffสถานะของไฟทั้งหมดในโครงสร้าง (โดยเฉพาะ ไฟเปิด/ปิด) ในเครื่องอ่านสถานะ ให้ทำดังนี้

// Build a Map<Entity, OnOff>
val onOffStateOfAllLights =
  stateReader(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

วิธีรับหลอดไฟในห้องที่เฉพาะเจาะจงและใช้ในเงื่อนไข

val livingRoomLights =
  stateReader(
    entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )
// Are any of the lights in the living room on?
condition { expression = livingRoomLights.values.any { it.onOff equals true } }

ในรันไทม์

สถานการณ์ ผลลัพธ์
ไม่มีอุปกรณ์ที่ตรงตามเกณฑ์ในเงื่อนไขเริ่มต้น การทำงานอัตโนมัติไม่ทริกเกอร์
ไม่มีอุปกรณ์ที่ตรงตามเกณฑ์ในเครื่องอ่านสถานะ การทำงานอัตโนมัติเริ่มขึ้นแต่ไม่ดำเนินการใดๆ
ไม่มีอุปกรณ์ที่ตรงตามเกณฑ์ในการดำเนินการ การทำงานอัตโนมัติเริ่มขึ้น แต่การดำเนินการไม่ทำอะไรเลย

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

val unused = automation {
  sequential {
    // If any light is turned on or off
    val starter =
      starter(
        entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
        trait = OnOff,
      )
    condition {
      // Check to see if the triggering light was turned off
      expression = starter.onOff equals false
    }
    // Turn off all lights except the hall light
    action(
      entityExpression =
        structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter {
          it notEquals entity(hallwayLight, OnOffLightDevice)
        }
    ) {
      command(OnOff.on())
    }
  }
}

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

เรียกใช้การทำงานอัตโนมัติที่สร้างขึ้นโดยใช้เมธอด execute()

createdAutomation.execute()

หากการทำงานอัตโนมัติมีเงื่อนไขเริ่มต้นด้วยตนเอง execute() จะเริ่มการทำงานอัตโนมัติตั้งแต่จุดนั้น โดยไม่สนใจโหนดทั้งหมดที่อยู่ก่อนหน้าเงื่อนไขเริ่มต้นด้วยตนเอง หากการทำงานอัตโนมัติไม่มีตัวเริ่มต้นด้วยตนเอง การดำเนินการ จะเริ่มต้นจากโหนดที่อยู่หลังโหนดตัวเริ่มต้นแรก

หากexecute()ดำเนินการไม่สำเร็จ ระบบอาจHomeException ดูการจัดการข้อผิดพลาด

หยุดการทำงานอัตโนมัติ

หยุดการทำงานอัตโนมัติที่กำลังทำงานอยู่โดยใช้วิธี stop()


createdAutomation.stop()

หากstop()ดำเนินการไม่สำเร็จ ระบบอาจHomeException ดูการจัดการข้อผิดพลาด

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

การทำงานอัตโนมัติจะกำหนดที่ระดับบ้าน รวบรวมในโครงสร้างของ automations() เพื่อเข้าถึงFlowของการทำงานอัตโนมัติ


import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
structure.automations().collect {
  println("Available automations:")
  for (automation in it) {
    println(String.format("%S %S", "$automation.id", "$automation.name"))
  }
}

หรือจะกำหนดให้กับ Collection ในเครื่องก็ได้ โดยทำดังนี้

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

var myAutomations: Collection<Automation> = emptyList()
myAutomations = structure.automations()

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

หากต้องการรับการทำงานอัตโนมัติโดยใช้รหัสการทำงานอัตโนมัติ ให้เรียกใช้เมธอด automations() ในโครงสร้าง แล้วจับคู่กับรหัส

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()

คำตอบ:

// Here's how the automation looks like in the get response.
// Here, it's represented as if calling a println(automation.toString())

Automation(
  name = "automation-name",
  description = "automation-description",
  isActive = true,
  id = Id("automation@automation-id"),
  automationGraph = SequentialFlow(
    nodes = [
      Starter(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@6789..."),
      Action(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@8765...",
        command="on")
    ]))

รับการทำงานอัตโนมัติตามชื่อ

คุณใช้เมธอด filter() ใน Kotlin เพื่อปรับแต่งการเรียก API เพิ่มเติมได้ หากต้องการรับการทำงานอัตโนมัติ ตามชื่อ ให้รับการทำงานอัตโนมัติของโครงสร้างและกรองตามชื่อการทำงานอัตโนมัติ

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().filter {
  it.name.equals("Sunset Blinds") }

รับการทำงานอัตโนมัติทั้งหมดสำหรับอุปกรณ์

หากต้องการดูการทำงานอัตโนมัติทั้งหมดที่อ้างอิงอุปกรณ์หนึ่งๆ ให้ใช้การกรองแบบซ้อน เพื่อสแกน automationGraph ของการทำงานอัตโนมัติแต่ละรายการ

import android.util.Log
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Automation.automationGraph
import com.google.home.automation.Node
import com.google.home.automation.ParallelFlow
import com.google.home.automation.SelectFlow
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.automation.StateReader

...

fun collectDescendants(node: Node): List<Node> {
  val d: MutableList<Node> = mutableListOf(node)

  val children: List<Node> =
    when (node) {
      is SequentialFlow -> node.nodes
      is ParallelFlow -> node.nodes
      is SelectFlow -> node.nodes
      else -> emptyList()
    }
  for (c in children) {
    d += collectDescendants(c)
  }
  return d
}

val myDeviceId = "device@452f78ce8-0143-84a-7e32-1d99ab54c83a"
val structure = homeManager.structures().list().single()
val automations =
  structure.automations().first().filter {
    automation: Automation ->
    collectDescendants(automation.automationGraph!!).any { node: Node ->
      when (node) {
        is Starter -> node.entity.id.id == myDeviceId
        is StateReader -> node.entity.id.id == myDeviceId
        is Action -> node.entity.id.id == myDeviceId
        else -> false
      }
    }
  }

อัปเดตการทำงานอัตโนมัติ

หากต้องการอัปเดตข้อมูลเมตาของระบบอัตโนมัติ ให้เรียกใช้เมธอด update() ของระบบอัตโนมัตินั้น โดยส่งนิพจน์ Lambda ที่ตั้งค่าข้อมูลเมตา

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update { this.name = "Flashing lights 2" }

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

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: Automation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update {
  this.automationGraph = sequential {
    val laundryWasherCompletionEvent =
      starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
    condition {
      expression =
        laundryWasherCompletionEvent.completionErrorCode equals
          // UByte 0x00u means NoError
          0x00u
    }
    action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
    }
  }
}

ลบการทำงานอัตโนมัติ

หากต้องการลบการทำงานอัตโนมัติ ให้ใช้วิธีการ deleteAutomation() ของโครงสร้าง ต้องลบการทำงานอัตโนมัติโดยใช้รหัสของการทำงานอัตโนมัตินั้น

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().first()
structure.deleteAutomation(automation.id)

หากลบไม่สำเร็จ ระบบอาจแสดง HomeException ดูการจัดการข้อผิดพลาด

ผลกระทบของการลบอุปกรณ์ต่อการทำงานอัตโนมัติ

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