التحكّم في الأجهزة على 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، أو من false إلى 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 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 أو مخطط Cloud-to-cloud smart home. بالنسبة إلى هذه السمات، يمكنك تحديد ما إذا كانت القيمة null التي تعرضها السمة ناتجة عن عدم إبلاغ الجهاز بهذه القيمة، أو ما إذا كانت قيمة السمة هي 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.

  • يمكن الكتابة فيه ويتأثر بأوامر أخرى. وهذا يعني أنّه يمكنك تغيير قيمة السمة مباشرةً باستخدام الدالة 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) }
    }

إرسال أوامر متعددة في الوقت نفسه

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

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

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

استخدم واجهة برمجة التطبيقات الدفعية

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

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

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

استدعِ طريقة Home.sendBatchedCommands(). في الخلفية، تعمل هذه الطريقة على إعداد تعبير lambda في سياق دفعة خاص.

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()، الذي يمكنه: - عرض نتيجة الأمر الذي تم تنفيذه، - أو عرض خطأ إذا لم يكتمل نطاق المجموعة أو إذا لم ينجح تنفيذ الأمر.

يجب عليك فقط التحقق من النتائج خارج نطاق lambda 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()
}