کنترل دستگاه‌ها در Android

بررسی اینکه آیا مشخصه‌ای از فرمان پشتیبانی می‌کند یا نه

پشتیبانی را می‌توان برای فرمان مشخصه نیز بررسی کرد. همچنین از تابع سطح مشخصه supports برای بررسی اینکه آیا فرمان برای دستگاه خاصی پشتیبانی می‌شود یا نه استفاده کنید.

برای مثال، برای بررسی پشتیبانی دستگاه از فرمان toggle ویژگی «روشن/خاموش»:

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

ارسال فرمان به دستگاه

ارسال فرمان شبیه به خواندن مشخصه وضعیت از یک ویژگی است. برای روشن یا خاموش کردن دستگاه، از فرمان «روشن/خاموش کردن» OnOff ویژگی استفاده کنید که در مدل داده بوم‌سازگان Google Home به‌عنوان toggle() تعریف شده است. این روش onOff را به false تغییر می‌دهد اگر true باشد، یا به true تغییر می‌دهد اگر false باشد:

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

همه دستورات مشخصه suspend کارکرد هستند و فقط زمانی تکمیل می‌شوند که پاسخی ازسوی «میانای برنامه‌سازی کاربردی» برگردانده شود (مثلاً تأیید شود که وضعیت دستگاه تغییر کرده است). اگر مشکلی در جریان اجرای دستورات شناسایی شود، ممکن است دستورات استثنایی برگردانند. به‌عنوان توسعه‌دهنده، باید از بلوک try-catch برای مدیریت صحیح این استثناها استفاده کنید و در مواردی که خطاها قابل اقدام هستند، اطلاعات دقیق را به کاربران ارائه دهید. استثناهای مدیریت‌نشده زمان اجرای برنامه را متوقف می‌کند و می‌تواند منجر به خرابی در برنامه شما شود.

یا از فرمان‌های off() یا on() برای تنظیم صریح وضعیت استفاده کنید:

onOffTrait.off()
onOffTrait.on()

پس‌از ارسال فرمان تغییر وضعیت، پس‌از تکمیل می‌توانید وضعیت را همان‌طور که در خواندن وضعیت دستگاه توضیح داده شده است بخوانید تا آن را در برنامه‌تان مدیریت کنید. یا از جاری‌سازی‌ها همان‌طور که در مشاهده وضعیت توضیح داده شده است استفاده کنید که روش ترجیحی است.

ارسال فرمان با پارامترها

برخی‌از فرمان‌ها ممکن است از پارامترهایی مثل پارامترهای موجود در OnOff یا LevelControl ویژگی‌ها استفاده کنند:

offWithEffect

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)

moveToLevel

// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

برخی‌از فرمان‌ها دارای متغیرهای مستقل اختیاری هستند که پس‌از متغیرهای مستقل الزامی می‌آیند.

برای مثال، فرمان step برای FanControl قابلیت دو آرگومان اختیاری دارد:

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 }

بررسی اینکه آیا مشخصه‌ای از مشخصه‌ای پشتیبانی می‌کند یا نه

برخی‌از دستگاه‌ها ممکن است از ویژگی Matter پشتیبانی کنند، اما از صفت خاصی پشتیبانی نکنند. برای مثال، دستگاه Cloud-to-cloud که به Matter نگاشت شده است ممکن است از همه مشخصه‌های Matter پشتیبانی نکند. برای رسیدگی به مواردی ازاین‌دست، از تابع سطح مشخصه supports و شمارش Attribute مشخصه برای بررسی اینکه آیا صفت برای دستگاه خاصی پشتیبانی می‌شود یا نه استفاده کنید.

برای مثال، برای بررسی پشتیبانی دستگاه از مشخصه روشن/خاموش onOff ویژگی:

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

برخی‌از مشخصه‌ها در مشخصات Matter یا طرحواره Cloud-to-cloud smart home تهی‌پذیر هستند. برای این مشخصه‌ها، می‌توانید بااستفاده از isNullable علاوه‌بر supports، تعیین کنید که آیا تهی برگشت‌داده‌شده توسط مشخصه به‌دلیل گزارش نکردن آن مقدار توسط دستگاه است یا اینکه مقدار مشخصه واقعاً null است:

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

به‌روزرسانی مشخصه‌های ویژگی

اگر می‌خواهید مقدار مشخصه‌ای را تغییر دهید و هیچ‌یک از دستورات مشخصه این کار را انجام نمی‌دهد، ممکن است مشخصه از تنظیم صریح مقدارش پشتیبانی کند.

اینکه مقدار یک مشخصه قابل تغییر باشد یا نه به دو عامل بستگی دارد:

  • آیا مشخصه نوشتنی است؟
  • آیا مقدار مشخصه می‌تواند به‌عنوان اثر جانبی ارسال فرمان ویژگی تغییر کند؟

اسناد مرجع برای ویژگی‌ها و مشخصه‌های آن‌ها این اطلاعات را ارائه می‌دهد.

بنابراین، ترکیب‌های ویژگی‌هایی که تعیین می‌کنند مقدار مشخصه چگونه ممکن است تغییر کند عبارت‌اند از:

  • فقط خواندنی و تحت تأثیر فرمان‌های دیگر قرار نمی‌گیرد. این یعنی مقدار ویژگی تغییر نمی‌کند. برای مثال، مشخصه currentPosition از Switch مشخصه.

  • فقط خواندنی و تحت تأثیر فرمان‌های دیگر. این یعنی تنها راه تغییر مقدار مشخصه، ارسال فرمان است. برای مثال، مشخصه currentLevel از LevelControl Matter مشخصه فقط خواندنی است، اما مقدار آن می‌تواند با فرمان‌هایی مثل moveToLevel تغییر کند.

  • قابل‌نوشتن و تحت تأثیر فرمان‌های دیگر قرار نمی‌گیرد. این یعنی می‌توانید بااستفاده از تابع update مشخصه، مقدار مشخصه را مستقیماً تغییر دهید، اما هیچ دستوری وجود ندارد که بر مقدار مشخصه تأثیر بگذارد. برای مثال، مشخصه WrongCodeEntryLimit از DoorLock مشخصه.

  • قابل‌نوشتن و تحت‌تأثیر فرمان‌های دیگر. این یعنی می‌توانید بااستفاده از تابع update مشخصه، مقدار مشخصه را مستقیماً تغییر دهید و مقدار مشخصه می‌تواند درنتیجه ارسال فرمان تغییر کند. برای مثال، مشخصه speedSetting عنصر FanControlTrait را می‌توان مستقیماً نوشت، اما بااستفاده از فرمان step نیز قابل‌تغییر است.

مثالی از استفاده از تابع به‌روزرسانی برای تغییر مقدار یک مشخصه

این مثال نشان می‌دهد که چگونه مقدار DoorLockTrait.WrongCodeEntryLimit مشخصه را به‌طور صریح تنظیم کنید.

برای تنظیم مقدار مشخصه، تابع update مشخصه را فراخوانی کنید و تابع جهش‌دهنده‌ای را که مقدار جدید را تنظیم می‌کند به آن ارسال کنید. بهتر است ابتدا تأیید کنید که مشخصه از مشخصه پشتیبانی می‌کند.

برای مثال:

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

ارسال چندین فرمان به‌طور هم‌زمان

«میانای برنامه‌سازی کاربردی دسته‌ای» به مشتری امکان می‌دهد چندین فرمان دستگاه «میانای برنامه‌سازی کاربردی خانه» را در یک بار داده ارسال کند. فرمان‌ها در یک بار واحد دسته‌بندی می‌شوند و به‌صورت موازی اجرا می‌شوند، مشابه با نحوه ساختن خودکارسازی «میانای برنامه‌سازی کاربردی Home» بااستفاده از گره موازی، مانند مثال باز کردن کرکره‌ها قبل‌از طلوع آفتاب. بااین‌حال، «میانای برنامه‌سازی کاربردی دسته‌ای» امکان رفتارهای پیچیده‌تر و پیشرفته‌تری نسبت به «میانای برنامه‌سازی کاربردی خودکارسازی» فراهم می‌کند، مثلاً امکان انتخاب پویا دستگاه‌ها در زمان اجرا براساس هر معیاری.

فرمان‌های موجود در یک دسته می‌توانند چندین ویژگی را در چندین دستگاه، در چندین اتاق، در چندین ساختمان هدف‌یابی کنند.

ارسال دسته‌ای فرمان‌ها به دستگاه‌ها امکان می‌دهد کنش‌ها را به‌طور هم‌زمان انجام دهند، که وقتی فرمان‌ها به‌صورت متوالی در درخواست‌های جداگانه ارسال می‌شوند واقعاً امکان‌پذیر نیست. عملکرد به‌دست‌آمده بااستفاده از دستورات دسته‌ای به توسعه‌دهنده امکان می‌دهد وضعیت گروهی از دستگاه‌ها را به‌گونه‌ای تنظیم کند که با وضعیت تجمیعی ازپیش تعیین‌شده مطابقت داشته باشد.

استفاده از Batching API

سه مرحله اساسی برای فراخوانی فرمان‌ها ازطریق «میانای برنامه‌سازی کاربردی دسته‌ای» وجود دارد:

  1. روش Home.sendBatchedCommands() را فراخوانی کنید.
  2. در بدنه بلوک sendBatchedCommands()، فرمان‌هایی را که باید در دسته‌ای قرار بگیرند مشخص کنید.
  3. نتایج دستورات ارسال‌شده را بررسی کنید تا ببینید موفقیت‌آمیز بوده‌اند یا ناموفق.

ارسال فرمان‌های دسته‌ای

روش Home.sendBatchedCommands() را فراخوانی کنید. در پشت صحنه، این روش عبارت لامبدا را در یک زمینه دسته‌ای ویژه تنظیم می‌کند.

home.sendBatchedCommands() {

فرمان‌های دسته‌ای را مشخص کنید

در بدنه بلوک sendBatchedCommands()، دستورات دسته‌ای را اضافه کنید. فرمان‌های دسته‌ای نسخه‌های «سایه» فرمان‌های موجود در «میانای برنامه‌سازی کاربردی دستگاه» هستند که می‌توانند در زمینه دسته‌ای استفاده شوند و با پسوند Batchable نام‌گذاری می‌شوند. برای مثال، فرمان LevelControl ویژگی moveToLevel() همتایی به نام moveToLevelBatchable() دارد.

مثال:

  val response1 = add(command1)

  val response2 = add(command2)

وقتی همه فرمان‌ها به بافت دسته‌ای اضافه شد و اجرا از بافت خارج شد، دسته به‌طور خودکار ارسال می‌شود.

پاسخ‌ها در DeferredResponse<T> شیء ضبط می‌شوند.

نمونه‌های DeferredResponse<T> را می‌توان در شیئی از هر نوعی، مثل Collection، یا کلاس داده‌ای که تعریف می‌کنید جمع‌آوری کرد. هر نوع شیئی را که برای سرهم کردن پاسخ‌ها انتخاب کنید، همان چیزی است که sendBatchedCommands() برمی‌گرداند. برای مثال، بافت دسته‌ای می‌تواند دو نمونه DeferredResponse را در Pair برگرداند:

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

یا اینکه بافت دسته‌ای می‌تواند نمونه‌های DeferredResponse را در کلاس داده سفارشی برگرداند:

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

بررسی هر پاسخ

خارج از بلوک sendBatchedCommands()، پاسخ‌ها را بررسی کنید تا مشخص شود فرمان مربوطه موفق بوده است یا ناموفق. این کار با فراخوانی DeferredResponse.getOrThrow() انجام می‌شود که یا: - نتیجه دستور اجراشده را برمی‌گرداند، - یا، اگر محدوده دسته‌ای تکمیل نشده باشد یا دستور ناموفق باشد، خطایی ایجاد می‌کند.

فقط باید نتایج خارج از حوزه sendBatchedCommands() لامبدا را بررسی کنید.

مثال

فرض کنید می‌خواهید برنامه‌ای بسازید که از «میانای برنامه‌سازی کاربردی دسته‌ای» برای راه‌اندازی صحنه «شب‌به‌خیر» استفاده کند و همه دستگاه‌های خانه را برای شب، زمانی که همه خواب هستند، پیکربندی کند. این برنامه باید چراغ‌ها را خاموش کند و درهای جلو و پشت را قفل کند.

در اینجا یک روش برای نزدیک شدن به تکلیف آورده شده است:

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