التحكّم في الأجهزة على نظام التشغيل iOS

يُعدّ هذا الدليل استكمالاً لدليل الوصول إلى الأجهزة والبيانات الوصفية للأجهزة على iOS، ويقدّم أمثلة إضافية على التحكّم في الأجهزة والوصول إليها.

لاستخدام أنواع أو سمات أجهزة معيّنة، مثل Matter OnOffTrait المستخدَمة في العديد من الأمثلة هنا، يجب استيرادها:

import GoogleHomeSDK
import GoogleHomeTypes

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

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

على سبيل المثال، للتحقّق مما إذا كان الجهاز يتيح استخدام الأمر toggle الخاص بسمة التشغيل/الإيقاف:

// Check if the OnOff trait supports the toggle command.
if onOffTraitTest.supportsToggleCommand {
  print("onOffTrait supports toggle command")
} else {
  print("onOffTrait does not support stateful toggle command")
}

إرسال أمر إلى جهاز

يشبه إرسال أمر قراءة سمة حالة من سمة. لتشغيل الجهاز أو إيقافه، استخدِم الأمر OnOffTrait Toggle، والذي يتم تحديده في نموذج بيانات منظومة Google Home المتكاملة على أنّه toggle(). تغيّر هذه الطريقة onOff إلى false إذا كانت true، أو إلى true إذا كانت false:

// Calling a command on a trait.
do {
  try await onOffTraitTest.toggle()
} catch let _ as HomeError {
  // Code for handling the exception
}

قد تعرض الأوامر استثناءً في حال رصد مشكلة في مسار التنفيذ. بصفتك مطوّرًا، عليك استخدام مجموعة do-catch للتعامل مع هذه الاستثناءات بشكل صحيح، وعرض معلومات تفصيلية للمستخدمين في الحالات التي يمكن فيها اتخاذ إجراءات بشأن الأخطاء. ستؤدي الاستثناءات غير المُعالَجة إلى إيقاف وقت تشغيل التطبيق وقد تؤدي إلى حدوث أعطال في تطبيقك.

بدلاً من ذلك، يمكنك استخدام الأمرَين off() أو on() لضبط الحالة بشكل صريح:

do {
  try await onOffTraitTest.off()
  try await onOffTraitTest.on()
} catch let _ as HomeError {
  // Code for handling the exception
}

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

إرسال أمر يتضمّن مَعلمات

قد تستخدم بعض الأوامر مَعلمات، مثل تلك الموجودة في OnOffTrait أو LevelControlTrait:

offWithEffect

// Turn off the light using the DyingLight effect.
do {
  try await onOffTraitTest.offWithEffect(
    effectIdentifier: Matter.OnOffTrait.EffectIdentifierEnum.dyingLight,
    effectVariant: 0
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

moveToLevel

// Change the brightness of the light to 50%
do {
  try await levelControlTraitTest.moveToLevel(
    level: UInt8(127),
    transitionTime: 0,
    optionsMask: Matter.LevelControlTrait.OptionsBitmap(),
    optionsOverride: Matter.LevelControlTrait.OptionsBitmap()
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

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

قد تتوافق بعض الأجهزة مع سمة Matter، ولكن ليس مع سمة معيّنة. على سبيل المثال، قد لا يتوافق جهاز Cloud-to-cloud الذي تم ربطه بـ Matter مع كل سمات Matter. للتعامل مع حالات مثل هذه، استخدِم السمة isSupported على مستوى السمة للتحقّق مما إذا كانت السمة متاحة لجهاز معيّن.

على سبيل المثال، للتحقّق من توافق الجهاز مع سمة التشغيل/الإيقاف onOff:

// Check if the OnOff trait supports the onOff attribute.
if onOffTrait.attributes.$onOff.isSupported {
  print("onOffTrait supports onOff state")
} else {
  print("onOffTrait is for a command only device!")
}

يمكن أن تكون بعض السمات فارغة في مواصفات Matter أو مخطط Cloud-to-cloud smart home. بالنسبة إلى هذه السمات، يمكنك تحديد ما إذا كانت القيمة nil التي تعرضها السمة ناتجة عن عدم إبلاغ الجهاز عن هذه القيمة، أو ما إذا كانت قيمة السمة هي nil فعليًا، وذلك باستخدام isNullable بالإضافة إلى isSupported:

// Check if a nullable attribute is set or is not supported.
if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
  if let onOffTrait = deviceType.traits[Matter.OnOffTrait.self] {
    if onOffTrait.attributes.startUpOnOff == nil {
      if onOffTrait.attributes.$startUpOnOff.isSupported {
        print(
          "onOffTrait supports startUpOnOff and it is nil. Check the spec for the contextual meaning."
        )
      } else {
        print("onOffTrait does not support startUpOnOff!")
      }
    } else {
      print(
        "onOffTrait supports startUpOnOff and it is set to \(String(describing: onOffTrait.attributes.startUpOnOff))"
      )
    }
  }
}

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

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

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

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

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

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

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

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

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

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

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

يوضّح هذا المثال كيفية ضبط قيمة السمة DoorLockTrait.wrongCodeEntryLimit بشكل صريح.

لضبط قيمة سمة، يمكنك استدعاء دالة update الخاصة بالسمة وتمرير دالة تعديل تضبط القيمة الجديدة. من الممارسات الجيدة التحقّق أولاً من أنّ السمة تتيح استخدام سمة معيّنة.

على سبيل المثال:

if doorLockTraitTest.attributes.$wrongCodeEntryLimit.isSupported {
  let _ = try await doorLockTraitTest.update {
    $0.setWrongCodeEntryLimit(3)
  }
}

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

بالإضافة إلى التحكّم الدقيق في كل عضو في المجموعة من خلال التكرار على الأجهزة الأعضاء، يمكنك التحكّم في DeviceGroup ككيان واحد. يتوافق DeviceGroup مع بروتوكول HasParts ويعرض أجزاء DeviceType المتوافقة مع الأجهزة في المجموعة:

  • تعرض الدالة await group.part(_:) المثيل الأول من DeviceType المحدّد إذا كانت المجموعة تتيح ذلك، أو تعرض nil في الحالات الأخرى.
  • تعرض الدالة try await group.parts(_:) مصفوفة من جميع الأجزاء ذات المستوى الأعلى التي تتطابق مع DeviceType المحدّد.

عند استدعاء أمر سمة على نوع جهاز ضمن DeviceGroup، تنقل حزمة تطوير البرامج (SDK) الأمر إلى جميع الأجهزة الأعضاء المرتبطة في المجموعة التي تتيح هذه الإمكانية، بغض النظر عن إمكانية الاتصال أو النقل الأساسي (مثل Matter أو Cloud-to-cloud).

سمات سمة جزء المجموعة ومعالجة الأخطاء

يؤدي استهداف جزء من المجموعة إلى تبسيط الحالات الشائعة التي تريد فيها إرسال أمر متكرر (مثل تشغيل الأضواء أو إطفائها أو ضبط مستوى السطوع) عبر مجموعة. عند العمل مع السمات والأوامر على جزء DeviceGroup، يُرجى مراعاة السلوكيات التالية:

  • لا تتم عادةً تعبئة سمات الحالة الديناميكية (مثل سمة onOff في OnOffTrait أو سمة currentLevel في LevelControlTrait) في سمة جزء المجموعة لأنّ الأجهزة الأعضاء في المجموعة يمكن أن تكون في حالات مختلفة لا يمكن دمجها في قيمة واحدة. ومع ذلك، يتم ملء الطلبات المتوافقة (التي تم التحقّق منها باستخدام supports(_:)، مثل .moveToLevelWithOnOff على ضوء قابل للتعتيم) واختيار سمات الإمكانات (مثل colorTempPhysicalMinMireds و colorTempPhysicalMaxMireds على ColorControlTrait للأضواء الملوّنة) بحيث يمكن لتطبيقك ضبط عناصر التحكّم في واجهة المستخدم بشكل مناسب.
  • لا تتوفّر جميع الأوامر المتعلّقة بسمات أعضاء المجموعة في جزء المجموعة. على وجه الخصوص، لا تتوفّر الأوامر التي تعرض القيم.
  • لا يعرض الأمر على جزء من المجموعة نتائج فردية لكل أمر جهاز عضو أساسي. لفحص نتائج الأوامر الفردية أو استدعاء الأوامر التي تعرض القيم، يمكنك التكرار من خلال أجهزة أعضاء المجموعة مباشرةً.
  • تكتمل الأوامر المُرسَلة إلى جزء من DeviceGroup بعد انتهاء عملية التوزيع بالكامل على جميع الأجهزة الأعضاء وجمع كل حالات نجاح أو فشل الأوامر الأساسية. لا يتعذّر تنفيذ الأمر (يظهر الخطأ HomeError) إلا إذا تعذّر تنفيذه على كل جهاز عضو مرتبط أو إذا كانت جميع أجهزة الاختبار غير متاحة، ويظهر ذلك كتعذُّر تنفيذ أمر واحد يتطابق مع بنية طلب الأمر الواحد. إذا نفّذ جهاز واحد على الأقل من الأجهزة الأعضاء الأمر، يكتمل أمر المجموعة بدون ظهور خطأ.

إرسال الأوامر إلى جزء من المجموعة

لتشغيل جميع مصابيح OnOffLightDeviceType في مجموعة أجهزة:

if let lightPart = await group.part(OnOffLightDeviceType.self),
   let onOffTrait = lightPart.matterTraits.onOffTrait,
   onOffTrait.supports(.on) {
  try await onOffTrait.on()
}

يمكنك أيضًا التحقّق من أنواع متعددة من الأجهزة ذات الصلة (على سبيل المثال، إيقاف أي نوع من أجهزة الإضاءة في المجموعة التي تتوافق مع OnOffTrait):

func onOffTrait(in group: DeviceGroup) async -> Matter.OnOffTrait? {
  if let light = await group.part(OnOffLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(DimmableLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ColorTemperatureLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ExtendedColorLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  return nil
}

if let trait = await onOffTrait(in: group) {
  try await trait.off()
}

يمكنك التحقّق من إمكانية تنفيذ الأمر أو قراءة حدود الإمكانات في جزء من المجموعة قبل إرسال الأمر (على سبيل المثال، التحقّق من .moveToLevelWithOnOff في جزء من ضوء قابل للتعتيم أو قراءة colorTempPhysicalMinMireds و colorTempPhysicalMaxMireds في جزء من ضوء درجة حرارة الألوان):

if let dimmableLight = await group.part(DimmableLightDeviceType.self),
   let levelTrait = dimmableLight.matterTraits.levelControlTrait,
   levelTrait.supports(.moveToLevelWithOnOff) {
  try await levelTrait.moveToLevelWithOnOff(
    level: 127,
    transitionTime: nil,
    optionsMask: [],
    optionsOverride: []
  )
}

if let colorLight = await group.part(ColorTemperatureLightDeviceType.self),
   let colorTrait = colorLight.matterTraits.colorControlTrait,
   let minMireds = colorTrait.attributes.colorTempPhysicalMinMireds,
   let maxMireds = colorTrait.attributes.colorTempPhysicalMaxMireds {
  let midMireds = UInt16((UInt32(minMireds) + UInt32(maxMireds)) / 2)
  try await colorTrait.moveToColorTemperature(
    colorTemperatureMireds: midMireds,
    transitionTime: 0,
    optionsMask: [],
    optionsOverride: []
  )
}