Kiểm soát thiết bị trên Android

Kiểm tra xem một đặc điểm có hỗ trợ lệnh hay không

Bạn cũng có thể kiểm tra tính năng hỗ trợ cho một lệnh thuộc tính. Ngoài ra, hãy sử dụng hàm supports cấp đặc điểm để kiểm tra xem một lệnh có được hỗ trợ cho một thiết bị cụ thể hay không.

Ví dụ: để kiểm tra xem thiết bị có hỗ trợ lệnh toggle của thuộc tính Bật/Tắt hay không:

// Check if the OnOff trait supports the toggle command.
if (onOffTrait.supports(OnOff.Command.Toggle)) {
  println("onOffTrait supports toggle command")
} else {
  println("onOffTrait does not support stateful toggle command")
}

Gửi lệnh đến một thiết bị

Việc gửi lệnh tương tự như việc đọc thuộc tính trạng thái từ một đặc điểm. Để bật hoặc tắt thiết bị, hãy sử dụng lệnh Bật/tắt của thuộc tính OnOff. Lệnh này được xác định trong mô hình dữ liệu hệ sinh thái Google Home là toggle(). Phương thức này thay đổi onOff thành false nếu là true hoặc thành true nếu là false:

// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}

Tất cả lệnh thuộc tính đều là hàm suspend và chỉ hoàn tất khi API trả về phản hồi (chẳng hạn như xác nhận trạng thái thiết bị đã thay đổi). Các lệnh có thể trả về một ngoại lệ nếu phát hiện thấy vấn đề với quy trình thực thi. Là nhà phát triển, bạn nên sử dụng khối try-catch để xử lý đúng cách các ngoại lệ này và hiển thị thông tin chi tiết cho người dùng trong trường hợp có thể xử lý lỗi. Các ngoại lệ chưa được xử lý sẽ dừng thời gian chạy ứng dụng và có thể dẫn đến sự cố trong ứng dụng.

Ngoài ra, hãy sử dụng lệnh off() hoặc on() để đặt trạng thái một cách rõ ràng:

onOffTrait.off()
onOffTrait.on()

Sau khi gửi lệnh thay đổi trạng thái, khi lệnh này hoàn tất, bạn có thể đọc trạng thái như mô tả trong phần Đọc trạng thái thiết bị để xử lý trạng thái đó trong ứng dụng. Ngoài ra, hãy sử dụng các luồng như mô tả trong phần Quan sát trạng thái. Đây là phương thức ưu tiên.

Gửi lệnh có tham số

Một số lệnh có thể sử dụng các tham số, chẳng hạn như các tham số trên các đặc điểm OnOff hoặc LevelControl:

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)
// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

Một số lệnh có đối số tuỳ chọn, nằm sau đối số bắt buộc.

Ví dụ: lệnh step cho đặc điểm FanControl có hai đối số không bắt buộc:

val fanControlTraitFlow: Flow<FanControl?> =
  device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged()

val fanControl = fanControlTraitFlow.firstOrNull()

// Calling a command with optional parameters not set.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase)

// Calling a command with optional parameters.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }

Kiểm tra xem một đặc điểm có hỗ trợ thuộc tính hay không

Một số thiết bị có thể hỗ trợ một đặc điểm Matter, nhưng không hỗ trợ một thuộc tính cụ thể. Ví dụ: thiết bị Cloud-to-cloud được ánh xạ đến Matter có thể không hỗ trợ mọi thuộc tính Matter. Để xử lý các trường hợp như vậy, hãy sử dụng hàm supports cấp đặc điểm và enum Attribute của đặc điểm để kiểm tra xem thuộc tính có được hỗ trợ cho một thiết bị cụ thể hay không.

Ví dụ: để kiểm tra xem thiết bị có hỗ trợ thuộc tính onOff của thuộc tính Bật/Tắt hay không:

// Check if the OnOff trait supports the onOff attribute.
if (onOffTrait.supports(OnOff.Attribute.onOff)) {
  println("onOffTrait supports onOff state")
} else {
  println("onOffTrait is for a command only device!")
}

Một số thuộc tính có thể nhận giá trị rỗng trong thông số kỹ thuật Matter hoặc giản đồ Cloud-to-cloud smart home. Đối với các thuộc tính này, bạn có thể xác định xem giá trị null do thuộc tính trả về có phải là do thiết bị không báo cáo giá trị đó hay không, hoặc giá trị của thuộc tính có thực sự là null hay không, bằng cách sử dụng isNullable ngoài supports:

// Check if a nullable attribute is set or is not supported.
if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) {
  // The device supports startupOnOff, it is safe to expect this value in the trait.
  if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) {
    // This value is nullable and set to null. Check the specification as to
    // what null in this case means
    println("onOffTrait supports startUpOnOff and it is null")
  } else {
    // This value is nullable and set to a value.
    println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}")
  }
} else {
  println("onOffTrait does not support startUpOnOff!")
}

Cập nhật thuộc tính đặc điểm

Nếu bạn muốn thay đổi giá trị của một thuộc tính nhất định và không có lệnh nào của thuộc tính thực hiện việc này, thì thuộc tính đó có thể hỗ trợ việc đặt giá trị một cách rõ ràng.

Việc có thể thay đổi giá trị của một thuộc tính hay không phụ thuộc vào hai yếu tố:

  • Thuộc tính có thể ghi được không?
  • Giá trị của thuộc tính có thể thay đổi do tác dụng phụ của việc gửi lệnh đặc điểm không?

Tài liệu tham khảo về các đặc điểm và thuộc tính của các đặc điểm đó cung cấp thông tin này.

Do đó, các tổ hợp thuộc tính chỉ định cách thay đổi giá trị của một thuộc tính là:

  • Chỉ có thể đọc và không bị ảnh hưởng bởi các lệnh khác. Điều này có nghĩa là giá trị của thuộc tính không thay đổi. Ví dụ: thuộc tính currentPosition của đặc điểm Switch.

  • Chỉ có thể đọc và bị ảnh hưởng bởi các lệnh khác. Điều này có nghĩa là cách duy nhất để giá trị của thuộc tính có thể thay đổi là do việc gửi lệnh. Ví dụ: thuộc tính currentLevel của đặc điểm LevelControl Matter chỉ có thể đọc, nhưng giá trị của thuộc tính này có thể được thay đổi bằng các lệnh như moveToLevel.

  • Có thể ghi và không bị ảnh hưởng bởi các lệnh khác. Điều này có nghĩa là bạn có thể trực tiếp thay đổi giá trị của thuộc tính bằng cách sử dụng hàm update của thuộc tính, nhưng không có lệnh nào sẽ ảnh hưởng đến giá trị của thuộc tính. Ví dụ: thuộc tính WrongCodeEntryLimit của đặc điểm DoorLock.

  • Có thể ghi và bị ảnh hưởng bởi các lệnh khác. Điều này có nghĩa là bạn có thể trực tiếp thay đổi giá trị của thuộc tính bằng cách sử dụng hàm update của thuộc tính và giá trị của thuộc tính có thể thay đổi do việc gửi lệnh. Ví dụ: bạn có thể ghi vào nhưng cũng có thể cập nhật thuộc tính occupiedCoolingSetpoint của đặc điểm Thermostat bằng lệnh setpointRaiseLower.

Ví dụ về cách sử dụng hàm cập nhật để thay đổi giá trị của một thuộc tính

Ví dụ này cho thấy cách đặt giá trị của thuộc tính DoorLockTrait.WrongCodeEntryLimit một cách rõ ràng.

Để đặt giá trị thuộc tính, hãy gọi hàm update của đặc điểm và truyền vào đó một hàm đối tượng sửa đổi để đặt giá trị mới. Trước tiên, bạn nên xác minh rằng đặc điểm này hỗ trợ một thuộc tính.

Ví dụ:

    var doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) }

    val traitFlow: Flow<DoorLock?> =
      doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged()

    val doorLockTrait: DoorLock = traitFlow.first()!!

    if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) {
      val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) }
    }

Gửi nhiều lệnh cùng lúc

API tạo lô cho phép ứng dụng gửi nhiều lệnh thiết bị API Home trong một tải trọng duy nhất. Các lệnh được phân thành một tải trọng duy nhất và được thực thi song song, tương tự như cách tạo một API Home tự động bằng cách sử dụng nút song song, chẳng hạn như ví dụ Mở rèm trước khi mặt trời mọc. Tuy nhiên, API xử lý hàng loạt cho phép các hành vi phức tạp và tinh vi hơn so với API Tự động hoá, chẳng hạn như khả năng tự động chọn thiết bị trong thời gian chạy theo bất kỳ tiêu chí nào.

Các lệnh trong một lô có thể nhắm đến nhiều đặc điểm trên nhiều thiết bị, trong nhiều phòng, trong nhiều cấu trúc.

Việc gửi lệnh theo lô cho phép các thiết bị thực hiện các thao tác đồng thời, điều này thực sự không thể thực hiện được khi các lệnh được gửi tuần tự theo các yêu cầu riêng biệt. Hành vi đạt được bằng cách sử dụng các lệnh theo lô cho phép nhà phát triển đặt trạng thái của một nhóm thiết bị để khớp với trạng thái tổng hợp được xác định trước.

Sử dụng API xử lý hàng loạt

Có ba bước cơ bản để gọi lệnh thông qua API Tập hợp:

  1. Gọi phương thức Home.sendBatchedCommands().
  2. Trong phần nội dung của khối sendBatchedCommands(), hãy chỉ định các lệnh sẽ được đưa vào lô.
  3. Kiểm tra kết quả của các lệnh đã gửi để xem các lệnh đó có thành công hay không.

Gọi phương thức sendBatchedCommands()

Gọi phương thức Home.sendBatchedCommands(). Trong hậu trường, phương thức này thiết lập một biểu thức lambda trong ngữ cảnh lô đặc biệt.

home.sendBatchedCommands() {

Chỉ định lệnh hàng loạt

Trong phần nội dung của khối sendBatchedCommands(), hãy điền các lệnh có thể thực hiện hàng loạt. Lệnh có thể xử lý hàng loạt là các phiên bản "phiên bản sao" của các lệnh API thiết bị hiện có có thể được sử dụng trong ngữ cảnh hàng loạt và được đặt tên bằng hậu tố Batchable. Ví dụ: lệnh moveToLevel() của đặc điểm LevelControl có một lệnh đối ứng có tên là moveToLevelBatchable().

Ví dụ:

  val response1 = add(command1)

  val response2 = add(command2)

Lệnh hàng loạt sẽ tự động được gửi sau khi tất cả các lệnh đã được thêm vào ngữ cảnh hàng loạt và quá trình thực thi đã rời khỏi ngữ cảnh.

Phản hồi được ghi lại trong các đối tượng DeferredResponse<T>.

Bạn có thể thu thập các thực thể DeferredResponse<T> vào một đối tượng thuộc bất kỳ loại nào, chẳng hạn như Collection hoặc một lớp dữ liệu mà bạn xác định. Mọi loại đối tượng mà bạn chọn để tập hợp các phản hồi đều là những gì sendBatchedCommands() trả về. Ví dụ: ngữ cảnh theo lô có thể trả về hai thực thể DeferredResponse trong Pair:

  val (response1, response2) = homeClient.sendBatchedComamnds {
    val response1 = add(someCommandBatched(...))
    val response2 = add(someOtherCommandBatched(...))
    Pair(response1, response2)
  }

Ngoài ra, ngữ cảnh theo lô có thể trả về các thực thể DeferredResponse trong một lớp dữ liệu tuỳ chỉnh:

  // Custom data class
  data class SpecialResponseHolder(
    val response1: DeferredResponse<String>,
    val response2: DeferredResponse<Int>,
    val other: OtherResponses
  )
  data class OtherResponses(...)

Kiểm tra từng câu trả lời

Bên ngoài khối sendBatchedCommands(), hãy kiểm tra các phản hồi để xác định xem lệnh tương ứng có thành công hay không. Bạn có thể thực hiện việc này bằng cách gọi DeferredResponse.getOrThrow(). Phương thức này: – trả về kết quả của lệnh đã thực thi, – hoặc nếu phạm vi lô chưa hoàn tất hoặc lệnh không thành công, thì sẽ gửi một lỗi.

Bạn chỉ nên kiểm tra kết quả bên ngoài phạm vi lambda sendBatchedCommands().

Ví dụ:

Giả sử bạn muốn xây dựng một ứng dụng sử dụng API xử lý hàng loạt để thiết lập cảnh "chúc ngủ ngon". Cảnh này sẽ định cấu hình tất cả thiết bị trong nhà vào ban đêm, khi mọi người đang ngủ. Ứng dụng này sẽ tắt đèn và khoá cửa trước và sau.

Sau đây là một cách để giải quyết nhiệm vụ này:

val lightDevices: List<OnOffLightDevice>
val doorlockDevices: List<DoorLockDevice>

// Send all the commands
val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
  // For each light device, send a Batchable command to turn it on
  val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
    add(lightDevice.standardTraits.onOff.onBatchable())
  }

  // For each doorlock device, send a Batchable command to lock it
  val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
    add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
  }

  lightResponses + doorLockResponses
}

// Check that all responses were successful
for (response in responses) {
  response.getOrThrow()
}