یک اتوماسیون در اندروید بسازید

APIهای اتوماسیون ممکن است از طریق APIهای خانگی برای اندروید قابل دسترسی باشند، اما از آنجایی که نقطه ورود آنها از طریق یک ساختار است، قبل از استفاده از آنها، ابتدا باید به ساختار اجازه داده شود.

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


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

یک ساختار شامل یک رابط HasAutomations با متدهای خاص اتوماسیون زیر است:

رابط برنامه‌نویسی کاربردی توضیحات
automations() تمام اتوماسیون‌هایی که به ساختار تعلق دارند را فهرست کنید. فقط اتوماسیون‌هایی که از طریق APIهای Home ایجاد کرده‌اید، بازگردانده می‌شوند.
createAutomation(automation) یک نمونه اتوماسیون برای یک ساختار ایجاد کنید.
deleteAutomation(automationId) یک نمونه اتوماسیون را با استفاده از شناسه آن حذف کنید.

ایجاد یک اتوماسیون

پس از ایجاد یک نمونه از Home و دریافت مجوزها از کاربر، ساختار و دستگاه(ها) را دریافت کنید:

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

سپس منطق اتوماسیون خود را با استفاده از Automation DSL تعریف کنید. در APIهای Home، یک اتوماسیون توسط رابط 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 مراجعه کنید.

مثال‌های کد

در اینجا چند نمونه کد ارائه می‌دهیم که می‌توان از آنها برای پیاده‌سازی بخش‌هایی از اتوماسیون‌های فرضی شرح داده شده در صفحه «طراحی اتوماسیون در اندروید» استفاده کرد.

اتوماسیون ساده

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

// 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 رخ دهد. به بخش مدیریت خطا مراجعه کنید.

لیستی از اتوماسیون‌ها را برای یک ساختار دریافت کنید

اتوماسیون‌ها در سطح ساختار تعریف می‌شوند. برای دسترسی به Flow از اتوماسیون‌ها، از 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()
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() در کاتلین می‌تواند برای اصلاح بیشتر فراخوانی‌های 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() در ساختار استفاده کنید. یک اتوماسیون باید با استفاده از شناسه (ID) آن حذف شود.

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