التحكّم في الأجهزة على Android

التحقّق مما إذا كانت السمة تتيح استخدام أمر

يمكن أيضًا التحقّق من توفّر الدعم لأمر سمة. استخدِم أيضًا الدالة supports على مستوى السمة للتحقّق مما إذا كان الجهاز يتيح استخدام أمر معيّن.

على سبيل المثال، للتحقّق مما إذا كان الجهاز يتيح استخدام الأمر لسمة On/Off: 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 إلى 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 traits:

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 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 لسمة On/Off:

// 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. بالنسبة إلى هذه السمات، يمكنك تحديد ما إذا كانت القيمة الخالية التي تعرضها السمة ترجع إلى عدم إبلاغ الجهاز بهذه القيمة، أو إذا كانت قيمة السمة null فعلاً، وذلك باستخدام isNullable بالإضافة إلى 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!")
}

تعديل سمات السمات

إذا أردت تغيير قيمة سمة معيّنة، ولم يغيّر أي من أوامر السمة هذه القيمة، قد تتيح السمة ضبط قيمتها بشكلٍ صريح.

يعتمد ما إذا كان يمكن تغيير قيمة السمة على عاملَين:

  • هل السمة قابلة للكتابة؟
  • هل يمكن أن تتغيّر قيمة السمة كنتيجة ثانوية لإرسال أمر سمة؟

توفّر المستندات المرجعية للسمات وسماتها هذه المعلومات.

لذلك، فإنّ مجموعات الخصائص التي تحدّد كيفية تغيير قيمة السمة هي:

  • للقراءة فقط و لا تتأثر بالأوامر الأخرى. هذا يعني أنّ قيمة السمة لا تتغيّر. على سبيل المثال، السمة currentPosition لسمة Switch.

  • للقراءة فقط و تتأثر بالأوامر الأخرى. هذا يعني أنّ الطريقة الوحيدة لتغيير قيمة السمة هي نتيجة إرسال أمر. على سبيل المثال، السمة currentLevel لسمة LevelControl Matter هي للقراءة فقط، ولكن يمكن تغيير قيمتها من خلال أوامر مثل moveToLevel.

  • قابلة للكتابة و لا تتأثر بالأوامر الأخرى. هذا يعني أنّه يمكنك تغيير قيمة السمة مباشرةً باستخدام الدالة update للسمة، ولكن لا توجد أي أوامر ستؤثر في قيمة السمة. على سبيل المثال، السمة WrongCodeEntryLimit لسمة DoorLock trait.

  • قابلة للكتابة و تتأثر بالأوامر الأخرى. هذا يعني أنّه يمكنك تغيير قيمة السمة مباشرةً باستخدام الدالة update للسمة، ويمكن أن تتغيّر قيمة السمة نتيجة إرسال أمر. على سبيل المثال، يمكن كتابة السمة speedSetting لـ FanControlTrait مباشرةً، ولكن يمكن أيضًا تغييرها باستخدام الأمر step.

مثال على استخدام الدالة update لتغيير قيمة سمة

يوضّح هذا المثال كيفية ضبط قيمة السمة 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) }
    }

إرسال أوامر متعدّدة في آنٍ واحد

تتيح Batching API للعميل إرسال أوامر أجهزة متعدّدة إلى Home APIs في حمولة واحدة. يتم تجميع الأوامر في حمولة واحدة و تنفيذها بالتوازي، على غرار كيفية إنشاء عملية تشغيل آلي في Home API باستخدام العُقدة المتوازية، مثل مثال فتح الستائر قبل شروق الشمس. ومع ذلك، تتيح Batching API سلوكيات أكثر تعقيدًا وتطورًا من Automation API، مثل القدرة على اختيار الأجهزة ديناميكيًا في وقت التشغيل وفقًا لأي معايير.

يمكن أن تستهدف الأوامر في دفعة واحدة سمات متعدّدة على أجهزة متعدّدة، وفي غرف متعدّدة، وفي هياكل متعدّدة.

يسمح إرسال الأوامر في دفعة واحدة للأجهزة بتنفيذ الإجراءات في آنٍ واحد، وهو أمر غير ممكن فعليًا عند إرسال الأوامر بالتسلسل في طلبات منفصلة. يسمح السلوك الذي يتم تحقيقه باستخدام الأوامر المجمّعة للمطوّر بضبط حالة مجموعة من الأجهزة لتتطابق مع حالة مجمّعة محدّدة مسبقًا.

استخدام Batching API

هناك ثلاث خطوات أساسية متضمّنة في استدعاء الأوامر من خلال Batching API:

  1. استدعِ الطريقة Home.sendBatchedCommands().
  2. ضِمن نص كتلة sendBatchedCommands()، حدِّد الأوامر المطلوب تضمينها في الدفعة.
  3. تحقَّق من نتائج الأوامر المُرسَلة لمعرفة ما إذا نجحت أو فشلت.

استدعاء الطريقة sendBatchedCommands()

استدعِ الطريقة Home.sendBatchedCommands(). وراء الكواليس، تُعدّ هذه الطريقة تعبير لامدا في سياق دفعة خاص.

home.sendBatchedCommands() {

تحديد أوامر الدفعة

ضِمن نص كتلة sendBatchedCommands()، املأ الأوامر القابلة للتجميع في دفعات. الأوامر القابلة للتجميع في دفعات هي إصدارات "ظل" من أوامر Device API الحالية التي يمكن استخدامها في سياق دفعة، ويتم تسميتها بإضافة اللاحقة Batchable. على سبيل المثال، يتضمّن الأمر moveToLevelBatchable()لسمة LevelControl أمرًا مطابقًا باسم moveToLevel().

مثال:

  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() فقط.

مثال

لنفترض أنّك تريد إنشاء تطبيق يستخدم Batching 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()
}