יצירת פעולה אוטומטית ב-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 עם השיטות הספציפיות ל-Automation הבאות:

API תיאור
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 משמש לאחזור. לדוגמה, זהו ה-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())}
         }
      }
   }
}

הפעלת פעולה אוטומטית

מריצים פעולה אוטומטית שנוצרה באמצעות השיטה 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()

אחזור פעולה אוטומטית לפי מזהה

כדי לקבל אוטומציה לפי מזהה האוטומציה, צריך לבצע קריאה ל-method‏ 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")
    ]))

אחזור של פעולה אוטומטית לפי שם

אפשר להשתמש ב-method‏ 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() שלו ולהעביר לה ביטוי 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")) }
    }
  }
}

מחיקת פעולה אוטומטית

כדי למחוק פעולה אוטומטית, משתמשים ב-method‏ 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, הגורם הזה לא יוכל יותר להפעיל את האוטומציה.