إنشاء تطبيق جوّال باستخدام Home APIs على Android

1. قبل البدء

توفّر واجهات برمجة التطبيقات Google Home API مجموعة من المكتبات لمطوّري تطبيقات Android للاستفادة من منظومة Google Home المتكاملة. باستخدام واجهات برمجة التطبيقات الجديدة هذه، يمكن للمطوّرين إنشاء تطبيقات تتيح إعداد الأجهزة المنزلية الذكية والتحكّم فيها بسلاسة.

توفّر Google تطبيقًا تجريبيًا على Android للمطوّرين الذين يريدون الوصول إلى مثال عملي باستخدام واجهات برمجة التطبيقات Google Home APIs. يستند هذا الدرس التطبيقي إلى فرع من "التطبيق التجريبي" يرشدك إلى كيفية استخدام واجهات برمجة التطبيقات Permissions وCommissioning وDevice وStructure.

المتطلبات الأساسية

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

  • كيفية إنشاء تطبيق Android باستخدام واجهات برمجة التطبيقات Google Home مع أفضل الممارسات
  • كيفية استخدام واجهات برمجة التطبيقات Device وStructure لتمثيل منزل ذكي والتحكّم فيه
  • كيفية استخدام واجهات برمجة التطبيقات الخاصة بالإعداد لإضافة الأجهزة إلى منظومة Google Home المتكاملة

اختياري: إعداد منزلك

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

افتح home-playground.withgoogle.com في متصفح الويب، وسجِّل الدخول باستخدام حسابك على Google، وتحقّق مما إذا كانت الأجهزة المحاكية التالية تظهر:

  • outlet1: مقبس تشغيل/إيقاف
  • light2: إضاءة قابلة للتعتيم
  • light3: تشغيل/إيقاف الإضاءة
  • ac3: مكيّف هواء
  • blinds4: Window Covering
  • washer5: Smart washer

914d23a42b72df8f.png

افتح تطبيق Google Home على جهازك الجوّال، وانقر على زر إضافة، ثم اختَر يعمل مع Google Home. ابحث عن "playground" في القائمة، ثم اختَر مشروع "Google Home Playground"، وانقر على متابعة.

e9ec257b0d9b1ed2.png29fd7416e274d216.pngd974573af0611fd8.png

ستعرض لك Google Home Playground صفحة تفويض الحساب. انقر على تفويض أو تسجيل الدخول باستخدام حساب Google. ستظهر لك جميع الأجهزة التي أعددتها من تطبيق الويب في تطبيق الأجهزة الجوّالة.

13108a3a15440151.png8791a6d33748f7c8.png

اختَر جميع الأجهزة وأكمِل عملية الإعداد. بالعودة إلى الصفحة الرئيسية، ستظهر لك جميع الأجهزة المتاحة.

2b021202e6fd1750.png

يمكن الآن استخدام الأجهزة المتوافقة المُدرَجة في القائمة مع واجهات Google Home APIs.

2. إعداد مشروعك

يوضّح الرسم البياني التالي بنية تطبيق يستخدم واجهات برمجة التطبيقات الخاصة بالأجهزة المنزلية:

بنية واجهات برمجة التطبيقات الخاصة بالمنزل الذكي لتطبيق Android

  • رمز التطبيق: هو الرمز الأساسي الذي يعمل عليه المطوّرون لإنشاء واجهة مستخدم التطبيق والمنطق اللازم للتفاعل مع حزمة تطوير البرامج (SDK) لواجهات برمجة التطبيقات في Home.
  • حزمة تطوير البرامج (SDK) لواجهات برمجة التطبيقات Home APIs: تعمل حزمة تطوير البرامج (SDK) لواجهات برمجة التطبيقات Home APIs التي توفّرها Google مع خدمة Home APIs في GMSCore للتحكّم في أجهزة المنزل الذكي. يصمّم المطوّرون تطبيقات تعمل مع واجهات برمجة التطبيقات Home من خلال تجميعها مع حزمة تطوير البرامج (SDK) الخاصة بواجهات برمجة التطبيقات Home.
  • GMSCore على Android: ‏GMSCore، المعروفة أيضًا باسم "خدمات Google Play"، هي منصة من Google توفّر خدمات النظام الأساسية، ما يتيح الوظائف الرئيسية على جميع أجهزة Android المعتمَدة. تحتوي وحدة "المنزل" في "خدمات Google Play" على الخدمات التي تتفاعل مع واجهات برمجة التطبيقات الخاصة بالمنزل.

إعداد حزمة تطوير البرامج (SDK) الخاصة بالمنزل الذكي

اتّبِع الخطوات الموضّحة في إعداد حزمة تطوير البرامج (SDK) للحصول على أحدث حزمة SDK.

الحصول على نموذج التطبيق

يتوفّر رمز المصدر لتطبيق Sample App على GitHub. يستخدم هذا الدرس التطبيقي حول الترميز الأمثلة من فرع codelab-branch-1 من التطبيق النموذجي.

انتقِل إلى المكان الذي تريد حفظ المشروع فيه واستنسِخ الفرع codelab-branch-1:

$ git clone -b codelab-branch-1 https://github.com/google-home/google-home-api-sample-app-android.git

إنشاء نموذج التطبيق

اتّبِع الخطوات من 1 إلى 5 في إنشاء التطبيق.

32f2b3c0cd80fcf1.png

عندما يعمل التطبيق بنجاح على هاتفك، ستظهر لك الصفحة الرئيسية لـ "تطبيق العيّنة". ولكن لن تتمكّن من تسجيل الدخول إلى أن تُعدّ إعدادات مصادقة OAuth وتنفّذ الأجزاء الناقصة باستخدام Permission API.

3- إعداد المصادقة

تستخدم واجهات برمجة التطبيقات Home APIs بروتوكول OAuth 2.0 لمنح إذن الوصول إلى الأجهزة في البنية. يسمح بروتوكول OAuth للمستخدم بمنح الإذن لتطبيق أو خدمة بدون الحاجة إلى الكشف عن بيانات اعتماد تسجيل الدخول.

اتّبِع التعليمات الواردة في إعداد موافقة OAuth لضبط شاشة طلب الموافقة. احرص على إنشاء حساب اختبار واحد على الأقل.

بعد ذلك، اتّبِع التعليمات الواردة في إعداد بيانات اعتماد OAuth لإنشاء بيانات اعتماد التطبيق.

4. تهيئة الأذونات والتعامل معها

في هذا القسم، ستتعرّف على كيفية إعداد حزمة تطوير البرامج (SDK) والتعامل مع أذونات المستخدمين من خلال إكمال الأجزاء الناقصة باستخدام Permissions API.

تحديد الأنواع والسمات المتوافقة

عند تطوير تطبيق، عليك أن توضّح بشكل صريح أنواع الأجهزة وخصائصها التي سيتوافق معها التطبيق. في التطبيق النموذجي، يتم ذلك من خلال تحديد قوائم ثابتة في العنصر المرافق في HomeApp.kt، والتي يمكن الرجوع إليها بعد ذلك في جميع أنحاء التطبيق حسب الحاجة:

companion object {

  // List of supported device types by this app:
  val supportedTypes: List<DeviceTypeFactory<out DeviceType>> = listOf(
    OnOffLightDevice,
    DimmableLightDevice,

  // ...
  )
  // List of supported device traits by this app:
  val supportedTraits: List<TraitFactory<out Trait>> = listOf(
  OnOff,
  LevelControl,
  // ...
  )
}

اطّلِع على أنواع الأجهزة المتوافقة وفهرس السمات على Android للاطّلاع على جميع أنواع الأجهزة والسمات المتوافقة.

أزِل التعليق من الخطوتين 4.1.1 و4.1.2 في ملف HomeApp.kt المصدر لتفعيل رمز المصدر الذي يطلب الإذن.

companion object {
// List of supported device types by this app:
val supportedTypes: List<DeviceTypeFactory<out DeviceType>> = listOf(
// TODO: 4.1.1 - Non-registered device types will be unsupported
//             ContactSensorDevice,
//             ColorTemperatureLightDevice,
//             DimmableLightDevice,
//             ExtendedColorLightDevice,
//             GenericSwitchDevice,
//             GoogleDisplayDevice,
//             GoogleTVDevice,
//             OccupancySensorDevice,
//             OnOffLightDevice,
//             OnOffLightSwitchDevice,
//             OnOffPluginUnitDevice,
//             OnOffSensorDevice,
//             RootNodeDevice,
//             SpeakerDevice,
//             ThermostatDevice,
)
// List of supported device traits by this app:
val supportedTraits: List<TraitFactory<out Trait>> = listOf(
// TODO: 4.1.2 - Non-registered traits will be unsupported
//             AreaAttendanceState,
//             AreaPresenceState,
//             Assistant,
//             AssistantBroadcast,
//             AssistantFulfillment,
//             BasicInformation,
//             BooleanState,
//             OccupancySensing,
//             OnOff,
//             Notification,
//             LevelControl,
//             TemperatureControl,
//             TemperatureMeasurement,
//             Thermostat,
//             Time,
//             Volume,
        )
}

إعداد عنصر HomeClient

تُعدّ جميع التطبيقات التي تستخدم واجهات برمجة تطبيقات Home عنصر HomeClient، وهو الواجهة الرئيسية للتفاعل مع واجهات برمجة التطبيقات. نجهّز هذا العنصر في أداة التهيئة للفئة HomeApp (HomeApp.kt).

// Registry to record device types and traits used in this app:
val registry = FactoryRegistry(
  types = supportedTypes,
  traits = supportedTraits
)
// Configuration options for the HomeClient:
val config = HomeConfig(
  coroutineContext = Dispatchers.IO,
  factoryRegistry = registry
)
// Initialize the HomeClient, which is the primary object to use all Home APIs:
homeClient = Home.getClient(context = context, homeConfig = config)

أولاً، ننشئ FactoryRegistry باستخدام الأنواع والسمات المتوافقة التي حدّدناها سابقًا. بعد ذلك، باستخدام هذا السجلّ، نضبط HomeConfig، الذي يحتوي على الإعدادات اللازمة لتشغيل واجهات برمجة التطبيقات. بعد ذلك، نستخدم طلب Home.getClient(...) للحصول على مثيل HomeClient.

ستتم جميع تفاعلاتنا مع واجهات برمجة تطبيقات Home من خلال عنصر HomeClient هذا.

استخدام Permissions API

تتم مصادقة المستخدم لواجهات برمجة تطبيقات Home من خلال Permissions API. يحتوي ملف المصدر PermissionsManager.kt لتطبيق Sample App على رمز لمصادقة المستخدم. أزِل التعليق من محتوى الدالتَين checkPermissions(...) وrequestPermissions(...) لتفعيل الأذونات لتطبيق Sample App.

التسجيل:

homeClient.registerActivityResultCallerForPermissions(activity)

سيتم إطلاق الميزات التالية:

try {
    val result: PermissionsResult
    result = homeClient.requestPermissions(forceLaunch = true)
    when (result.status) {
        PermissionsResultStatus.SUCCESS -> // Success Case
        PermissionsResultStatus.CANCELLED -> // User Cancelled
        PermissionsResultStatus.ERROR -> // Some Error
else -> // Unsupported Case
    }
}
catch (e: HomeException) { ... }

جارٍ التحقّق:

try {
    val state: PermissionsState
    state = homeClient.hasPermissions().first { state ->
        state != PermissionsState.PERMISSIONS_STATE_UNINITIALIZED
    }
    when (state) {
        PermissionsState.GRANTED -> // Signed In
        PermissionsState.NOT_GRANTED -> // Not Signed In
        PermissionsState.PERMISSIONS_STATE_UNAVAILABLE -> // ...
        PermissionsState.PERMISSIONS_STATE_UNINITIALIZED -> // ...
else -> // Unsupported case
    }
}
catch (e: HomeException) { ... }

الاشتراك:

       homeClient.hasPermissions().collect( { state ->
// Track the changes on state
        } )

أزِل التعليق من الخطوة 4.3.1 في PermissionsManager.kt لتفعيل الرمز الذي يطلب الأذونات:

fun requestPermissions() {
    scope.launch {
    try {
// TODO: 4.3.1 - Request the permissions from the Permissions API
//                 // Request permissions from the Permissions API and record the result:
//                 val result: PermissionsResult = client.requestPermissions(forceLaunch = true)
//                 // Adjust the sign-in status according to permission result:
//                 if (result.status == PermissionsResultStatus.SUCCESS)
//                     isSignedIn.emit(true)
//                 // Report the permission result:
//                 reportPermissionResult(result)
    }
    catch (e: HomeException) { MainActivity.showError(this, e.message.toString()) }
    }
}

شغِّل التطبيق الآن على هاتفك واتّبِع الخطوات واسمح بالأذونات. من المفترض أن يظهر لك المسار التالي:

c263dcee4e945bf1.png f518cfd1fdb8a9d8.png 59937372f28c472f.png 383073ae57d2ced4.png 89f774a2ba6898ae.png

لا تختفي الرسالة "جارٍ التحميل" أبدًا، ولكن السبب هو أنّنا لم ننفّذ الرمز الذي يقرأ البنية والأجهزة. سنشرح لك طريقة تنفيذ ذلك في القسم التالي.

5- فهم نموذج البيانات

في واجهات برمجة التطبيقات Home APIs، يتألف نموذج البيانات مما يلي:

  • يمثّل Structure منزلاً يحتوي على غرف وأجهزة.
  • Room هو جزء من بنية ويتضمّن أجهزة.
  • يمكن تعيين الأجهزة (المعرَّفة على أنّها HomeDevice) إلى بنية (أو منزل) أو غرفة في البنية.
  • تتألف الأجهزة من مثيل واحد أو أكثر من DeviceType.
  • يتكوّن DeviceType من Trait مثيل.
  • يتألف Trait من Attribute مثيل (للقراءة والكتابة) وCommand مثيل (للتحكّم في السمات) وEvent مثيل (لقراءة سجلّات التغييرات السابقة أو الاشتراك فيها).
  • تشكّل مثيلات Automation جزءًا من بنية وتستخدم بيانات وصفية عن المنزل والأجهزة لتنفيذ المهام بشكل آلي في المنزل.

76d35b44d5a8035e.png

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

قراءة البُنى

يستند تصميم واجهات برمجة التطبيقات الخاصة بمنصة Home إلى Kotlin Flows لبث عناصر نموذج البيانات (مثل Structure وHomeDevice وما إلى ذلك). يشترك المطوّرون في Flow للحصول على جميع العناصر المضمّنة في العنصر (على سبيل المثال، Structure وRoom وما إلى ذلك).

لاسترداد جميع البُنى، استدعِ الدالة structures() التي تعرض مجموعة من البُنى. بعد ذلك، استدعِ دالة القائمة في التدفق للحصول على جميع البُنى التي يملكها المستخدم.

// Get the a snapshot of all structures from the current homeClient
val allStructures : Set<Structure> =
    homeClient.structures()   // HomeObjectsFlow<Structure>
    .list()                   // Set<Structure>

يُنصح بشدة في دليل تصميم التطبيقات باتّباع نهج حديث للبرمجة التفاعلية من أجل تحسين تدفّق بيانات التطبيق وإدارة حالته.

في ما يلي كيفية التزام "التطبيق النموذجي" بنمط الترميز التفاعلي:

  • تشترك طرق العرض (مثل StructureViewModel وDeviceViewModel، باعتبارها عنصرًا يحتفظ بالحالة) في التدفقات من حزمة تطوير البرامج (SDK) لواجهات برمجة التطبيقات الخاصة بالمنزل لتلقّي تغييرات القيم والاحتفاظ بأحدث الحالات.
  • تشترك طرق العرض (مثل StructureView وDeviceView) في نماذج العرض لتلقّي الحالات وعرض واجهة المستخدم بما يعكس هذه التغييرات.
  • عندما ينقر المستخدم على زر في طريقة العرض (على سبيل المثال، الزر "تشغيل" لجهاز إضاءة)، تؤدي الأحداث إلى تشغيل وظائف نموذج طريقة العرض، والتي بدورها تستدعي وظائف واجهات برمجة التطبيقات المتوافقة مع Home (على سبيل المثال، الأمر On الخاص بسمة OnOff).

في الخطوة 5.1.1 في HomeAppViewModel.kt، نشترك في أحداث تغيير البنية من خلال استدعاء الدالة collect(). أزِل التعليق من القسم الذي يتنقّل في structureSet الذي يعرضه ردّ Structures API ويتم تقديمه في StructureViewModel's StateFlow. يتيح ذلك للتطبيق تتبُّع التغييرات في حالة البنية:

   private suspend fun subscribeToStructures() {
// TODO: 5.1.1 - Subscribe the structure data changes
// // Subscribe to structures returned by the Structures API:
// homeApp.homeClient.structures().collect { structureSet ->
//     val structureVMList: MutableList<StructureViewModel> = mutableListOf()
//     // Store structures in container ViewModels:
//     for (structure in structureSet) {
//         structureVMList.add(StructureViewModel(structure))
//     }
//     // Store the ViewModels:
//     structureVMs.emit(structureVMList)
//
//     // If a structure isn't selected yet, select the first structure from the list:
//     if (selectedStructureVM.value == null && structureVMList.isNotEmpty())
//         selectedStructureVM.emit(structureVMList.first())
//
// }
}

في DevicesView.kt، يشترك التطبيق في StructureViewModel'sStateFlow, الذي يؤدي إلى إعادة إنشاء واجهة المستخدم عند تغيير البيانات الهيكلية. أزِل التعليق من رمز المصدر في الخطوة 5.1.2 لعرض قائمة البنية كقائمة منسدلة:

   val structureVMs: List<StructureViewModel> = homeAppVM.structureVMs.collectAsState().value
...
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
// TODO: 5.1.2 - Show list of structures in DropdownMenu
//  for (structure in structureVMs) {
//      DropdownMenuItem(
//          text = { Text(structure.name) },
//          onClick = {
//              scope.launch { homeAppVM.selectedStructureVM.emit(structure) }
//              expanded = false
//          }
//      )
//  }
}
...

تشغيل التطبيق مرة أخرى من المفترض أن تظهر لك القائمة عند النقر على السهم:

f1fc2be1cb6436b6.png

تحليل البنية

الخطوة التالية هي الانتقال بين عناصر المنزل في بنية. استرداد الغرف من البنية:

val rooms: Set<Room>
rooms = structure.rooms().list()

يمكنك بعد ذلك التنقّل بين الغرف لاسترداد الأجهزة:

val devices: Set<HomeDevice>
devices = room.devices().list()

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

val devicesWithoutRooms: MutableSet<HomeDevice> = mutableSetOf()

for (device in structure.devices().list())
if (device.roomId == null)
  devicesWithoutRooms.add(device)

مرة أخرى، في الرمز النموذجي الحالي، نشترك في تدفق للحصول على أحدث قائمة بالغرف والأجهزة. تحقَّق من الرمز في الخطوتين 5.2.1 و5.2.2 في ملف المصدر StructureViewModel.kt وأزِل التعليق منه لتفعيل الاشتراك في بيانات الغرفة:

val roomVMs : MutableStateFlow<List<RoomViewModel>>
val deviceVMs : MutableStateFlow<List<DeviceViewModel>>
val deviceVMsWithoutRooms : MutableStateFlow<List<DeviceViewModel>>
private suspend fun subscribeToRooms() {
// TODO: 5.2.1 - Subscribe the room data changes
//   // Subscribe to changes on rooms:
//   structure.rooms().collect { roomSet ->
//       val roomVMs = mutableListOf<RoomViewModel>()
//       // Store rooms in container ViewModels:
//       for (room in roomSet) {
//           roomVMs.add(RoomViewModel(room))
//       }
//       // Store the ViewModels:
//       this.roomVMs.emit(roomVMs)
//   }
}
private suspend fun subscribeToDevices() {
// TODO: 5.2.2 - Subscribe the device data changes in a structure
//   // Subscribe to changes on devices:
//   structure.devices().collect { deviceSet ->
//       val deviceVMs = mutableListOf<DeviceViewModel>()
//       val deviceWithoutRoomVMs = mutableListOf<DeviceViewModel>()
//       // Store devices in container ViewModels:
//       for (device in deviceSet) {
//           val deviceVM = DeviceViewModel(device)
//           deviceVMs.add(deviceVM)
//           // For any device that's not in a room, additionally keep track of a separate list:
//           if (device.roomId == null)
//               deviceWithoutRoomVMs.add(deviceVM)
//       }
//       // Store the ViewModels:
//       this.deviceVMs.emit(deviceVMs)
//       deviceVMsWithoutRooms.emit(deviceWithoutRoomVMs)
//   }
    }

أزِل التعليق من الخطوتين 5.2.3 و5.2.4 في ملف المصدر DevicesView.kt لعرض قائمة الغرف كقائمة:

val selectedRoomVMs: List<RoomViewModel> =
selectedStructureVM.roomVMs.collectAsState().value
...
for (roomVM in selectedRoomVMs) {
// TODO: 5.2.3 - Render the list of rooms
//   RoomListItem(roomVM)
// TODO: 5.2.4 - Render the list of devices in a room
//   val deviceVMsInRoom: List<DeviceViewModel> = roomVM.deviceVMs.collectAsState().value
//
//   for (deviceVM in deviceVMsInRoom) {
//       DeviceListItem(deviceVM, homeAppVM)
//   }
}

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

e715ddda50e04839.png

6. العمل مع الأجهزة

تستخدم واجهات برمجة التطبيقات Home APIs عنصر HomeDevice لتسجيل الجهاز وإمكاناته. يمكن للمطوّرين الاشتراك في سمات الأجهزة واستخدامها لتمثيل الأجهزة المنزلية الذكية في تطبيقاتهم.

قراءة حالات الجهاز

يعرض الكائن HomeDevice مجموعة من القيم الثابتة، مثل اسم الجهاز أو حالة الاتصال. بصفتك مطوّرًا، يمكنك استرداد هذه المعلومات بعد الحصول على الجهاز من واجهات برمجة التطبيقات مباشرةً:

val id: String = device.id.id
val name: String = device.name
val connectivity: ConnectivityState =
    device.sourceConnectivity.connectivityState

للحصول على إمكانات الجهاز، عليك استرداد الأنواع والسمات من HomeDevice. لإجراء ذلك، يمكنك الاشتراك في مسار نوع الجهاز على النحو التالي، واسترداد السمات من أنواع الأجهزة:

device.types().collect { typeSet ->
var primaryType : DeviceType = UnknownDeviceType()
for (typeInSet in typeSet)
if (typeInSet.metadata.isPrimaryType)
                    primaryType = typeInSet
            val traits: List<Trait> = mutableListOf()
for (trait in primaryType.traits())
if (trait.factory in myTraits)
                    traits.add(trait)
for (trait in traits)
                parseTrait(trait, primaryType)
        }

يحتوي كل جهاز على مجموعة من DeviceType المتوافقة (الإمكانات المجمّعة)، والتي يمكنك استردادها باستخدام device.types(). تحتوي أنواع الأجهزة هذه على سمات يمكن استردادها باستخدام type.traits(). يحدّد كل جهاز أحد أنواعه كنوع أساسي (يمكن التحقّق منه باستخدام type.metadata.isPrimaryType) يجب أن تمثّله في تطبيقك. ولتقديم تجربة كاملة للمستخدمين، ننصحك باستعراض جميع الأنواع التي تم إرجاعها ودمج جميع السمات المتاحة لك.

بعد استرداد سمة، يمكنك تحليلها باستخدام دالة مثل ما يلي لتفسير القيم:

fun <T : Trait?> parseTrait(trait : T, type: DeviceType) {
    val status : String = when (trait) {
        is OnOff -> { if (trait.onOff) "On" else "Off" }
        is LevelControl -> { trait.currentLevel.toString() }
        is BooleanState -> {
            when (type.factory) {
                ContactSensorDevice -> {
if (trait.stateValue) "Closed"
else "Open"
                }
else -> ...
            }
        }
else -> ...
    }
}

يُرجى العِلم أنّه يمكن أن تكون هناك اختلافات في ما تمثّله السمة، وذلك حسب نوع الجهاز الذي يعرضها (راجِع BooleanState في المثال السابق)، لذا عليك أن تكون على دراية بسياق كل نوع من أنواع الأجهزة لفهم ما تمثّله سماتها حقًا.

أزِل التعليق من الخطوتين 6.1.1 و6.1.2 في ملف المصدر DeviceViewModel.kt لاسترداد الحالات:

private suspend fun subscribeToType() {
// Subscribe to changes on device type, and the traits/attributes within:
device.types().collect { typeSet ->
// Container for the primary type for this device:
var primaryType : DeviceType = UnknownDeviceType()
...
// TODO: 6.1.1 - Determine the primary type for this device
//       // Among all the types returned for this device, find the primary one:
//       for (typeInSet in typeSet)
//           if (typeInSet.metadata.isPrimaryType)
//               primaryType = typeInSet
//
//       // Optional: For devices with a single type that did not define a primary:
//       if (primaryType is UnknownDeviceType && typeSet.size == 1)
//           primaryType = typeSet.first()
// Container for list of supported traits present on the primary device type:
val supportedTraits: List<Trait> = getSupportedTraits(primaryType.traits())
...
}
fun getSupportedTraits(traits: Set<Trait>) : List<Trait> {
           val supportedTraits: MutableList<Trait> = mutableListOf()
// TODO: 6.1.2 - Get only the supported traits for this device
//   for (trait in traits)
//       if (trait.factory in HomeApp.supportedTraits)
//           supportedTraits.add(trait)
return supportedTraits
}

أزِل التعليق من الخطوة 6.1.3 في DeviceView.kt لعرض سمة OnOff، بما في ذلك اسمها وحالتها، كـ String:

Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
when (trait) {
is OnOff -> {
// TODO: 6.1.3 - Render controls based on the trait type
// Column (Modifier.fillMaxWidth()) {
//     Text(trait.factory.toString(), fontSize = 20.sp)
//     Text(DeviceViewModel.getTraitStatus(trait, type), fontSize = 16.sp)
// }
...
}
is LevelControl -> {
      ...
  }
   is BooleanState -> {
      ...
  }
   is OccupancySensing -> {
      ...
  }
  ...
}

إذا شغّلت التطبيق الآن باستخدام أنواع الأجهزة المتوافقة (مثل جهاز إضاءة)، من المفترض أن يعرض الحالات الحديثة لجميع الأجهزة.

1bd8b3b2796c4c7a.png

إصدار أوامر للجهاز

لإصدار أوامر إلى الأجهزة، توفّر واجهات برمجة تطبيقات Home وظائف ملائمة في عناصر Trait، مثل trait.on() أو trait.moveToLevel(...):

fun <T : Trait?> issueCommand(trait : T) {
     when (trait) {
         is OnOff -> {
// trait.on()
// trait.off()
   }
   is LevelControl -> {
// trait.moveToLevel(...)
// trait.moveToLevelWithOnOff(...)
        }
    }
}

ملاحظة: بعد تحديد نوع السمة، استخدِم ميزة الإكمال التلقائي في Android Studio لمعرفة أنواع الإجراءات المتاحة للتفاعل مع السمة.

أزِل التعليق من الخطوة 6.2.1 في DeviceView.kt لإضافة عناصر تحكّم وظيفية في التطبيق:

Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
when (trait) {
is OnOff -> {
                ....
// TODO: 6.2.1 - Render controls based on the trait type
//   Switch (checked = (trait.onOff == true), modifier = Modifier.align(Alignment.CenterEnd),
//       onCheckedChange = { state ->
//           scope.launch { if (state) trait.on() else trait.off() }
//       },
//       enabled = isConnected
//   )
}

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

إذا نقرت على عنصر التحكّم OnOff في المصباح، من المفترض أن يضيء الجهاز الآن.

c8ed3ecf5031546e.png

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

7. أجهزة العمولة

تتيح واجهة برمجة التطبيقات Commissioning API للمطوّرين إضافة أجهزة إلى المنظومة المتكاملة Google Home، وإتاحة التحكّم فيها باستخدام واجهات برمجة التطبيقات Home APIs. لا تتوافق إلا أجهزة Matter. في هذا القسم، سنتعرّف على كيفية تفعيل ميزة إعداد الأجهزة في تطبيقاتك.

قبل البدء في هذا القسم، تأكَّد من استيفاء المتطلبات الأساسية التالية:

إذا كان لديك جهاز Matter فعلي يتضمّن رمز استجابة سريعة لإجراء عملية الإعداد، يمكنك الانتقال إلى تفعيل واجهة برمجة التطبيقات الخاصة بعملية الإعداد. بخلاف ذلك، انتقِل إلى القسم التالي الذي نناقش فيه كيفية استخدام تطبيق Matter Virtual Device (MVD) لإنشاء أجهزة افتراضية يمكن إعدادها.

اختياري: تجهيز جهاز متوافق مع معيار Matter

أبسط طريقة لإعداد جهاز متوافق مع Matter هي استخدام جهاز محاكى يوفّره تطبيق Matter Virtual Device (MVD).

بعد تثبيت MVD وإعداد جدار الحماية، شغِّل MVD:

b20283893073ac1b.png

أنشئ جهاز OnOff. لاحظ أنّه لم يتم إعدادها بعد، بل سيتم ذلك لاحقًا في هذا الدرس العملي.

5f4855b808312898.png

تفعيل واجهة برمجة التطبيقات Commissioning API

تعمل واجهة برمجة التطبيقات Commissioning API خارج نشاط التطبيق، لذا يجب التعامل مع عملية الإعداد بشكل مختلف عن واجهات برمجة التطبيقات الأخرى في Home. لكي يكون تطبيقك جاهزًا للتكليف، تحتاج إلى متغيرَين.

أحد المتغيّرات هو ActivityResultLauncher، ويُستخدَم لإرسال نية التكليف وإدارة دالة ردّ النتائج. المتغيّر الآخر هو CommissioningResult، وهو العنصر المستخدَم لتخزين نتيجة الإعداد. اطّلِع على المثال التالي لمعرفة كيفية إعداد عمولة:

var launcher: ActivityResultLauncher<IntentSenderRequest>
lateinit var commissioningResult: CommissioningResult?
launcher = activity.registerForActivityResult(StartIntentSenderForResult()) { result ->
try {
  commissioningResult = CommissioningResult.fromIntentSenderResult(
      result.resultCode, result.data)
  } catch (exception: ApiException) {
// Catch any issues
 }
}

بعد إعداد مسار الإعداد، عليك إنشاء نية الإعداد وإطلاقها باستخدام مشغّل التطبيق الذي أنشأناه في المثال السابق. ننصحك بوضع الغرض والمشغّل في دالة مخصّصة مثل ما يلي. يمكن ربط وظيفة مخصّصة بأحد عناصر واجهة المستخدم (مثل الزر +إضافة جهاز) واستدعاؤها استنادًا إلى طلب المستخدم:

fun requestCommissioning() {
// Retrieve the onboarding payload used when commissioning devices:
val payload = activity.intent?.getStringExtra(Matter.EXTRA_ONBOARDING_PAYLOAD)
  scope.launch {
    // Create a commissioning request to store the device in Google's Fabric:
    val request = CommissioningRequest.builder()
      .setStoreToGoogleFabric(true)
      .setOnboardingPayload(payload)
      .build()
    // Initialize client and sender for commissioning intent:
    val client: CommissioningClient = Matter.getCommissioningClient(context)
    val sender: IntentSender = client.commissionDevice(request).await()
    // Launch the commissioning intent on the launcher:
    launcher.launch(IntentSenderRequest.Builder(sender).build())
  }
}

أزِل التعليق من الخطوة 7.1.1 في CommissioningManager.kt لتفعيل إمكانية الإعداد وجعل الزر +Add Device يعمل في تطبيق Sample App.

// Called by +Add Device button in DeviceView.kt
fun requestCommissioning() {
// Retrieve the onboarding payload used when commissioning devices:
val payload = activity.intent?.getStringExtra(Matter.EXTRA_ONBOARDING_PAYLOAD)
// TODO: 7.1.1 - Launch the commissioning intent
// scope.launch {
//     // Create a commissioning request to store the device in Google's Fabric:
//     val request = CommissioningRequest.builder()
//         .setStoreToGoogleFabric(true)
//         .setOnboardingPayload(payload)
//         .build()
//     // Initialize client and sender for commissioning intent:
//     val client: CommissioningClient = Matter.getCommissioningClient(context)
//     val sender: IntentSender = client.commissionDevice(request).await()
//     // Launch the commissioning intent on the launcher:
//     launcher.launch(IntentSenderRequest.Builder(sender).build())
// }
}

من المفترض أن يؤدي تشغيل هذه الدالة إلى بدء عملية الإعداد، والتي من المفترض أن تعرض شاشة تشبه لقطة الشاشة التالية:

baae45588f460664.png

فهم مسار عملية إعداد حسابات العملاء

يتضمّن مسار الإعداد مجموعة من الشاشات التي ترشد المستخدم خلال عملية إضافة جهاز إلى حسابه على Google:

2fb0404820d4a035.png 3cbfa8ff9cfd5ee4.png a177c197ee7a67bf.png 3fdef24672c77c0.png dec8e599f9aa119.png

سيظهر للمستخدمين ماسح ضوئي لرموز الاستجابة السريعة يمكنهم استخدامه لمسح رموز الاستجابة السريعة من أجهزة Matter. بعد ذلك، ستنتقل عملية الإعداد إلى عرض "اتفاقية المستخدم"، واكتشاف الجهاز وإعداده، وتسمية الجهاز. بعد اكتمال عملية الربط، سيتم إعادة التركيز على التطبيق، وسيتم تمرير نتيجة الربط في دالة معاودة الاتصال التي أعددناها في القسم السابق.

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

لمزيد من المعلومات حول واجهة برمجة التطبيقات الخاصة بالإعداد، انتقِل إلى واجهة برمجة التطبيقات الخاصة بالإعداد على Android.

8. تهانينا!

تهانينا! لقد أنشأت تطبيق Android بنجاح باستخدام واجهات برمجة التطبيقات Google Home. خلال هذا الدرس البرمجي، استكشفت واجهات برمجة التطبيقات الخاصة بالأذونات والأجهزة والبِنى والإعداد. في الدرس التطبيقي التالي حول الترميز، إنشاء عمليات تشغيل آلي متقدّمة باستخدام واجهات برمجة التطبيقات Home APIs على Android Codelab، سنتعرّف على واجهات برمجة التطبيقات Automation وDiscovery، وسنكمل التطبيق.

نأمل أن تستمتع بإنشاء تطبيقات تتحكّم بشكل مبتكر في الأجهزة ضمن منظومة Google Home المتكاملة.

الخطوات التالية