Xây dựng quy trình tự động hoá trên Android

Bạn có thể truy cập vào Automation API thông qua Home API cho Android, 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 này 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

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

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

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

Sau khi tạo một thực thể của Home và nhận được 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 Home API, 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ả.
  • Các cờ cho biết, ví dụ: liệu có thể thực thi quy trình tự động hoá hay không.
  • Một danh sách các nút chứa logic của quy trình tự động hoá, được gọi là biểu đồ quy trình 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ư điều kiện khởi động, điều kiện hoặc hành động.

Chỉ định namedescription cho quy trình tự động hoá.

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

Giao diện DraftAutomation dùng để xây dựng và tạo quy trình tự động hoá, còn giao diện Automation dùng để truy xuất. Ví dụ: đây là Automation DSL 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 Automation DSL, hãy truyền DSL này đến createAutomation() phương thức để 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ì thông báo 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 ValidationIssueType.

Ví dụ về mã

Sau đây là một số ví dụ về mã 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á trên Android.

Quy trình tự động hoá đơn giản

Một quy trình tự động hoá nâng rèm cửa lúc 8:00 sáng có thể được triển khai 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.
}

Quy trình tự động hoá phức tạp

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 có thể được triển khai 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())}
         }
      }
   }
}

Chọn thiết bị một cách linh hoạt bằng bộ lọc thực thể

Khi viết một quy trình tự động hoá, bạn không bị giới hạn trong việc chỉ định các thiết bị cụ thể. Một tính năng có tên là bộ lọc thực thể cho phép quy trình tự động hoá chọn thiết bị trong thời gian chạy dựa trên nhiều tiêu chí.

Ví dụ: bằng cách sử dụng bộ lọc thực thể, quy trình tự động hoá có thể nhắm đến:

  • tất cả các thiết bị thuộc một loại thiết bị cụ thể
  • tất cả các thiết bị trong một phòng cụ thể
  • tất cả các thiết bị thuộc một loại thiết bị cụ thể trong một phòng cụ thể
  • tất cả các thiết bị đang bật
  • tất cả các thiết bị đang bật trong một phòng cụ thể

Cách sử dụng bộ lọc thực thể:

  1. Trên Structure hoặc Room, hãy gọi atExecutionTime(). Thao tác này sẽ trả về TypedExpression<TypedEntity<StructureType>>.
  2. Trên đối tượng này, hãy gọi getDevicesOfType(), truyền đối tượng này vào DeviceType.

Bạn có thể sử dụng bộ lọc thực thể trong điều kiện khởi động, trình đọc trạng thái và hành động.

Ví dụ: để bất kỳ Đèn bật/tắt nào kích hoạt một quy trình tự động hoá từ điều kiện khởi động:

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

Để nắm bắt trạng thái OnOff của tất cả các đèn trong một cấu trúc (cụ thể là đèn bật/tắt) trong trình đọc trạng thái:

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

Để lấy đèn trong một phòng cụ thể và sử dụng đèn đó trong một điều kiện:

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

Trong thời gian chạy:

Trường hợp Kết quả
Không có thiết bị nào đáp ứng tiêu chí trong điều kiện khởi động. Quy trình tự động hoá không kích hoạt.
Không có thiết bị nào đáp ứng tiêu chí trong trình đọc trạng thái. Quy trình tự động hoá bắt đầu nhưng sẽ tiếp tục dựa trên nút điều kiện.
Không có thiết bị nào đáp ứng tiêu chí trong một hành động. Quy trình tự động hoá bắt đầu nhưng hành động không thực hiện bất kỳ thao tác nào.

Ví dụ sau đây là một quy trình tự động hoá tắt tất cả các đèn, ngoại trừ đèn hành lang bất cứ khi nào một đèn riêng lẻ bị tắt:

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

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

Chạy một quy trình tự động hoá đã 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ừ đ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ó điều kiện khởi động thủ công, thì quá trình thực thi sẽ bắt đầu từ nút sau nút điều kiện khởi động đầu tiên.

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

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

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


createdAutomation.stop()

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

Xem danh sách các quy trình tự động hoá cho một cấu trúc

Các quy trình tự động hoá được xác định ở cấp cấu trúc. Thu thập trên cấu trúc's automations() để 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 chỉ định quy trình tự động hoá 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()

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

Để xem một quy trình tự động hoá theo mã nhận dạng quy trình tự động hoá, hãy gọi automations() phương thức 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")
    ]))

Xem một 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. Để xem một quy trình tự động hoá theo tên, hãy xem 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") }

Xem tất cả các quy trình tự động hoá cho một thiết bị

Để xem 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 một quy trình tự động hoá

Để 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() của quy trình tự động hoá đó, truyền phương thức này vào một biểu thức lambda đặt 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 đồ quy trình tự động hoá, nhưng không hỗ trợ chỉnh sửa từng nút của biểu đồ. Việc chỉnh sửa từng nút dễ gây ra lỗi do sự phụ thuộc lẫn nhau giữa các nút. 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ó.

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

Xoá quy trình tự động hoá

Để xoá một quy trình tự động hoá, 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 hoá 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 quá trình xoá không thành công, thì HomeException có thể được gửi. Xem phần Xử lý lỗi.

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

Nếu người dùng xoá một thiết bị được sử dụng trong một quy trình tự động hoá, thì thiết bị đã xoá không thể kích hoạt bất kỳ điều kiện 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 gửi lệnh đến thiết bị đó. Ví dụ: nếu người dùng xoá OccupancySensorDevice khỏi nhà và một quy trình tự động hoá có điều kiện khởi động phụ thuộc vào the OccupancySensorDevice, thì điều kiện khởi động đó không còn có thể kích hoạt quy trình tự động hoá.