Tạo quy trình tự động hoá bằng Home API trên Android

1. Trước khi bắt đầu

Đây là lớp học lập trình thứ hai trong loạt lớp học lập trình về cách tạo ứng dụng Android bằng API Google Home. Trong lớp học lập trình này, chúng ta sẽ tìm hiểu cách tạo tính năng tự động hoá nhà ở và đưa ra một số mẹo về các phương pháp hay nhất khi sử dụng API. Nếu chưa hoàn thành lớp học lập trình đầu tiên, Tạo ứng dụng di động bằng Home API trên Android, bạn nên hoàn thành lớp học lập trình đó trước khi bắt đầu lớp học lập trình này.

Google Home API cung cấp một bộ thư viện để nhà phát triển Android điều khiển các thiết bị nhà thông minh trong hệ sinh thái Google Home. Với các API mới này, nhà phát triển có thể thiết lập các quy trình tự động hoá cho nhà thông minh để điều khiển các chức năng của thiết bị dựa trên các điều kiện được xác định trước. Google cũng cung cấp một Discovery API (API Khám phá) cho phép bạn truy vấn các thiết bị để tìm hiểu những thuộc tính và lệnh mà chúng hỗ trợ.

Điều kiện tiên quyết

Kiến thức bạn sẽ học được

  • Cách tạo quy trình tự động hoá cho các thiết bị nhà thông minh bằng Home API.
  • Cách sử dụng Discovery API để khám phá các chức năng được hỗ trợ của thiết bị.
  • Cách áp dụng các phương pháp hay nhất khi tạo ứng dụng bằng Home API.

2. Thiết lập dự án

Sơ đồ sau đây minh hoạ cấu trúc của một ứng dụng Home API:

Cấu trúc của Home API cho ứng dụng Android

  • Mã ứng dụng: Mã cốt lõi mà nhà phát triển sử dụng để tạo giao diện người dùng của ứng dụng và logic tương tác với Home APIs SDK.
  • Home APIs SDK: Home APIs SDK do Google cung cấp hoạt động với Dịch vụ Home APIs trong GMSCore để điều khiển các thiết bị nhà thông minh. Nhà phát triển tạo các ứng dụng hoạt động với Home API bằng cách kết hợp các ứng dụng đó với Home API SDK.
  • GMSCore trên Android: GMSCore (còn gọi là Dịch vụ Google Play) là một nền tảng của Google cung cấp các dịch vụ hệ thống cốt lõi, cho phép các chức năng chính hoạt động trên mọi thiết bị Android được chứng nhận. Mô-đun trang chủ của Dịch vụ Google Play chứa các dịch vụ tương tác với Home API.

Trong lớp học lập trình này, chúng ta sẽ xây dựng dựa trên những nội dung đã đề cập trong lớp học lập trình Tạo ứng dụng di động bằng Home API trên Android.

Đảm bảo bạn đã thiết lập một cấu trúc có ít nhất hai thiết bị được hỗ trợ và đang hoạt động trên tài khoản. Vì chúng ta sẽ thiết lập các quy trình tự động hoá trong lớp học lập trình này (một thay đổi về trạng thái thiết bị sẽ kích hoạt một hành động trên thiết bị khác), nên bạn sẽ cần 2 thiết bị để xem kết quả.

Tải ứng dụng mẫu

Mã nguồn của Ứng dụng mẫu có trên GitHub trong kho lưu trữ google-home/google-home-api-sample-app-android.

Lớp học lập trình này sử dụng các ví dụ trong nhánh codelab-branch-2 của Ứng dụng mẫu.

Chuyển đến vị trí bạn muốn lưu dự án và sao chép nhánh codelab-branch-2:

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

Xin lưu ý rằng đây là một nhánh khác với nhánh được dùng trong phần Tạo ứng dụng di động bằng Home API trên Android. Nhánh này của cơ sở mã được xây dựng dựa trên nội dung của lớp học lập trình đầu tiên. Lần này, các ví dụ sẽ hướng dẫn bạn cách tạo quy trình tự động hoá. Nếu đã hoàn tất lớp học lập trình trước và có thể sử dụng tất cả chức năng, bạn có thể chọn sử dụng cùng một dự án Android Studio để hoàn tất lớp học lập trình này thay vì sử dụng codelab-branch-2.

Sau khi bạn biên dịch mã nguồn và sẵn sàng chạy trên thiết bị di động, hãy tiếp tục với phần tiếp theo.

3. Tìm hiểu về tính năng Tự động hoá

Tự động hoá là một tập hợp các câu lệnh "nếu điều này, thì điều đó" có thể kiểm soát trạng thái của thiết bị dựa trên các yếu tố đã chọn theo cách tự động. Nhà phát triển có thể sử dụng tính năng tự động hoá để tạo các tính năng tương tác nâng cao trong API của họ.

Tự động hoá bao gồm 3 loại thành phần được gọi là nút: nút bắt đầu, nút hành động và nút điều kiện. Các nút này hoạt động cùng nhau để tự động hoá các hành vi bằng cách sử dụng thiết bị nhà thông minh. Thông thường, các quy tắc này được đánh giá theo thứ tự sau:

  1. Trình kích hoạt – Xác định các điều kiện ban đầu kích hoạt quy trình tự động hoá, chẳng hạn như thay đổi giá trị của một đặc điểm. Một quy trình tự động hoá phải có Điều kiện khởi động.
  2. Điều kiện – Mọi điều kiện ràng buộc bổ sung cần đánh giá sau khi một quy trình tự động hoá được kích hoạt. Biểu thức trong Điều kiện phải đánh giá là đúng thì các hành động của một quy trình tự động hoá mới thực thi.
  3. Hành động – Các lệnh hoặc nội dung cập nhật trạng thái được thực hiện khi tất cả điều kiện đã được đáp ứng.

Ví dụ: bạn có thể thiết lập một quy trình tự động hoá để làm mờ đèn trong phòng khi công tắc được bật, trong khi TV trong phòng đó đang bật. Trong ví dụ này:

  • Starter – Công tắc trong phòng được bật.
  • Điều kiện – Trạng thái Bật/Tắt của TV được đánh giá là Bật.
  • Hành động – Đèn trong cùng phòng với Công tắc sẽ mờ đi.

Những nút này được Công cụ tự động hoá đánh giá theo cách tuần tự hoặc song song.

image5.png

Luồng tuần tự chứa các nút thực thi theo thứ tự tuần tự. Thông thường, đây sẽ là điều kiện bắt đầu, điều kiện và hành động.

image6.png

Một Luồng song song có thể có nhiều nút hành động thực thi đồng thời, chẳng hạn như bật nhiều đèn cùng một lúc. Các nút tuân theo một luồng song song sẽ không thực thi cho đến khi tất cả các nhánh của luồng song song kết thúc.

Ngoài ra, còn có những loại nút khác trong giản đồ tự động hoá. Bạn có thể tìm hiểu thêm về các nút này trong phần Nút của Hướng dẫn dành cho nhà phát triển Home API. Ngoài ra, nhà phát triển có thể kết hợp nhiều loại nút để tạo các quy trình tự động hoá phức tạp, chẳng hạn như:

image13.png

Nhà phát triển cung cấp các nút này cho Automation Engine bằng ngôn ngữ dành riêng cho miền (DSL) được tạo riêng cho các quy trình tự động hoá của Google Home.

Khám phá DSL tự động hoá

Ngôn ngữ dành riêng cho miền (DSL) là ngôn ngữ dùng để ghi lại hành vi của hệ thống trong mã. Trình biên dịch tạo ra các lớp dữ liệu được chuyển đổi tuần tự thành JSON của vùng đệm giao thức và được dùng để thực hiện các lệnh gọi đến Dịch vụ tự động hoá của Google.

DSL tìm kiếm giản đồ sau:

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

Quy trình tự động hoá trong ví dụ trước sẽ đồng bộ hoá 2 bóng đèn. Khi trạng thái OnOff của device1 thay đổi thành On (onOffTrait.onOff equals true), thì trạng thái OnOff của device2 sẽ thay đổi thành On (command(OnOff.on()).

Khi làm việc với các quy trình tự động hoá, hãy lưu ý rằng có giới hạn về tài nguyên.

Quy trình tự động hoá là một công cụ rất hữu ích để tạo các chức năng tự động hoá trong nhà thông minh. Trong trường hợp sử dụng cơ bản nhất, bạn có thể mã hoá rõ ràng một quy trình tự động hoá để sử dụng các thiết bị và đặc điểm cụ thể. Tuy nhiên, một trường hợp sử dụng thực tế hơn là trường hợp ứng dụng cho phép người dùng định cấu hình các thiết bị, lệnh và thông số của một quy trình tự động hoá. Phần tiếp theo giải thích cách tạo một trình chỉnh sửa tự động hoá cho phép người dùng thực hiện chính xác thao tác đó.

4. Xây dựng một trình chỉnh sửa tự động hoá

Trong Ứng dụng mẫu, chúng ta sẽ tạo một trình chỉnh sửa tự động hoá để người dùng có thể chọn thiết bị, các chức năng (hành động) mà họ muốn sử dụng và cách kích hoạt quy trình tự động hoá bằng các trình kích hoạt.

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

Thiết lập điều kiện khởi động

Điều kiện khởi động quy trình tự động hoá là điểm bắt đầu của quy trình tự động hoá. Điều kiện khởi động sẽ kích hoạt một quy trình tự động hoá khi một sự kiện nhất định diễn ra. Trong Ứng dụng mẫu, chúng ta sẽ ghi lại các thành phần khởi động tự động hoá bằng cách sử dụng lớp StarterViewModel trong tệp nguồn StarterViewModel.kt và hiển thị chế độ xem trình chỉnh sửa bằng cách sử dụng StarterView (StarterView.kt).

Một nút khởi động cần có các phần tử sau:

  • Thiết bị
  • Đặc điểm
  • Hoạt động
  • Giá trị

Bạn có thể chọn thiết bị và đặc điểm trong số các đối tượng do Devices API trả về. Các lệnh và tham số cho từng thiết bị được hỗ trợ là một vấn đề phức tạp hơn, cần được xử lý riêng.

Ứng dụng xác định một danh sách các thao tác đặt trước:

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

Sau đó, đối với mỗi đặc điểm được hỗ trợ, hãy theo dõi các thao tác được hỗ trợ:

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

Tương tự, Ứng dụng mẫu sẽ theo dõi các giá trị có thể chỉ định cho các đặc điểm:

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

Và theo dõi mối liên kết giữa các giá trị do ứng dụng xác định và các giá trị do API xác định:

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

Sau đó, ứng dụng sẽ hiển thị một nhóm các phần tử khung hiển thị mà người dùng có thể dùng để chọn các trường bắt buộc.

Huỷ đánh dấu Bước 4.1.1 trong tệp StarterView.kt để kết xuất tất cả các thiết bị khởi động và triển khai lệnh gọi lại khi nhấp vào trong 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
//         }
//     )
// }
}

Huỷ nhận xét Bước 4.1.2 trong tệp StarterView.kt để hiển thị tất cả các đặc điểm của thiết bị khởi động và triển khai lệnh gọi lại khi nhấp vào trong 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
//         }
//     )
}
}

Huỷ nhận xét Bước 4.1.3 trong tệp StarterView.kt để hiển thị tất cả các thao tác của đặc điểm đã chọn và triển khai lệnh gọi lại khi nhấp vào trong 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
    //          }
    //      )
    //  }
}

Huỷ chú thích Bước 4.1.4 trong tệp StarterView.kt để hiển thị tất cả các giá trị của đặc điểm đã chọn và triển khai lệnh gọi lại khi nhấp vào trong 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 -> {
              ...
      }
   }

Huỷ đánh dấu là bình luận Bước 4.1.5 trong tệp StarterView.kt để lưu trữ tất cả các biến ViewModel của điều kiện khởi động vào ViewModel của điều kiện khởi động tự động hoá bản nháp (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)) }

Khi chạy ứng dụng và chọn một quy trình tự động hoá và quy trình khởi động mới, bạn sẽ thấy một khung hiển thị như sau:

79beb3b581ec71ec.png

Ứng dụng mẫu chỉ hỗ trợ các chương trình khởi động dựa trên đặc điểm của thiết bị.

Thiết lập hành động

Hành động tự động hoá phản ánh mục đích chính của một quy trình tự động hoá, cách quy trình đó tác động đến sự thay đổi trong thế giới thực. Trong Ứng dụng mẫu, chúng ta sẽ ghi lại các thao tác tự động hoá bằng cách sử dụng lớp ActionViewModel và hiển thị chế độ xem trình chỉnh sửa bằng cách sử dụng lớp ActionView.

Ứng dụng mẫu sử dụng các thực thể Home API sau đây để xác định các nút thao tác tự động hoá:

  • Thiết bị
  • Đặc điểm
  • Lệnh
  • Giá trị (Không bắt buộc)

Mỗi thao tác lệnh thiết bị đều sử dụng một lệnh, nhưng một số lệnh cũng sẽ yêu cầu một giá trị tham số được liên kết với lệnh đó, chẳng hạn như MoveToLevel() và tỷ lệ phần trăm mục tiêu.

Bạn có thể chọn thiết bị và đặc điểm trong số các đối tượng do Devices API trả về.

Ứng dụng này xác định một danh sách các lệnh được xác định trước:

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

Ứng dụng theo dõi các thao tác được hỗ trợ cho từng đặc điểm được hỗ trợ:

 // 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,
)

Đối với các lệnh nhận một hoặc nhiều tham số, cũng có một biến:

   val valueLevel: MutableStateFlow<UByte?>

API này hiển thị một nhóm các phần tử khung hiển thị mà người dùng có thể dùng để chọn các trường bắt buộc.

Huỷ đánh dấu bước 4.2.1 trong tệp ActionView.kt để kết xuất tất cả các thiết bị thao tác và triển khai lệnh gọi lại khi nhấp vào trong DropdownMenu để đặt 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
//         }
//     )
// }
}

Huỷ nhận xét Bước 4.2.2 trong tệp ActionView.kt để hiển thị tất cả các đặc điểm của actionDeviceVM và triển khai lệnh gọi lại khi nhấp vào trong DropdownMenu để đặt actionTrait, biểu thị đặc điểm mà lệnh thuộc về.

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

Huỷ đánh dấu bước 4.2.3 trong tệp ActionView.kt để hiển thị tất cả các thao tác có sẵn của actionTrait và triển khai lệnh gọi lại khi nhấp vào trong DropdownMenu để đặt actionAction, đại diện cho thao tác tự động hoá đã chọn.

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

Huỷ nhận xét Bước 4.2.4 trong tệp ActionView.kt để hiển thị các giá trị có sẵn của thao tác đặc điểm (lệnh) và lưu trữ giá trị vào actionValueLevel trong lệnh gọi lại thay đổi giá trị:

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

Huỷ đánh dấu bước 4.2.5 trong tệp ActionView.kt để lưu trữ tất cả các biến ViewModel của hành động trong ViewModel của hành động tự động hoá bản nháp (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)) }

Khi chạy ứng dụng và chọn một quy trình tự động hoá và hành động mới, bạn sẽ thấy một khung hiển thị như sau:

6efa3c7cafd3e595.png

Chúng tôi chỉ hỗ trợ các thao tác dựa trên đặc điểm của thiết bị trong Ứng dụng mẫu.

Kết xuất quy trình tự động hoá nháp

Khi DraftViewModel hoàn tất, HomeAppView.kt có thể kết xuất DraftViewModel:

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

Trong DraftView.kt:

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

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

Giờ đây, bạn đã học được cách tạo các thành phần khởi động và hành động, bạn đã sẵn sàng tạo bản nháp tự động hoá và gửi bản nháp đó đến Automation API. API này có một hàm createAutomation() nhận bản nháp của quy trình tự động hoá làm đối số và trả về một phiên bản quy trình tự động hoá mới.

Quá trình chuẩn bị bản nháp tự động hoá diễn ra trong lớp DraftViewModel trong Ứng dụng mẫu. Hãy xem hàm getDraftAutomation() để tìm hiểu thêm về cách chúng tôi cấu trúc bản nháp tự động hoá bằng cách sử dụng các biến khởi động và biến hành động trong phần trước.

Huỷ đánh dấu là bình luận Bước 4.4.1 trong tệp DraftViewModel.kt để tạo các biểu thức "select" cần thiết để tạo biểu đồ tự động hoá khi đặc điểm khởi động là 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()
     ...
}

Huỷ đánh dấu bước 4.4.2 trong tệp DraftViewModel.kt để tạo các biểu thức song song cần thiết để tạo biểu đồ tự động hoá khi đặc điểm hành động đã chọn là LevelControl và hành động đã chọn là 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) }
          ...
}

Bước cuối cùng để hoàn tất quy trình tự động hoá là triển khai hàm getDraftAutomation để tạo AutomationDraft.

Huỷ chú thích Bước 4.4.3 trong tệp HomeAppViewModel.kt để tạo quy trình tự động hoá bằng cách gọi API Home và xử lý các trường hợp ngoại lệ:

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

Bây giờ, hãy chạy ứng dụng và xem các thay đổi trên thiết bị của bạn!

Sau khi chọn điều kiện khởi động và hành động, bạn đã sẵn sàng tạo quy trình tự động hoá:

ec551405f8b07b8e.png

Đảm bảo rằng bạn đặt tên cho quy trình tự động hoá một cách riêng biệt, sau đó nhấn vào nút Create Automation (Tạo quy trình tự động hoá). Nút này sẽ gọi các API và đưa bạn quay lại chế độ xem danh sách quy trình tự động hoá cùng với quy trình tự động hoá của bạn:

8eebc32cd3755618.png

Nhấn vào quy trình tự động hoá mà bạn vừa tạo và xem cách các API trả về quy trình đó.

931dba7c325d6ef7.png

Xin lưu ý rằng API trả về một giá trị cho biết liệu một quy trình tự động hoá có hợp lệ và hiện đang hoạt động hay không. Bạn có thể tạo những quy trình tự động hoá không vượt qua được quy trình xác thực khi được phân tích cú pháp ở phía máy chủ. Nếu một quy trình tự động hoá phân tích cú pháp không xác thực được, isValid sẽ được đặt thành false, cho biết quy trình tự động hoá không hợp lệ và không hoạt động. Nếu quy trình tự động hoá của bạn không hợp lệ, hãy kiểm tra trường automation.validationIssues để biết thông tin chi tiết.

Đảm bảo rằng quy trình tự động hoá của bạn được thiết lập là hợp lệ và đang hoạt động, sau đó bạn có thể dùng thử quy trình tự động hoá.

Dùng thử quy trình tự động hoá

Bạn có thể thực thi quy trình tự động hoá theo hai cách:

  1. Với một sự kiện kích hoạt. Nếu các điều kiện trùng khớp, thì điều này sẽ kích hoạt hành động mà bạn đặt trong quy trình tự động hoá.
  2. Bằng lệnh gọi API thực thi thủ công.

Nếu một bản nháp tự động hoá có manualStarter() được xác định trong khối DSL của bản nháp tự động hoá, thì công cụ tự động hoá sẽ hỗ trợ việc thực thi thủ công cho hoạt động tự động hoá đó. Điều này đã có trong các ví dụ về mã trong Ứng dụng mẫu.

Vì bạn vẫn đang ở màn hình chế độ xem tự động hoá trên thiết bị di động, hãy nhấn vào nút Thực thi thủ công. Thao tác này sẽ gọi automation.execute(), chạy lệnh thao tác trên thiết bị bạn đã chọn khi thiết lập quy trình tự động hoá.

Sau khi xác thực lệnh thao tác thông qua quá trình thực thi thủ công bằng API, giờ là lúc bạn xem lệnh đó có thực thi bằng trình khởi chạy mà bạn đã xác định hay không.

Chuyển đến thẻ Thiết bị, chọn thiết bị hành động và đặc điểm, rồi đặt thành một giá trị khác (ví dụ: đặt light2's LevelControl (độ sáng) thành 50%, như minh hoạ trong ảnh chụp màn hình sau:

d0357ec71325d1a8.png

Bây giờ, chúng ta sẽ thử kích hoạt quy trình tự động hoá bằng thiết bị khởi động. Chọn thiết bị khởi động mà bạn đã chọn khi tạo quy trình tự động hoá. Bật/tắt đặc điểm mà bạn đã chọn (ví dụ: đặt OnOff của starter outlet1 thành On):

230c78cd71c95564.png

Bạn sẽ thấy rằng thao tác này cũng thực thi quy trình tự động hoá và đặt đặc điểm LevelControl của thiết bị hành động light2 thành giá trị ban đầu là 100%:

1f00292128bde1c2.png

Xin chúc mừng! Bạn đã sử dụng thành công Home API để tạo các quy trình tự động hoá!

Để tìm hiểu thêm về Automation API, hãy xem Android Automation API.

5. Khám phá các khả năng

Home API bao gồm một API chuyên dụng có tên là Discovery API. Nhà phát triển có thể dùng API này để truy vấn những đặc điểm có khả năng tự động hoá được hỗ trợ trong một thiết bị nhất định. Ứng dụng mẫu cung cấp một ví dụ về cách bạn có thể sử dụng API này để khám phá những lệnh có sẵn.

Khám phá các lệnh

Trong phần này, chúng ta sẽ thảo luận về cách khám phá CommandCandidates được hỗ trợ và cách tạo một quy trình tự động hoá dựa trên các nút đề xuất đã khám phá.

Trong Ứng dụng mẫu, chúng ta gọi device.candidates() để lấy danh sách các ứng cử viên, trong đó có thể bao gồm các thực thể CommandCandidate, EventCandidate hoặc TraitAttributesCandidate.

Chuyển đến tệp HomeAppViewModel.kt rồi bỏ chú thích Bước 5.1.1 để truy xuất danh sách đề xuất và lọc theo loại 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)
}

Xem cách API lọc CommandCandidate. Các đề xuất do API trả về thuộc nhiều loại. Ứng dụng mẫu hỗ trợ CommandCandidate. Huỷ chú thích Bước 5.1.2 trong commandMap được xác định trong ActionViewModel.kt để đặt các đặc điểm được hỗ trợ sau:

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

Giờ đây, chúng ta có thể gọi Discovery API và lọc những kết quả mà chúng ta hỗ trợ trong Ứng dụng mẫu. Chúng ta sẽ thảo luận về cách tích hợp API này vào trình chỉnh sửa.

8a2f0e8940f7056a.png

Để tìm hiểu thêm về Discovery API, hãy xem bài viết Tận dụng tính năng khám phá thiết bị trên Android.

Tích hợp trình chỉnh sửa

Cách phổ biến nhất để sử dụng các thao tác được phát hiện là trình bày các thao tác đó cho người dùng cuối chọn. Ngay trước khi người dùng chọn các trường tự động hoá bản nháp, chúng ta có thể cho họ xem danh sách các hành động đã phát hiện và tuỳ thuộc vào giá trị mà họ chọn, chúng ta có thể điền sẵn nút hành động trong bản nháp tự động hoá.

Tệp CandidatesView.kt chứa lớp khung hiển thị hiển thị các đề xuất đã phát hiện. Huỷ đánh dấu Bước 5.2.1 để bật hàm .clickable{} của CandidateListItem. Hàm này đặt homeAppVM.selectedDraftVM thành 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)) }
        }) {
            ...
        }
    }
}

Tương tự như Bước 4.3 trong HomeAppView.kt, khi selectedDraftVM được đặt, nó sẽ hiển thị 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)
  }
   ...
}

Hãy thử lại bằng cách nhấn vào light2 – MOVE_TO_LEVEL (di chuyển đến cấp độ), như minh hoạ ở phần trước. Thao tác này sẽ nhắc bạn tạo một quy trình tự động hoá mới dựa trên lệnh của ứng viên:

15e67763a9241000.png

Giờ đây, khi đã quen với cách tạo quy trình tự động hoá trong Ứng dụng mẫu, bạn có thể tích hợp các quy trình tự động hoá vào ứng dụng của mình.

6. Ví dụ về tính năng tự động hoá nâng cao

Trước khi kết thúc, chúng ta sẽ thảo luận về một số ví dụ khác về DSL tự động hoá. Những ví dụ này minh hoạ một số chức năng nâng cao mà bạn có thể đạt được bằng các API này.

Thời gian trong ngày làm điểm bắt đầu

Ngoài các đặc điểm của thiết bị, API Google Home còn cung cấp các đặc điểm dựa trên cấu trúc, chẳng hạn như Time. Bạn có thể tạo một quy trình tự động hoá có điều kiện kích hoạt dựa trên thời gian, chẳng hạn như:

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

Thông báo của Trợ lý dưới dạng hành động

Đặc điểm AssistantBroadcast có thể là đặc điểm cấp thiết bị trong SpeakerDevice (nếu loa hỗ trợ) hoặc là đặc điểm cấp cấu trúc (vì loa Google và thiết bị di động Android có thể phát thông báo của Trợ lý). Ví dụ:

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

Sử dụng DelayForsuppressFor

Automation API cũng cung cấp các toán tử nâng cao như delayFor (để trì hoãn các lệnh) và suppressFor (có thể ngăn một quy trình tự động hoá bị kích hoạt bởi cùng một sự kiện trong một khoảng thời gian nhất định). Dưới đây là một số ví dụ sử dụng các toán tử này:

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

Sử dụng AreaPresenceState trong một thành phần khởi động

AreaPresenceState là một đặc điểm ở cấp cấu trúc, giúp phát hiện xem có người ở nhà hay không.

Ví dụ: ví dụ sau đây minh hoạ cách tự động khoá cửa khi có người ở nhà sau 10 giờ tối:

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

Giờ đây, khi đã nắm rõ các tính năng tự động hoá nâng cao này, hãy bắt tay vào tạo những ứng dụng tuyệt vời!

7. Xin chúc mừng!

Xin chúc mừng! Bạn đã hoàn thành phần thứ hai của việc phát triển một Ứng dụng Android bằng cách sử dụng Google Home API. Trong suốt lớp học lập trình này, bạn đã khám phá các API Tự động hoá và Khám phá.

Chúng tôi hy vọng bạn sẽ thích thú khi xây dựng các ứng dụng có thể điều khiển thiết bị một cách sáng tạo trong hệ sinh thái Google Home và xây dựng các tình huống tự động hoá thú vị bằng cách sử dụng Home API!

Các bước tiếp theo

  • Hãy đọc phần Khắc phục sự cố để tìm hiểu cách gỡ lỗi ứng dụng một cách hiệu quả và khắc phục các vấn đề liên quan đến Home API.
  • Bạn có thể liên hệ với chúng tôi để đưa ra đề xuất hoặc báo cáo mọi vấn đề thông qua Công cụ theo dõi lỗi, chủ đề hỗ trợ Nhà thông minh.