واجهات برمجة تطبيقات Structure على نظام التشغيل iOS

يمكن الوصول إلى واجهات برمجة التطبيقات Structure API من خلال واجهات برمجة تطبيقات Home لأجهزة iOS.

لاستخدام واجهات برمجة التطبيقات Structure API، عليك أولاً استيراد حزمة GoogleHomeSDK إلى تطبيقك:

import GoogleHomeSDK

معالجة الأخطاء

تُرسل بعض الطرق في واجهات برمجة تطبيقات Home الخطأ HomeError، لذا ننصحك باستخدام كتلة do-catch لرصد HomeError في هذه الطلبات.

عند التعامل مع HomeError، يُرجى التحقّق من حقلَي code وmessage لمعرفة الخطأ الذي حدث.

ستؤدي أي أخطاء لم تتم معالجتها إلى تعطُّل تطبيقك.

لمزيد من المعلومات، يُرجى الاطّلاع على معالجة الأخطاء.

Structure API

يمثّل Home رسم Home البياني، وهو نقطة الدخول إلى Structure API. وتوفّر مراجع للمباني والغرف والأجهزة.

يمثّل "Structure" بنية في Home Graph. وتتيح الوصول إلى البيانات الوصفية للمنزل، مثل id وname.

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

واجهة برمجة التطبيقات الوصف
stream() تعرض هذه السمة Publisher يُصدر كل كائن على حدة عند حدوث تغييرات.
batched() تعرض هذه السمة Publisher يعرض النتيجة الحالية كـ Set من العناصر. يمثّل كل Set يتم إصداره الحالة الحالية للرسم البياني للكائن.
list() تعرض النتيجة الحالية كـ Set من العناصر.

قد لا يعرض طلب structures().list() على الفور مجموعة صالحة من البُنى. إذا كان تطبيقك تفاعليًا ويستدعي stream() للاشتراك في جميع تغييرات البنية لتشغيل واجهة المستخدم، يجب عرض قائمة صالحة بالبُنى في النهاية. هناك حالات أخرى قد يتم فيها عرض قائمة فارغة بالبُنى، على سبيل المثال، إذا فقد هاتف المستخدم إمكانية الاتصال أو إذا ألغى المستخدم الأذونات الممنوحة لتطبيقك. يجب التأكّد من معالجة هذه الحالات في تطبيقك.

@Published public private(set) var structures: [Structure] = []
private var structuresCancellable: AnyCancellable?

  self.structuresCancellable = home
    .structures()
    .batched()
    .receive(on: DispatchQueue.main)
    .map { Array($0) }
    .catch {
      // Failed to load structures
      return Just([Structure]())
    }
    .assign(to: \.structures, on: self)

نموذج عن مكالمات Structure

الحصول على مجموعة من البُنى

يؤدي استدعاء list() في Query<Structure> إلى عرض أحدث مجموعة من العناصر:

// Get a stream of all structures accessible to the user
let allStructuresChanges = self.home.structures()
let allStructures = try? await allStructuresChanges.list()

عند تصميم تطبيق تفاعلي، عليك استخدام طلبات batched() وstream() بدلاً من list()، لأنّها تنتج البيانات تلقائيًا عند تغيير الرسم البياني للمنزل.

الحصول على خصائص البنية

باستخدام قائمة الهياكل المتوفرة، يمكنك الوصول إلى خصائصها:

// Get a stream of changes taking place on a structure.
let structureChanges = try await home.structures().list().filter { $0.id == structureID }

// Get a snapshot of the structure.
let structure = structureChanges.first!

// Get structure properties
print("id \(structure.id) ")
print("name \(structure.name) ")

العثور على منزل حسب الاسم

إذا كنت تعرف اسم البنية، يمكنك أيضًا الوصول إليها باستخدام السمة name:

do {
  structure1 = try await home.structures().list().first(where: { $0.name == "Main House" })
} catch let _ as HomeError {
  // Code for handling the exception
}

ومن هناك، يمكن الوصول إلى المواقع والغرف والأجهزة لكل بنية.

العمل مع هياكل متعددة

لاستخدام أكثر من بنية واحدة، احصل على مرجع منفصل لكل بنية:

var structure1: Structure!
var structure2: Structure!
do {
  structure1 = try await home.structures().list().first(where: { $0.name == "Main House" })
} catch let _ as HomeError {
  // Code for handling the exception
}
do {
  structure2 = try await home.structures().list().first(where: { $0.name == "Guest Cottage" })
} catch let _ as HomeError {
  // Code for handling the exception
}

الغُرف

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

استخدِم Home.rooms() لاسترداد جميع الغرف في الحساب، ثم استخدِم roomID = device.roomID لعرض الأجهزة المقابلة في كل غرفة.

self.home.rooms().batched()
  .combineLatest(self.home.devices().batched())
  .receive(on: DispatchQueue.main)
  .catch { error in
    // Failed to load rooms and devices
    return Just((Set<Room>(), Set<HomeDevice>()))
  }
  .map { rooms, devices in
    var devicesByRoom = [Room: [HomeDevice]]()
    for room in rooms where room.structureID == currentStructureID {
      devicesByRoom[room] = devices.filter { $0.roomID == room.id }
    }
    return devicesByRoom
  }.assign(to: &self.$devicesByRoom)

مكالمات نموذجية في الغرفة

الحصول على قائمة بالغرف

باستخدام فئة Home، يمكنك الحصول على قائمة بالغرف والوصول إلى خصائصها:

let allRoomsChanges = self.home.rooms()
let allRooms = try await allRoomsChanges.list()
let room = allRooms.first!
XCTAssertTrue(allRooms.contains(room))

print("id \(room.id) ")
print("name \(room.name) ")

إنشاء غرفة

لإنشاء غرفة جديدة في Structure:

let testName = "Test Room Name"
var newRoom: Room!
do {
  newRoom = try await structure.createRoom(name: testName)
  XCTAssertNotNil(newRoom)
} catch let _ as HomeError {
  // Code for handling the exception
}

حذف غرفة

أو يمكنك بدلاً من ذلك حذف غرفة:

val roomToDelete = structure.rooms().list().filter { it.name == "room_id1" }.firstOrNull()
    structure.deleteRoom(roomToDelete!!)

يمكنك أيضًا حذف غرفة باستخدام معرّفها:

let roomToDelete = allRooms.first(where: { $0.id == room.id })
if let roomToDelete1 = roomToDelete {
  do {
    try await structure.deleteRoom(roomToDelete1)
  } catch let _ as HomeError {
    // Code for handling the exception
  }
}

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

نقل الأجهزة إلى غرفة مختلفة

يتيح لك تطبيق "Structure" أيضًا نقل جهاز إلى غرفة مختلفة:

do {
  try await structure.move(device: light, to: room)
} catch let _ as HomeError {
  // Code for handling the exception
}

تغيير اسم غرفة

استخدِم طريقة setName(_:) لتغيير اسم الغرفة:

let updatedRoom = try await theRoom.setName("new room name")

عند تغيير اسم غرفة، تظل بنية Room الأصلية كما هي، ويظهر التغيير في عنصر Room المعدَّل الذي يتم عرضه.

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

مجموعات الأجهزة

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

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

يتم تمثيل مجموعات الأجهزة من خلال الكيان DeviceGroup، وهو يختلف عن HomeDevice. للتفاعل مع مجموعات الأجهزة، يجب تضمين أنواع الأجهزة وسماتها ذات الصلة عند ضبط ReferencedAutomationTypes في Home.configure، بالإضافة إلى السمات GroupTrait وGroupManagementTrait وGroupMembershipTrait.

يمكنك التفاعل مع الأجهزة في مجموعة إما من خلال تكرار أجهزتها الأعضاء مباشرةً (راجِع التفاعل مع الأجهزة الأعضاء في المجموعة) أو من خلال استهداف جزء من نوع الجهاز على DeviceGroup (راجِع التحكّم في مجموعة أجهزة).

الحصول على قائمة بمجموعات الأجهزة

للاستعلام عن جميع مجموعات الأجهزة في المنزل:

let groups = try await home.entities(DeviceGroup.self).list()
for group in groups {
  let groupTrait = await group.traits.get(Google.GroupTrait.self)
  let name = groupTrait?.attributes.name ?? "Device Group"
  print("Found group: \(name) with ID: \(group.id)")
}

التفاعل مع أجهزة أعضاء المجموعة

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

أولاً، عليك فلترة الأجهزة في المنزل من خلال التحقّق من GroupMembershipTrait لكل جهاز بحثًا عن رقم تعريف المجموعة المستهدَفة:

let allDevices = try await home.devices().list()
var members: [HomeDevice] = []
for device in allDevices {
  if let membership = await device.traits.get(
    Google.GroupMembershipTrait.self
  ),
  membership.attributes.groupObjectIdsArray.contains(group.id) {
    members.append(device)
  }
}

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

var onOffTraits: [Matter.OnOffTrait] = []
for device in members {
  if let light = await device.types.get(OnOffLightDeviceType.self),
     let onOff = light.matterTraits.onOffTrait {
    onOffTraits.append(onOff)
  }
}

let traitsToUse = onOffTraits
let responses = try await home.sendBatchedCommands { batcher in
  return try traitsToUse.map { batcher.add(try $0.onBatchable()) }
}

for response in responses {
  try response.getOrThrow()
}

يمكنك أيضًا تعديل سمة في جميع الأجهزة الأعضاء المطابقة في مجموعة (على سبيل المثال، ضبط onTransitionTime على 1 في جميع أعضاء DimmableLightDeviceType) في الوقت نفسه وانتظار اكتمال العملية خلال موعد نهائي:

try await withThrowingTaskGroup(of: Void.self) { taskGroup in
  taskGroup.addTask {
    try await Task.sleep(for: .seconds(5))
    throw CancellationError()
  }

  for device in members {
    taskGroup.addTask {
      guard let light = await device.types.get(DimmableLightDeviceType.self),
            let levelControl = light.matterTraits.levelControlTrait,
            levelControl.attributes.$onTransitionTime.isSupported
      else { return }
      _ = try await levelControl.update {
        $0.setOnTransitionTime(1)
      }
    }
  }

  for _ in 0..<members.count {
    try await taskGroup.next()
  }
  taskGroup.cancelAll()
}

لتجميع سمة معيّنة في جميع أجهزة المجموعة، مثل العثور على الحد الأدنى لـ colorTempPhysicalMaxMireds في جميع المصابيح التي تتيح ضبط درجة حرارة الألوان في المجموعة:

var maxMiredsValues: [UInt16] = []
for device in members {
  if let light = await device.types.get(ColorTemperatureLightDeviceType.self),
     let mireds = light.matterTraits.colorControlTrait?.attributes
       .colorTempPhysicalMaxMireds {
    maxMiredsValues.append(mireds)
  }
}
let minMaxMireds = maxMiredsValues.min()

إنشاء مجموعة أجهزة

استخدِم GroupManagementTrait في البنية لإنشاء مجموعة أجهزة:

let groupsManagementTrait =
  try await structure.traits(Google.GroupManagementTrait)
let _ = try await groupsManagementTrait.createUserDefinedGroup(
  withName: "Kitchen Lights",
  withDevices: [light1, light2]
)

إدارة عضوية المجموعة

لإضافة أعضاء إلى مجموعة حالية:

let groupManagementTrait =
  try await structure.traits(Google.GroupManagementTrait.self)
groupManagementTrait.addGroupMembers(
  groupObjectId: group.id,
  memberDeviceObjectIds = [light3.id],
)

لإزالة أعضاء من مجموعة، اتّبِع الخطوات التالية:

let groupManagementTrait =
  try await structure.traits(Google.GroupManagementTrait.self)
_ = try await groupManagementTrait.removeGroupMembers(
  groupObjectId: group.id,
  memberDeviceObjectIdsArray: Array(removedIDs)
)

تغيير اسم مجموعة أجهزة

لإعادة تسمية مجموعة أجهزة، عدِّل حقل name في GroupTrait:

let groupTrait = group.traits.get(Google.GroupTrait.self)
_ = try await groupTrait.update { $0.name = "Movie Corner" }

حذف مجموعة أجهزة

لحذف مجموعة أجهزة من بنية، اتّبِع الخطوات التالية:

let groupManagementTrait =
  try await structure.traits.get(Google.GroupManagementTrait.self)

_ = try await groupManagementTrait.deleteGroup(groupObjectId: group.id)