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 của bạn:


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ả quy trình tự động hoá thuộc cấu trúc. Chỉ những quy trình tự động hoá mà bạn đã tạo thông qua Home API mới được trả về.
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 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ờ cho biết, ví dụ: liệu quy trình tự động hoá có thể được thực thi 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.

automationGraphtheo mặc định là thuộc loại SequentialFlow. Đây 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 theo mặc định, do đó, bạn không cần phải đặ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 hoá. 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ụ: sau đâ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 vào 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 quá trình xác thực, thì một 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 đề này. Để biết thêm thông tin, hãy tham khảo tài liệu tham khảo ValidationIssueType.

Ngay cả khi createAutomation() hoàn tất mà không đưa ra ngoại lệ, quy trình tự động hoá đã tạo có thể không hợp lệ hoặc không thực thi được. Phần phụ trợ cho phép lưu các bản nháp quy trình tự động hoá không hợp lệ (ví dụ: nếu người dùng chưa cấp các sự đồng ý bắt buộc như tính năng phát hiện Khuôn mặt quen thuộc hoặc nếu thiếu các chức năng của thiết bị).

Bạn phải luôn xác minh isValid và kiểm tra validationIssues trên thực thể được trả về Automation:

val createdAutomation = structure.createAutomation(automation)

if (!createdAutomation.isValid) {
  // Iterate through validation issues to identify errors and warnings
  for (issue in createdAutomation.validationIssues) {
    when (issue.severity) {
      ValidationIssueSeverity.ERROR -> {
        Log.e(
          "Automation",
          "Validation error on node ${issue.node}: ${issue.issueType}"
        )
        // Handle error (for example, prompt the user to enable missing
        // consents or device features)
      }
      ValidationIssueSeverity.WARNING -> {
        Log.w(
          "Automation",
          "Validation warning on node ${issue.node}: ${issue.issueType}"
        )
      }
      else -> {}
    }
  }
}

Ví dụ về mã

Sau đây là một số ví dụ về mã mà bạn 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ế một quy trình tự động hoá trên Android.

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

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

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

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 chỉ giới hạn ở 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ả thiết bị thuộc một loại thiết bị cụ thể
  • tất cả thiết bị trong một phòng cụ thể
  • tất 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ả thiết bị đang bật
  • tất 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,
  )

Để ghi lại trạng thái OnOff của tất 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 chúng 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 làm gì.

Ví dụ sau đây là một quy trình tự động hoá tắt tất 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 , execute() sẽ bắt đầu quy trình tự động hoá từ điểm đó, bỏ qua tất cả các nút 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 đưa ra. 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 đưa ra. Xem phần Xử lý lỗi.

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

Quy trình tự động hoá được xác định ở cấp cấu trúc. Thu thập trên `automations()` của cấu trúc automations() để truy cập vào Flow của 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 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ả quy trình tự động hoá cho một thiết bị

Để xem tất 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 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 biểu đồ theo từng nút. Việc chỉnh sửa theo từng nút dễ xả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á một 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 đưa ra. 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 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ỳ đ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à của họ 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 đó sẽ không còn kích hoạt được quy trình tự động hoá.