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

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

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


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

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

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

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

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

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

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

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

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

حدِّد name وdescription للتشغيل الآلي.

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

تُستخدَم واجهة DraftAutomation لإنشاء عمليات التشغيل الآلي، بينما تُستخدَم واجهة 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.

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

يجب دائمًا التحقّق من isValid والتحقّق من validationIssues في مثيل Automation الذي تم عرضه:

val createdAutomation = structure.createAutomation(automation)

if (!createdAutomation.isValid) {
  // Iterate through validation issues to identify errors and warnings
  for (issue in createdAutomation.validationIssues) {
    when (issue.severity) {
      ValidationIssueSeverity.ERROR -> {
        Log.e(
          "Automation",
          "Validation error on node ${issue.node}: ${issue.issueType}"
        )
        // Handle error (for example, prompt the user to enable missing
        // consents or device features)
      }
      ValidationIssueSeverity.WARNING -> {
        Log.w(
          "Automation",
          "Validation warning on node ${issue.node}: ${issue.issueType}"
        )
      }
      else -> {}
    }
  }
}

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

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