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

يمكن الوصول إلى واجهات Automation APIs من خلال Home APIs لنظام التشغيل Android، ولكن بما أنّ نقطة الدخول إليها تكون من خلال بنية، يجب أولاً منح الإذن بالوصول إلى البنية قبل استخدامها.

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


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. في واجهات برمجة التطبيقات الخاصة بالمنزل، يتم تمثيل عملية التشغيل الآلي من خلال واجهة Automation. تحتوي هذه الواجهة على مجموعة من الخصائص:

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

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

امنح عملية التشغيل الآلي name وdescription.

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

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

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()) }
  }
}

بعد تحديد لغة DSL الخاصة بالأتمتة، مرِّرها إلى طريقة createAutomation() لإنشاء مثيل DraftAutomation:

val createdAutomation = structure.createAutomation(automation)

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

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

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

أمثلة على الرموز

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

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

يمكن تنفيذ عملية تشغيل آلي لرفع الستائر في الساعة 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())}
         }
      }
   }
}

اختيار الأجهزة بشكل ديناميكي باستخدام فلاتر الكيانات

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

على سبيل المثال، باستخدام فلاتر الكيانات، يمكن أن يستهدف التشغيل الآلي ما يلي:

  • جميع الأجهزة من نوع جهاز معيّن
  • جميع الأجهزة في غرفة معيّنة
  • جميع الأجهزة من نوع جهاز معيّن في غرفة معيّنة
  • جميع الأجهزة التي تم تشغيلها
  • جميع الأجهزة التي تم تشغيلها في غرفة معيّنة

لاستخدام فلاتر الكيانات، اتّبِع الخطوات التالية:

  1. على Structure أو Room، اتّصِل بالرقم atExecutionTime(). يؤدي ذلك إلى عرض TypedExpression<TypedEntity<StructureType>>.
  2. في هذا العنصر، استدعِ الدالة getDevicesOfType()، مع تمرير DeviceType إليها.

يمكن استخدام فلاتر الكيانات في أدوات التفعيل وقارئات الحالة والإجراءات.

على سبيل المثال، لجعل أي مصباح مزوّد بمفتاح تشغيل/إيقاف يؤدي إلى تفعيل إجراء مبرمَج من إجراء تفعيل:

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

لالتقاط حالة OnOff جميع الأضواء في بنية (على وجه التحديد، الأضواء المطفأة أو المضاءة) في قارئ الحالة، اتّبِع الخطوات التالية:

// Build a Map<Entity, OnOff>
val onOffStateOfAllLights =
  stateReader(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

للحصول على المصابيح في غرفة معيّنة واستخدامها في شرط:

val livingRoomLights =
  stateReader(
    entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )
// Are any of the lights in the living room on?
condition { expression = livingRoomLights.values.any { it.onOff equals true } }

في وقت التشغيل:

السيناريو النتيجة
لا تستوفي أي أجهزة المعايير في جهاز التفعيل. لا يتم تشغيل عملية التشغيل الآلي.
ما مِن أجهزة تستوفي المعايير في قارئ الحالة. يبدأ التشغيل الآلي ولكنّه لا ينفّذ أي إجراء.
لا تستوفي أي أجهزة المعايير المحدّدة في أحد الإجراءات. يبدأ التشغيل الآلي ولكن لا يتخذ الإجراء أي خطوات.

المثال التالي هو عملية تشغيل آلي لإطفاء جميع الأضواء باستثناء ضوء الممر كلما تم إطفاء ضوء فردي:

val unused = automation {
  sequential {
    // If any light is turned on or off
    val starter =
      starter(
        entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
        trait = OnOff,
      )
    condition {
      // Check to see if the triggering light was turned off
      expression = starter.onOff equals false
    }
    // Turn off all lights except the hall light
    action(
      entityExpression =
        structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter {
          it notEquals entity(hallwayLight, OnOffLightDevice)
        }
    ) {
      command(OnOff.on())
    }
  }
}

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

تشغيل عملية تشغيل آلي تم إنشاؤها باستخدام طريقة 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() ، مع تمرير تعبير lambda يضبط البيانات الوصفية:

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() استبدال الرسم البياني للتشغيل الآلي بالكامل، ولكن لا تتيح تعديل الرسم البياني على مستوى كل عقدة. تتسبّب التبعيات المتبادلة بين العُقد في حدوث أخطاء عند تعديل كل عقدة على حدة. إذا أردت تغيير منطق عملية تشغيل آلي، أنشئ رسمًا بيانيًا جديدًا واستبدِل الرسم الحالي به بالكامل.

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: Automation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update {
  this.automationGraph = sequential {
    val laundryWasherCompletionEvent =
      starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
    condition {
      expression =
        laundryWasherCompletionEvent.completionErrorCode equals
          // UByte 0x00u means NoError
          0x00u
    }
    action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
    }
  }
}

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

لحذف عملية تشغيل آلي، استخدِم طريقة 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، لن يتمكّن هذا المشغّل من تفعيل التشغيل الآلي.