إنشاء عملية تشغيل آلي

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

بعد منح الأذونات لبنية معيّنة، استورِد الحِزم التالية إلى تطبيقك:


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

تحتوي البنية على واجهة HasAutomations تتضمّن الطرق التالية المتعلّقة بالتشغيل الآلي:

واجهة برمجة التطبيقات الوصف
automations() إدراج جميع عمليات التشغيل الآلي التي تنتمي إلى البنية لا يتم عرض سوى عمليات التشغيل الآلي التي أنشأتها من خلال واجهات برمجة تطبيقات Home.
createAutomation(automation) أنشئ مثيلًا للتشغيل الآلي لبنية معيّنة.
deleteAutomation(automationId) حذف مثيل التشغيل الآلي حسب رقم تعريفه

إنشاء عملية تشغيل آلي

بعد إنشاء مثيل من Home وتلقّي الأذونات من المستخدم، يمكنك الحصول على البنية والأجهزة:

val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!

بعد ذلك، حدِّد منطق التشغيل الآلي باستخدام Automation DSL. في واجهات برمجة التطبيقات Home APIs، يتم تمثيل التشغيل الآلي من خلال واجهة Automation. تحتوي هذه الواجهة على مجموعة من السمات:

  • البيانات الوصفية، مثل الاسم والوصف
  • علامات تشير، على سبيل المثال، إلى ما إذا كان يمكن تنفيذ الإجراء المبرمَج أم لا
  • قائمة بالعقد التي تحتوي على منطق التشغيل الآلي، وتُعرف باسم الرسم البياني للتشغيل الآلي، ويتم تمثيلها من خلال السمة automationGraph.

يكون automationGraph تلقائيًا من النوع SequentialFlow، وهو فئة تحتوي على قائمة بالعقد التي يتم تنفيذها بترتيب تسلسلي. تمثّل كل عقدة عنصرًا من الإجراء المبرمَج، مثل عنصر بدء أو شرط أو إجراء.

خصِّص name وdescription للعملية المبرمَجة.

يؤدي إنشاء عملية آلية إلى ضبط العلامة isActive تلقائيًا على true، وبالتالي ليس من الضروري ضبط هذه العلامة صراحةً ما لم تكن تريد في البداية إيقاف العملية الآلية. في هذا السيناريو، اضبط العلامة على false أثناء الإنشاء.

تُستخدَم واجهة DraftAutomation لإنشاء عمليات التشغيل الآلي وبنائها، بينما تُستخدَم واجهة Automation لاستردادها. على سبيل المثال، إليك لغة برمجة برمجة التشغيل الآلي لتشغيل جهاز عند إشعال جهاز آخر:

import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Condition
import com.google.home.automation.DraftAutomation
import com.google.home.automation.Equals
import com.google.home.automation.Node
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.matter.standard.OnOff
import com.google.home.Structure

...

val automation: DraftAutomation = automation {
  name = "MyFirstAutomation"
  description = "Turn on a device when another device is turned on."
  sequential {
    val starterNode = starter<_>(device1, OnOffLightDevice, trait=OnOff)
    condition() { expression = stateReaderNode.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

بعد تحديد لغة وصف النص البرمجي للتشغيل الآلي، يجب تمريرها إلى createAutomation() لإنشاء مثيل DraftAutomation:

val createdAutomation = structure.createAutomation(automation)

من هنا، يمكنك استخدام جميع طرق التشغيل الآلي الأخرى في التشغيل الآلي، مثل execute() وstop() وupdate().

أخطاء التحقق من الصحة

إذا لم يجتَز إنشاء الإجراء المبرمَج عملية التحقّق، ستعرض رسالة تحذير أو خطأ معلومات عن المشكلة. لمزيد من المعلومات، يُرجى الرجوع إلى مرجع ValidationIssueType.

أمثلة على الرموز البرمجية

في ما يلي بعض أمثلة الرموز البرمجية التي يمكن استخدامها لتنفيذ أجزاء من العمليات الآلية الافتراضية الموضّحة في صفحة تصميم عملية آلية.

التشغيل الآلي البسيط

يمكن تنفيذ عملية آلية ترفع الستائر في الساعة 8:00 صباحًا على النحو التالي:

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// determine whether a scheduled automation can be constructed
val isSchedulingSupported =
  allCandidates.any {
    it is EventCandidate &&
      it.eventFactory == Time.ScheduledTimeEvent &&
      it.unsupportedReasons.isEmpty()
  }
// get the blinds present in the structure
val blinds =
  allCandidates
    .filter {
      it is CommandCandidate &&
        it.commandDescriptor == WindowCoveringTrait.UpOrOpenCommand &&
        it.unsupportedReasons.isEmpty()
    }
    .map { it.entity }
    .filterIsInstance<HomeDevice>()
    .filter { it.has(WindowCoveringDevice) }
 if (isSchedulingSupported && blinds.isNotEmpty()) {
  // Proceed to create automation
  val automation: DraftAutomation = automation {
    name = "Day time open blinds"
    description = "Open all blinds at 8AM everyday"
    isActive = true
    sequential {
      // At 8:00am local time....
      val unused =
        starter(structure, Time.ScheduledTimeEvent) {
          parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(8, 0, 0, 0)))
        }
        // ...open all the blinds
       parallel {
        for (blind in blinds) {
          action(blind, WindowCoveringDevice) { command(WindowCovering.upOrOpen()) }
        }
      }
    }
  }
   val createdAutomation = structure.createAutomation(automation)
} else if (!isSchedulingSupported) {
  // Cannot create automation. Set up your address on the structure, then try again.
} else {
  // You don't have any WindowCoveringDevices. Try again after adding some blinds to your structure.
}

التشغيل الآلي المعقد

يمكن تنفيذ عملية التشغيل الآلي التي تؤدي إلى تشغيل أضواء وامض عند رصد الحركة على النحو التالي:

import com.google.home.Home
import com.google.home.HomeClient
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.equals
import com.google.home.automation.parallel
import com.google.home.automation.starter
import com.google.home.google.AssistantBroadcast
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.toggle
import com.google.home.matter.standard.OnOffLightDevice
import java.time.Duration

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()

// get the lights present in the structure
val availableLights = allCandidates.filter {
   it is CommandCandidate &&
   it.commandDescriptor == OnOffTrait.OnCommand
}.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter {it.has(OnOffLightDevice) ||
         it.has(ColorTemperatureLightDevice) ||
         it.has(DimmableLightDevice) ||
         it.has(ExtendedColorLightDevice)}

val selectedLights = ... // user selects one or more lights from availableLights

automation {
isActive = true

sequential {
   // If the presence state changes...
   val starterNode = starter<_>(structure, AreaPresenceState)
   // ...and if the area is occupied...
   condition() {
      expression = starterNode.presenceState equals PresenceState.PresenceStateOccupied
   }
   // "blink" the light(s)
   parallel {
            for(light in selectedLights) {
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle())}
         }
      }
   }
}

التشغيل الآلي العام

يوضّح الرمز البرمجي التالي كيفية جمع بيانات عن منزل المستخدم باستخدام Structure API وDevice API و Discovery API لتنفيذ تطبيق يرشد المستخدم خلال إنشاء عملية التشغيل الآلي العامة الخاصة به (على النحو الموضّح في تصميم عملية التشغيل الآلي: عملية التشغيل الآلي العامة).

val structureFlow: Flow<Structure> = home.structures().itemFlow(myStructureId)

// Get a snapshot of the structure.
val structure: Structure = structureFlow.first()

   // map of devices where key is device id
val devicesById = mutableMapOf<Id,HomeDevice>()

home.devices().list().map { devicesById.put(it.id, it) }

// map of rooms where key is room id
val roomsByName: MutableMap<String,Room?> = mutableMapOf<String,Room?>()

structure.rooms().list().map { roomsByName.put(it.name, it) }

// map of commands where key is trait id
val commandsByTrait: MutableMap<String,MutableList<CommandCandidate>?> = mutableMapOf<String,MutableList<CommandCandidate>?>()

// map of commands where key is DeviceType.Metadata.hashcode()
val commandsByDeviceType: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()

// map of commands where key is entity id
val commandsByEntity: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()


// map of stateReaders where key is trait id
val stateReadersByTrait: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is DeviceType.Metadata.hashcode()
val stateReadersByDeviceType: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is  entity id
val stateReadersByEntity: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of starters where key is trait id
val startersByTrait: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// map of starters where key is DeviceType.Metadata.hashcode()
val startersByDeviceType: MutableMap<Int,MutableList<CommandCandidate>?> = mutableMapOf<Int,MutableList<CommandCandidate>?>()

// map of starters where key is entity id
val startersByEntity: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// populate candidate maps
structure.allCandidates().first().map {
  when (it) {
   is CommandCandidate -> {

      // update commandsByTrait
      // TODO refactor into generic function to eliminate duplicate code
      var commandsByTraitList: MutableList<CommandCandidate>? = commandsByTrait.get(it.trait.factory.traitId: String)
      if(commandsByTraitList == null) { commandsByTraitList = arrayListOf<CommandCandidate>() }

      commandsByTraitList.add(it)
      commandsByTrait.put(it.trait.factory.traitId, commandsByTraitList)

      // update commandsByDeviceType
      // TODO refactor into generic function to eliminate duplicate code
      for (t in it.types) {

      //TODO filter out device types not present in the home

      //TODO how to get a reference to device type id? 

      var commandsByDeviceTypeList: MutableList<CommandCandidate>? = commandsByDeviceType.get(t.factory.typeId: Id)

      if (commandsByDeviceTypeList == null)  { commandsByDeviceTypeList = arrayListOf<CommandCandidate>() }

      commandsByDeviceTypeList.add(it)

      commandsByDeviceType.put(t.factory.typeId, commandsByDeviceTypeList)
      }

      // update commandsByEntity
      // TODO refactor into generic function to eliminate duplicate code
     var commandsByEntityList: MutableList<CommandCandidate>? = commandsByEntity.get(it.entity.id: Id)
      if ( commandsByEntityList == null ) {commandsByEntityList = arrayListOf<CommandCandidate>()}
      commandsByEntityList.add(it)
      commandsByEntity.put(it.entity.id, commandsByEntityList)
   }

/*
   is StateReaderCandidate -> {

      // update stateReadersByTrait

      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByTrait.get(it.trait.factory.traitId)
      if(stateReadersList == null) { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.add(it)
      stateReadersByTrait.put(it.trait.factory.traitId, stateReadersList)

   // update stateReadersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByDeviceType.get(t.metadata.hashcode())

      if (stateReadersList == null)  { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.put(it)

      stateReadersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update stateReadersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = stateReadersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)
   }

   */

   /*
   is StarterCandidate  -> {

      // update startersByTrait

      var startersList: MutableList<StateReaderCandidate>? = startersByTrait.get(it.trait.factory.traitId)
      if(startersList == null) { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.add(it)
      startersByTrait.put(it.trait.factory.traitId, startersList)

   // update startersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var startersList: MutableList<StateReaderCandidate>? = startersByDeviceType.get(t.metadata.hashcode())

      if (startersList == null)  { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.put(it)

      startersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update startersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = startersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)

      }
*/
 else -> println("unknown type encountered: " + it)

  }
}

تنفيذ عملية تشغيل آلي

يمكنك تنفيذ عملية تشغيل آلي تم إنشاؤها باستخدام الطريقة التالية: execute() :

createdAutomation.execute()

إذا كان التشغيل الآلي يحتوي على مشغِّل يدوي، يبدأ execute() التشغيل الآلي من تلك النقطة، مع تجاهل جميع العقد التي تسبق المشغِّل اليدوي. إذا لم يكن للتشغيل الآلي مشغّل يدوي، يبدأ التنفيذ من العقدة التي تليها عقدة المشغّل الأولى.

إذا تعذّر تنفيذ عملية execute()، قد يتم طرح HomeException. اطّلِع على معالجة الخطأ.

إيقاف عملية تشغيل آلي

يمكنك إيقاف عملية تشغيل آلي باستخدام طريقة stop():


createdAutomation.stop()

إذا تعذّر تنفيذ عملية stop()، قد يتم طرح HomeException. اطّلِع على معالجة الخطأ.

الحصول على قائمة بعمليات التشغيل الآلي لبنية معيّنة

يتمّ تحديد الإجراءات المبرمَجة على مستوى البنية. يمكنك جمع البيانات من automations() للوصول إلى Flow من عمليات التشغيل الآلي:


import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
structure.automations().collect {
  println("Available automations:")
  for (automation in it) {
    println(String.format("%S %S", "$automation.id", "$automation.name"))
  }
}

بدلاً من ذلك، يمكنك إسنادها إلى Collection محلي:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

var myAutomations: Collection<Automation> = emptyList()
myAutomations = structure.automations()

الحصول على عملية تشغيل آلي حسب رقم التعريف

للحصول على عملية تشغيل آلي حسب معرّف العملية الآلية، يمكنك استدعاء الأسلوب automations() في البنية، والمطابقة حسب المعرّف:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()

الردّ:

// Here's how the automation looks like in the get response. Here, it's represented
// as if calling a println(automation.toString())

Automation(
  name = "automation-name",
  description = "automation-description",
  isActive = true,
  id = Id("automation@automation-id"),
  automationGraph = SequentialFlow(
    nodes = [
      Starter(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@6789..."),
      Action(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@8765...",
        command="on")
    ]))

الحصول على عملية التشغيل الآلي حسب الاسم

يمكن استخدام الأسلوب filter() في Kotlin لتحسين طلبات البيانات من واجهة برمجة التطبيقات. للحصول على عملية تشغيل آلي حسب الاسم، احصل على عمليات التشغيل الآلي للبنية وفلتِر حسب اسم عملية التشغيل الآلي:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().filter {
  it.name.equals('Sunset Blinds') }

الحصول على جميع عمليات التشغيل الآلي لجهاز

للحصول على جميع الإجراءات المبرمَجة التي تشير إلى جهاز معيّن، استخدِم الفلترة المُدمجة لفحص automationGraph لكل إجراء مبرمَج:

import android.util.Log
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Automation.automationGraph
import com.google.home.automation.Node
import com.google.home.automation.ParallelFlow
import com.google.home.automation.SelectFlow
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.automation.StateReader

...

fun collectDescendants(node: Node): List<Node> {
  val d: MutableList<Node> = mutableListOf(node)

  val children: List<Node> =
    when (node) {
      is SequentialFlow -> node.nodes
      is ParallelFlow -> node.nodes
      is SelectFlow -> node.nodes
      else -> emptyList()
    }
  for (c in children) {
    d += collectDescendants(c)
  }
  return d
}

val myDeviceId = "device@452f78ce8-0143-84a-7e32-1d99ab54c83a"
val structure = homeManager.structures().list().single()
val automations =
  structure.automations().first().filter {
    automation: Automation ->
    collectDescendants(automation.automationGraph!!).any { node: Node ->
      when (node) {
        is Starter -> node.entity.id.id == myDeviceId
        is StateReader -> node.entity.id.id == myDeviceId
        is Action -> node.entity.id.id == myDeviceId
        else -> false
      }
    }
  }

تعديل عملية التشغيل الآلي

لتعديل البيانات الوصفية لعملية التشغيل الآلي، يمكنك استدعاء الأسلوب update() ، مع تمريره بتعبير لاmda يضبط البيانات الوصفية:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update { this.name = "Flashing lights 2" }

تتيح طريقة update() استبدال الرسم البياني للتشغيل الآلي بالكامل، ولكن لا تتيح تعديل الرسم البياني لكل عقدة. إنّ التعديل لكلّ عقدة معرّض للخطأ بسبب الاعتماد المتبادل بين العقد. إذا أردت تغيير منطق إحدى الإجراءات المبرمَجة، أنشئ رسمًا بيانيًا جديدًا واستبدِل الرسم البياني الحالي بالكامل.

حذف عملية تشغيل آلي

لحذف عملية مبرمَجة، استخدِم أسلوب deleteAutomation() في البنية. يجب حذف عملية التشغيل الآلي باستخدام معرّفها.

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().first()
structure.deleteAutomation(automation.id)

إذا تعذّر الحذف، قد يتم طرح HomeException. اطّلِع على معالجة الخطأ.

تأثير حذف الجهاز في الإجراءات المبرمَجة

إذا حذف مستخدم جهازًا مستخدَمًا في عملية التشغيل الآلي، لن يتمكّن الجهاز المحذوف من بدء أي عمليات تشغيل، ولن تتمكّن عملية التشغيل الآلي من قراءة السمات منه أو إصدار أوامر إليه. على سبيل المثال، إذا حذف مستخدم جهاز OccupancySensorDevice من منزله، وكان أحد الإجراءات المبرمَجة يتضمّن مشغّلاً يعتمد على OccupancySensorDevice، لن يتمكّن هذا المشغّل من تفعيل الإجراء المبرمَج بعد ذلك.