التحقّق مما إذا كانت السمة تتيح استخدام أمر
يمكن أيضًا التحقّق من توفّر دعم لأمر السمة. استخدِم أيضًا دالة
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
، والذي تم تحديده في toggle()
في 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
:
// 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
على وسيطتَين اختياريتين:
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
.
مثال على استخدام الدالة update لتغيير قيمة سمة
يوضّح هذا المثال كيفية ضبط قيمة سمة
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()
. في الكواليس، تهيئ هذه الطريقة تعبيرًا لاmda في سياق ملف بوتقة خاص.
home.sendBatchedCommands() {
تحديد أوامر مجمّعة
في نص العنصر sendBatchedCommands()
، املأ الطلبات التي يمكن إجراؤها بشكلٍ مجمّع. الأوامر القابلة للاستخدام في الحِزم هي إصدارات "ظلّية" لأوامر Device API
الحالية التي يمكن استخدامها في سياق الحِزم، ويتم تسميتها باستخدام اللاحقة
Batchable
المُضافة. على سبيل المثال، يحتوي الأمر
moveToLevel()
لسمة
LevelControl
على نظير له باسم
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()
}