Tạo quy trình tự động

Bạn có thể truy cập vào các API Tự động hoá thông qua các API Home, nhưng vì điểm truy cập của các API này là thông qua một cấu trúc, nên trước tiên, bạn phải cấp quyền trên cấu trúc đó thì mới có thể sử dụng các API này.

Sau khi cấp quyền cho một cấu trúc, hãy nhập các gói sau vào ứng dụng:


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

Cấu trúc chứa giao diện HasAutomations với các phương thức dành riêng cho tính năng tự động hoá sau:

API Mô tả
automations() Liệt kê tất cả các thao tác tự động thuộc cấu trúc. Chỉ những quy trình tự động hoá mà bạn đã tạo thông qua API Home mới được trả về.
createAutomation(automation) Tạo một thực thể tự động hoá cho một cấu trúc.
deleteAutomation(automationId) Xoá một thực thể tự động hoá theo mã nhận dạng của thực thể đó.

Tạo quy trình tự động hoá

Sau khi tạo một thực thể của Home và nhận quyền từ người dùng, hãy lấy cấu trúc và(các) thiết bị:

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

Sau đó, hãy xác định logic của quy trình tự động hoá bằng Automation DSL. Trong API Home, một quy trình tự động hoá được biểu thị bằng giao diện Automation. Giao diện này chứa một tập hợp các thuộc tính:

  • Siêu dữ liệu, chẳng hạn như tên và nội dung mô tả.
  • Ví dụ: các cờ cho biết liệu có thể thực thi quy trình tự động hoá hay không.
  • Danh sách các nút chứa logic của quá trình tự động hoá, được gọi là biểu đồ tự động hoá, được biểu thị bằng thuộc tính automationGraph.

Theo mặc định, automationGraph thuộc loại SequentialFlow, là một lớp chứa danh sách các nút thực thi theo thứ tự tuần tự. Mỗi nút đại diện cho một phần tử của quy trình tự động hoá, chẳng hạn như trình khởi động, điều kiện hoặc hành động.

Gán cho quy trình tự động hoá một namedescription.

Khi tạo một quy trình tự động, cờ isActive sẽ được đặt mặc định thành true, do đó, bạn không cần đặt cờ này một cách rõ ràng trừ phi ban đầu bạn muốn tắt quy trình tự động. Trong trường hợp đó, hãy đặt cờ thành false trong quá trình tạo.

Giao diện DraftAutomation dùng để tạo và xây dựng các quy trình tự động hoá, còn giao diện Automation dùng để truy xuất. Ví dụ: sau đây là DSL tự động hoá cho một quy trình tự động hoá bật một thiết bị khi một thiết bị khác được bật:

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()) }
  }
}

Sau khi xác định DSL tự động hoá, hãy truyền DSL đó đến phương thức createAutomation() để tạo thực thể DraftAutomation:

val createdAutomation = structure.createAutomation(automation)

Từ đây, bạn có thể sử dụng tất cả các phương thức tự động hoá khác trên quy trình tự động hoá, chẳng hạn như execute(), stop()update().

Lỗi xác thực

Nếu quá trình tạo quy trình tự động hoá không vượt qua quy trình xác thực, thì một cảnh báo hoặc thông báo lỗi sẽ cung cấp thông tin về vấn đề. Để biết thêm thông tin, hãy tham khảo tài liệu tham khảo về ValidationIssueType.

Ví dụ về mã

Dưới đây là một số mã ví dụ có thể dùng để triển khai các phần của quy trình tự động hoá giả định được mô tả trên trang Thiết kế quy trình tự động hoá.

Tự động hoá đơn giản

Bạn có thể triển khai một quy trình tự động nâng rèm lúc 8:00 như sau:

// 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.
}

Tự động hoá phức tạp

Bạn có thể triển khai một quy trình tự động hoá kích hoạt đèn nhấp nháy khi phát hiện chuyển động như sau:

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())}
         }
      }
   }
}

Tự động hoá chung

Đoạn mã sau đây cho biết cách thu thập dữ liệu về nhà của người dùng bằng cách sử dụng Structure API (API Cấu trúc), Device API (API Thiết bị) và Discovery API (API Khám phá) để triển khai một ứng dụng hướng dẫn người dùng tạo quy trình tự động hoá chung của riêng họ (như mô tả trong phần Thiết kế quy trình tự động hoá: Quy trình tự động hoá chung).

val structureFlow: Flow<Structure> = home.structures().itemFlow(myStructureId)

// Get a snapshot of the structure.
val structure: Structure = structureFlow.first()

   // map of devices where key is device id
val devicesById = mutableMapOf<Id,HomeDevice>()

home.devices().list().map { devicesById.put(it.id, it) }

// map of rooms where key is room id
val roomsByName: MutableMap<String,Room?> = mutableMapOf<String,Room?>()

structure.rooms().list().map { roomsByName.put(it.name, it) }

// map of commands where key is trait id
val commandsByTrait: MutableMap<String,MutableList<CommandCandidate>?> = mutableMapOf<String,MutableList<CommandCandidate>?>()

// map of commands where key is DeviceType.Metadata.hashcode()
val commandsByDeviceType: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()

// map of commands where key is entity id
val commandsByEntity: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()


// map of stateReaders where key is trait id
val stateReadersByTrait: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is DeviceType.Metadata.hashcode()
val stateReadersByDeviceType: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is  entity id
val stateReadersByEntity: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of starters where key is trait id
val startersByTrait: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// map of starters where key is DeviceType.Metadata.hashcode()
val startersByDeviceType: MutableMap<Int,MutableList<CommandCandidate>?> = mutableMapOf<Int,MutableList<CommandCandidate>?>()

// map of starters where key is entity id
val startersByEntity: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// populate candidate maps
structure.allCandidates().first().map {
  when (it) {
   is CommandCandidate -> {

      // update commandsByTrait
      // TODO refactor into generic function to eliminate duplicate code
      var commandsByTraitList: MutableList<CommandCandidate>? = commandsByTrait.get(it.trait.factory.traitId: String)
      if(commandsByTraitList == null) { commandsByTraitList = arrayListOf<CommandCandidate>() }

      commandsByTraitList.add(it)
      commandsByTrait.put(it.trait.factory.traitId, commandsByTraitList)

      // update commandsByDeviceType
      // TODO refactor into generic function to eliminate duplicate code
      for (t in it.types) {

      //TODO filter out device types not present in the home

      //TODO how to get a reference to device type id? 

      var commandsByDeviceTypeList: MutableList<CommandCandidate>? = commandsByDeviceType.get(t.factory.typeId: Id)

      if (commandsByDeviceTypeList == null)  { commandsByDeviceTypeList = arrayListOf<CommandCandidate>() }

      commandsByDeviceTypeList.add(it)

      commandsByDeviceType.put(t.factory.typeId, commandsByDeviceTypeList)
      }

      // update commandsByEntity
      // TODO refactor into generic function to eliminate duplicate code
     var commandsByEntityList: MutableList<CommandCandidate>? = commandsByEntity.get(it.entity.id: Id)
      if ( commandsByEntityList == null ) {commandsByEntityList = arrayListOf<CommandCandidate>()}
      commandsByEntityList.add(it)
      commandsByEntity.put(it.entity.id, commandsByEntityList)
   }

/*
   is StateReaderCandidate -> {

      // update stateReadersByTrait

      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByTrait.get(it.trait.factory.traitId)
      if(stateReadersList == null) { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.add(it)
      stateReadersByTrait.put(it.trait.factory.traitId, stateReadersList)

   // update stateReadersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByDeviceType.get(t.metadata.hashcode())

      if (stateReadersList == null)  { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.put(it)

      stateReadersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update stateReadersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = stateReadersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)
   }

   */

   /*
   is StarterCandidate  -> {

      // update startersByTrait

      var startersList: MutableList<StateReaderCandidate>? = startersByTrait.get(it.trait.factory.traitId)
      if(startersList == null) { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.add(it)
      startersByTrait.put(it.trait.factory.traitId, startersList)

   // update startersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var startersList: MutableList<StateReaderCandidate>? = startersByDeviceType.get(t.metadata.hashcode())

      if (startersList == null)  { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.put(it)

      startersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update startersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = startersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)

      }
*/
 else -> println("unknown type encountered: " + it)

  }
}

Thực thi quy trình tự động hoá

Chạy quy trình tự động đã tạo bằng phương thức execute():

createdAutomation.execute()

Nếu quy trình tự động hoá có điều kiện khởi động thủ công, thì execute() sẽ bắt đầu quy trình tự động hoá từ thời điểm đó, bỏ qua tất cả các nút đứng trước điều kiện khởi động thủ công. Nếu quy trình tự động hoá không có trình khởi động thủ công, quá trình thực thi sẽ bắt đầu từ nút theo sau nút trình khởi động đầu tiên.

Nếu thao tác execute() không thành công, HomeException có thể được gửi. Xem phần Xử lý lỗi.

Dừng quy trình tự động hoá

Dừng một quy trình tự động đang chạy bằng phương thức stop():


createdAutomation.stop()

Nếu thao tác stop() không thành công, HomeException có thể được gửi. Xem phần Xử lý lỗi.

Lấy danh sách các thao tác tự động cho một cấu trúc

Thao tác tự động được xác định ở cấp cấu trúc. Thu thập trên automations() của cấu trúc để truy cập vào Flow của các quy trình tự động hoá:


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"))
  }
}

Ngoài ra, hãy gán giá trị này cho một Collection cục bộ:

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()

Lấy quy trình tự động hoá theo mã nhận dạng

Để lấy một quy trình tự động hoá theo mã nhận dạng quy trình tự động hoá, hãy gọi phương thức automations() trên cấu trúc và so khớp theo mã nhận dạng:

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()

Phản hồi:

// 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")
    ]))

Tìm quy trình tự động hoá theo tên

Bạn có thể sử dụng phương thức filter() trong Kotlin để tinh chỉnh thêm các lệnh gọi API. Để lấy một quy trình tự động hoá theo tên, hãy lấy các quy trình tự động hoá của cấu trúc và lọc theo tên quy trình tự động hoá:

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') }

Nhận tất cả thao tác tự động cho một thiết bị

Để lấy tất cả các quy trình tự động hoá tham chiếu đến một thiết bị nhất định, hãy sử dụng tính năng lọc lồng nhau để quét automationGraph của từng quy trình tự động hoá:

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
      }
    }
  }

Cập nhật quy trình tự động

Để cập nhật siêu dữ liệu của một quy trình tự động hoá, hãy gọi phương thức update(), truyền vào đó một biểu thức lambda để thiết lập siêu dữ liệu:

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" }

Phương thức update() hỗ trợ thay thế hoàn toàn biểu đồ tự động hoá, nhưng không hỗ trợ chỉnh sửa biểu đồ theo từng nút. Việc chỉnh sửa theo nút dễ xảy ra lỗi do các nút phụ thuộc lẫn nhau. Nếu bạn muốn thay đổi logic của một quy trình tự động hoá, hãy tạo một biểu đồ mới và thay thế hoàn toàn biểu đồ hiện có.

Xoá quy tắc của thao tác tự động

Để xoá một quy tắc tự động, hãy sử dụng phương thức deleteAutomation() của cấu trúc. Bạn phải xoá một quy trình tự động bằng mã nhận dạng của quy trình đó.

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)

Nếu không xoá được, HomeException có thể được gửi. Xem phần Xử lý lỗi.

Ảnh hưởng của việc xoá thiết bị đối với các quy trình tự động

Nếu người dùng xoá một thiết bị được dùng trong một quy trình tự động hoá, thì thiết bị đã xoá sẽ không thể kích hoạt bất kỳ trình khởi động nào và quy trình tự động hoá sẽ không thể đọc các thuộc tính từ thiết bị đó hoặc đưa ra lệnh cho thiết bị đó. Ví dụ: nếu người dùng xoá một OccupancySensorDevice khỏi nhà của họ và một quy trình tự động hoá có trình khởi động phụ thuộc vào OccupancySensorDevice, thì trình khởi động đó không thể kích hoạt quy trình tự động hoá nữa.