ساختن خودکارسازی در Android

ازطریق Home APIs برای Android می‌توان به Automation APIs دسترسی داشت، اما ازآنجاکه نقطه ورود آن‌ها ازطریق ساختار است، ابتدا باید اجازه در ساختار داده شود تا بتوان از آن‌ها استفاده کرد.

پس‌از اعطای اجازه‌های ساختار، این بسته‌ها را به برنامه خود وارد کنید:


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 نشان داده می‌شود. این میانای شامل مجموعه‌ای از دارایی‌ها است:

  • فراداده، مانند نام و شرح.
  • پرچم‌هایی که برای مثال نشان می‌دهند آیا خودکارسازی می‌تواند اجرا شود یا نه.
  • فهرستی از گره‌هایی که حاوی منطق خودکارسازی هستند، که به آن گراف خودکارسازی می‌گویند و با دارایی 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()) }
  }
}

پس‌از تعریف شدن 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 توضیح داده شده است استفاده شود.

خودکارسازی ساده

خودکارسازی که ساعت ۸:۰۰ صبح کرکره‌ها را بالا می‌برد ممکن است به‌این‌صورت پیاده‌سازی شود:

// 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 می‌توان برای پالایش بیشتر فراخوانی‌های API استفاده کرد. برای دریافت خودکارسازی براساس نام، خودکارسازی‌های ساختار را دریافت کنید و براساس نام خودکارسازی فیلتر کنید:

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() روش آن را فراخوانی کنید و عبارت لامبدایی را که فراداده را تنظیم می‌کند به آن ارسال کنید:

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 وابسته باشد، آن آغازگر دیگر نمی‌تواند خودکارسازی را فعال کند.