التحقّق مما إذا كانت السمة تتوافق مع أمر
يمكن أيضًا التحقّق من إمكانية استخدام أمر سمة. استخدِم أيضًا الدالة
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
ولا تكتمل إلا عند تلقّي استجابة من واجهة برمجة التطبيقات (API) (مثل تأكيد تغيير حالة الجهاز).
قد تعرض الأوامر استثناءً في حال رصد مشكلة في مسار التنفيذ. بصفتك مطوِّرًا، عليك استخدام حظر 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
الخاصة بالسمة، ويمكن أن تتغيّر قيمة السمة نتيجةً لإرسال أمر. على سبيل المثال، يمكن كتابة سمةoccupiedCoolingSetpoint
الخاصة بسمةThermostat
، ويمكن أيضًا تعديلها باستخدام الأمرsetpointRaiseLower
.
مثال على استخدام دالة التعديل لتغيير قيمة إحدى السمات
يوضّح هذا المثال كيفية ضبط قيمة السمة
DoorLockTrait.WrongCodeEntryLimit
بشكل صريح.
لضبط قيمة سمة، استدعِ الدالة update
الخاصة بالسمة ومرِّر إليها دالة معدِّلة تضبط القيمة الجديدة.
من الممارسات الجيدة أن تتأكّد أولاً من أنّ السمة تتيح استخدام سمة.
على سبيل المثال:
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) } }
إرسال أوامر متعددة في الوقت نفسه
تتيح واجهة Batching API للعميل إرسال أوامر متعددة إلى أجهزة Home APIs في حمولة واحدة. يتم تجميع الطلبات في حمولة واحدة وتنفيذها بالتوازي، على غرار طريقة إنشاء عملية تشغيل آلي باستخدام واجهة برمجة التطبيقات Home API من خلال العقدة المتوازية، مثل المثال فتح الستائر قبل شروق الشمس. ومع ذلك، تتيح واجهة Batching API سلوكيات أكثر تعقيدًا وتطوّرًا من واجهة Automation API، مثل إمكانية اختيار الأجهزة بشكل ديناميكي في وقت التشغيل وفقًا لأي معايير.
يمكن أن تستهدف الأوامر في مجموعة واحدة سمات متعددة على أجهزة متعددة، وفي غرف متعددة، وفي مبانٍ متعددة.
يتيح إرسال الأوامر في مجموعة للأجهزة تنفيذ الإجراءات في الوقت نفسه، وهو أمر غير ممكن عند إرسال الأوامر بالتسلسل في طلبات منفصلة. يسمح السلوك الذي يتم تحقيقه باستخدام الأوامر المجمّعة للمطوّر بضبط حالة مجموعة من الأجهزة لتتطابق مع حالة مجمّعة محدّدة مسبقًا.
استخدام Batching API
هناك ثلاث خطوات أساسية لإصدار الأوامر من خلال Batching API:
- استدعِ طريقة
Home.sendBatchedCommands()
. - ضِمن نص كتلة
sendBatchedCommands()
، حدِّد الأوامر المطلوب تضمينها في المجموعة. - اطّلِع على نتائج الأوامر المُرسَلة لمعرفة ما إذا كانت ناجحة أو فاشلة.
تنشيط طريقة sendBatchedCommands()
استدعِ طريقة
Home.sendBatchedCommands()
. في الخلفية، تعمل هذه الطريقة على إعداد تعبير lambda في سياق دفعة خاص.
home.sendBatchedCommands() {
تحديد أوامر مُجمّعة
ضمن نص كتلة sendBatchedCommands()
، املأ الأوامر التي يمكن تنفيذها بشكل مجمّع. الأوامر التي يمكن تنفيذها بشكل مجمّع هي إصدارات "ظل" من أوامر Device API الحالية التي يمكن استخدامها في سياق مجمّع، ويتم تسميتها باللاحقة المضافة 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()
lambda.
مثال
لنفترض أنّك تريد إنشاء تطبيق يستخدم 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()
}