کنترل دستگاه ها در اندروید

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

همچنین می‌توان پشتیبانی از یک دستور trait را بررسی کرد. همچنین از تابع trait-level 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")
}

ارسال دستور به دستگاه

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

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

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

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

onOffTrait.off()
onOffTrait.on()

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

ارسال دستور به همراه پارامترها

برخی از دستورات ممکن است از پارامترهایی مانند پارامترهای موجود در صفات OnOff یا 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(),
)

برخی از دستورات آرگومان‌های اختیاری دارند که پس از آرگومان‌های الزامی می‌آیند.

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

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 یا طرح smart home Cloud-to-cloud قابل null شدن هستند. برای این ویژگی‌ها، می‌توانید با استفاده از isNullable علاوه بر supports ، تعیین کنید که آیا null برگردانده شده توسط ویژگی به دلیل عدم گزارش آن مقدار توسط دستگاه است یا اینکه مقدار ویژگی در واقع 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 مربوط به trait، مقدار attribute را مستقیماً تغییر دهید، اما هیچ دستوری وجود ندارد که بر مقدار attribute تأثیر بگذارد. به عنوان مثال، ویژگی WrongCodeEntryLimit از trait DoorLock .

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

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

این مثال نحوه‌ی تنظیم صریح مقدار ویژگی DoorLockTrait.WrongCodeEntryLimit را نشان می‌دهد.

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

برای مثال:

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

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

API دسته بندی به کلاینت اجازه می‌دهد تا چندین دستور دستگاه Home API را در یک payload واحد ارسال کند. این دستورات در یک payload واحد دسته بندی شده و به صورت موازی اجرا می‌شوند، مشابه نحوه ساخت اتوماسیون Home API با استفاده از گره موازی ، مانند مثال Open blinds before sunrise . با این حال، API دسته بندی رفتارهای پیچیده‌تر و تخصصی‌تری نسبت به API اتوماسیون، مانند توانایی انتخاب پویای دستگاه‌ها در زمان اجرا بر اساس هر معیاری را فراهم می‌کند.

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

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

از API دسته بندی استفاده کنید

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

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

متد sendBatchedCommands() را فراخوانی کنید.

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

home.sendBatchedCommands() {

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

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

مثال:

  val response1 = add(command1)

  val response2 = add(command2)

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

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

نمونه‌های DeferredResponse<T> را می‌توان در یک شیء از هر نوع، مانند یک Collection یا یک کلاس داده که شما تعریف می‌کنید، جمع‌آوری کرد. هر نوع شیء که برای جمع‌آوری پاسخ‌ها انتخاب کنید، همان چیزی است که توسط sendBatchedCommands() برگردانده می‌شود. برای مثال، زمینه batch می‌تواند دو نمونه 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() را بررسی کنید.

مثال

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

در اینجا یک راه برای انجام این کار آورده شده است:

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