با استفاده از APIهای Home در Android، اتوماسیون ایجاد کنید

1. قبل از شروع

این دومین مجموعه کد در ساخت برنامه اندروید با استفاده از APIهای Google Home است. در این نرم‌افزار، نحوه ایجاد اتوماسیون‌های خانگی را بررسی می‌کنیم و نکاتی را در مورد بهترین شیوه‌ها با استفاده از API ارائه می‌کنیم. اگر هنوز اولین لبه کد را تکمیل نکرده‌اید، یک برنامه تلفن همراه با استفاده از APIهای Home در Android بسازید ، توصیه می‌کنیم قبل از راه‌اندازی این کد، آن را تکمیل کنید.

API های Google Home مجموعه ای از کتابخانه ها را برای توسعه دهندگان اندروید برای کنترل دستگاه های خانه هوشمند در اکوسیستم Google Home فراهم می کند. با این API های جدید، توسعه دهندگان می توانند اتوماسیون هایی را برای یک خانه هوشمند تنظیم کنند که می تواند قابلیت های دستگاه را بر اساس شرایط از پیش تعریف شده کنترل کند. Google همچنین یک Discovery API ارائه می‌کند که به شما امکان می‌دهد دستگاه‌ها را جستجو کنید تا بفهمید از چه ویژگی‌ها و دستوراتی پشتیبانی می‌کنند.

پیش نیازها

چیزی که یاد خواهید گرفت

  • نحوه ایجاد اتوماسیون برای دستگاه های خانه هوشمند با استفاده از Home API.
  • نحوه استفاده از Discovery API برای کشف قابلیت‌های دستگاه پشتیبانی‌شده.
  • نحوه به کارگیری بهترین شیوه ها هنگام ساخت برنامه های خود با API های Home.

2. راه اندازی پروژه خود

نمودار زیر معماری یک برنامه Home APIs را نشان می دهد:

معماری APIهای Home برای یک برنامه اندروید

  • کد برنامه: کد اصلی که توسعه دهندگان برای ایجاد رابط کاربری برنامه و منطق تعامل با Home APIs SDK روی آن کار می کنند.
  • Home APIs SDK: Home APIs SDK ارائه شده توسط Google با سرویس Home APIs در GMSCore برای کنترل دستگاه‌های خانه هوشمند کار می‌کند. توسعه‌دهندگان برنامه‌هایی را می‌سازند که با APIهای Home کار می‌کنند و آنها را با Home APIs SDK ترکیب می‌کنند.
  • GMSCore در Android: GMSCore، همچنین به عنوان خدمات Google Play شناخته می‌شود، یک پلتفرم Google است که خدمات سیستم اصلی را ارائه می‌کند و عملکردهای کلیدی را در همه دستگاه‌های Android تأیید شده فعال می‌کند. ماژول خانه سرویس‌های Google Play شامل سرویس‌هایی است که با APIهای Home تعامل دارند.

در این نرم‌افزار کد، آنچه را که در ساخت اپلیکیشن موبایل با استفاده از APIهای Home در Android پوشش داده‌ایم، می‌سازیم.

مطمئن شوید که ساختاری دارید که حداقل دو دستگاه پشتیبانی شده راه‌اندازی شده و روی حساب کار می‌کنند. همانطور که ما قصد داریم اتوماسیون‌ها را در این کد لبه راه‌اندازی کنیم (تغییر در وضعیت یک دستگاه باعث ایجاد یک عمل در دیگری می‌شود) برای دیدن نتایج به دو دستگاه نیاز دارید.

برنامه نمونه را دریافت کنید

کد منبع برای برنامه نمونه در GitHub در مخزن google-home/google-home-api-sample-app-android موجود است.

این کد لبه از نمونه هایی از شاخه codelab-branch-2 از برنامه نمونه استفاده می کند.

به جایی که می خواهید پروژه را ذخیره کنید بروید و شاخه codelab-branch-2 را شبیه سازی کنید:

$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git

توجه داشته باشید که این شاخه ای متفاوت از آن است که در ساخت برنامه تلفن همراه با استفاده از Home API در Android استفاده می شود. این شاخه از پایگاه کد بر اساس جایی است که اولین لبه کد متوقف شد. این بار، مثال ها شما را با نحوه ایجاد اتوماسیون آشنا می کنند. اگر نسخه کد قبلی را تکمیل کردید و توانستید همه عملکردها را اجرا کنید، می توانید به جای استفاده از codelab-branch-2 از همان پروژه Android Studio برای تکمیل این کد لبه استفاده کنید.

هنگامی که کد منبع را کامپایل کردید و برای اجرا در دستگاه تلفن همراه خود آماده کردید، به بخش بعدی ادامه دهید.

3. درباره اتوماسیون ها بیاموزید

اتوماسیون‌ها مجموعه‌ای از عبارات «اگر این، پس آن» هستند که می‌توانند وضعیت‌های دستگاه را بر اساس فاکتورهای انتخاب‌شده به صورت خودکار کنترل کنند. توسعه دهندگان می توانند از اتوماسیون ها برای ایجاد ویژگی های تعاملی پیشرفته در API های خود استفاده کنند.

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

  1. شروع - شرایط اولیه ای را که اتوماسیون را فعال می کند، مانند تغییر به یک مقدار مشخصه، تعریف می کند. یک اتوماسیون باید یک استارت داشته باشد.
  2. شرط - هر گونه محدودیت اضافی برای ارزیابی پس از راه اندازی اتوماسیون. عبارت در یک شرط باید به درستی ارزیابی شود تا اقدامات یک اتوماسیون اجرا شود.
  3. Action - دستورات یا به‌روزرسانی‌های حالتی که در صورت برآورده شدن همه شرایط انجام می‌شوند.

به عنوان مثال، می‌توانید یک اتوماسیون داشته باشید که با روشن کردن یک سوئیچ، چراغ‌های اتاق را کم‌نور می‌کند، در حالی که تلویزیون آن اتاق روشن است. در این مثال:

  • شروع کننده - سوئیچ در اتاق تغییر می کند.
  • وضعیت - وضعیت خاموش تلویزیون روشن ارزیابی می شود.
  • اقدام - چراغ‌ها در همان اتاقی که سوئیچ وجود دارد کم‌نور است.

این گره ها توسط موتور اتوماسیون به صورت سریال یا موازی ارزیابی می شوند.

image5.png

یک جریان متوالی شامل گره هایی است که به ترتیب اجرا می شوند. به طور معمول، اینها شروع کننده، شرط و عمل هستند.

image6.png

یک جریان موازی ممکن است دارای چندین گره عملی باشد که به طور همزمان اجرا می شوند، مانند روشن کردن چندین چراغ به طور همزمان. گره هایی که یک جریان موازی را دنبال می کنند تا زمانی که تمام شاخه های جریان موازی به پایان نرسند اجرا نمی شوند.

انواع دیگری از گره ها در طرح اتوماسیون وجود دارد. می‌توانید در بخش Nodes در راهنمای توسعه‌دهندگان Home APIs درباره آنها اطلاعات بیشتری کسب کنید. علاوه بر این، توسعه دهندگان می توانند انواع مختلفی از گره ها را برای ایجاد اتوماسیون های پیچیده ترکیب کنند، مانند موارد زیر:

image13.png

توسعه دهندگان این گره ها را با استفاده از یک زبان دامنه خاص (DSL) که به طور خاص برای اتوماسیون های Google Home ایجاد شده است، در اختیار موتور اتوماسیون قرار می دهند.

اتوماسیون DSL را کاوش کنید

یک زبان دامنه خاص (DSL) زبانی است که برای ثبت رفتار سیستم در کد استفاده می شود. کامپایلر کلاس‌های داده‌ای را تولید می‌کند که به صورت سریال بافر پروتکل JSON، و برای برقراری تماس با سرویس‌های اتوماسیون Google استفاده می‌شوند.

DSL به دنبال طرح زیر است:

automation {
name = "AutomationName"
  description = "An example automation description."
  isActive = true
    sequential {
    val onOffTrait = starter<_>(device1, OnOffLightDevice, OnOff)
    condition() { expression = onOffTrait.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

اتوماسیون در مثال قبل دو لامپ را همگام می کند. هنگامی که وضعیت OnOff device1 به On تغییر می کند ( onOffTrait.onOff equals true )، سپس وضعیت OnOff (OnOff) device2 به On ( command(OnOff.on() ) تغییر می کند.

هنگامی که با اتوماسیون ها کار می کنید، بدانید که محدودیت های منابع وجود دارد.

اتوماسیون ها ابزار بسیار مفیدی برای ایجاد قابلیت های خودکار در یک خانه هوشمند هستند. در ابتدایی ترین حالت استفاده، می توانید به صراحت یک اتوماسیون را برای استفاده از دستگاه ها و ویژگی های خاص کدنویسی کنید. اما یک مورد کاربردی تر، موردی است که در آن برنامه به کاربر اجازه می دهد دستگاه ها، دستورات و پارامترهای یک اتوماسیون را پیکربندی کند. بخش بعدی نحوه ایجاد یک ویرایشگر اتوماسیون را توضیح می دهد که به کاربر امکان می دهد دقیقاً این کار را انجام دهد.

4. یک ویرایشگر اتوماسیون بسازید

در Sample App، یک ویرایشگر اتوماسیون ایجاد خواهیم کرد که با آن کاربران می‌توانند دستگاه‌ها، قابلیت‌ها (عملکردهایی) را که می‌خواهند استفاده کنند، و نحوه راه‌اندازی خودکارها با استفاده از استارت‌ها را انتخاب کنند.

img11-01.pngimg11-02.pngimg11-03.pngimg11-04.png

استارت ها را تنظیم کنید

استارت اتوماسیون نقطه ورود اتوماسیون است. هنگامی که یک رویداد خاص اتفاق می افتد، یک شروع کننده یک اتوماسیون را راه اندازی می کند. در Sample App، ما شروع کننده های اتوماسیون را با استفاده از کلاس StarterViewModel که در فایل منبع StarterViewModel.kt یافت می شود، می گیریم و نمای ویرایشگر را با استفاده از StarterView ( StarterView.kt ) نمایش می دهیم.

یک گره شروع به عناصر زیر نیاز دارد:

  • دستگاه
  • صفت
  • عملیات
  • ارزش

دستگاه و صفت را می توان از اشیاء بازگردانده شده توسط Devices API انتخاب کرد. دستورات و پارامترهای مربوط به هر دستگاه پشتیبانی شده موضوع پیچیده تری هستند که باید به طور جداگانه بررسی شوند.

برنامه یک لیست از پیش تنظیم شده از عملیات را تعریف می کند:

   // List of operations available when creating automation starters:
enum class Operation {
  EQUALS,
  NOT_EQUALS,
  GREATER_THAN,
  GREATER_THAN_OR_EQUALS,
  LESS_THAN,
  LESS_THAN_OR_EQUALS
    }

سپس برای هر صفت پشتیبانی شده، عملیات پشتیبانی شده را پیگیری می کند:

// List of operations available when comparing booleans:
 object BooleanOperations : Operations(listOf(
     Operation.EQUALS,
     Operation.NOT_EQUALS
 ))
// List of operations available when comparing values:
object LevelOperations : Operations(listOf(
    Operation.GREATER_THAN,
    Operation.GREATER_THAN_OR_EQUALS,
    Operation.LESS_THAN,
    Operation.LESS_THAN_OR_EQUALS
))

به روشی مشابه، برنامه Sample مقادیر قابل تخصیص به صفات را ردیابی می کند:

enum class OnOffValue {
   On,
   Off,
}
enum class ThermostatValue {
  Heat,
  Cool,
  Off,
}

و نگاشت بین مقادیر تعریف شده توسط برنامه و مقادیر تعریف شده توسط API ها را پیگیری می کند:

val valuesOnOff: Map Boolean> = mapOf(
  OnOffValue.On to true,
  OnOffValue.Off to false,
)
val valuesThermostat: Map ThermostatTrait.SystemModeEnum> = mapOf(
  ThermostatValue.Heat to ThermostatTrait.SystemModeEnum.Heat,
  ThermostatValue.Cool to ThermostatTrait.SystemModeEnum.Cool,
  ThermostatValue.Off to ThermostatTrait.SystemModeEnum.Off,
)

سپس برنامه مجموعه ای از عناصر نمایش را نمایش می دهد که کاربران می توانند از آنها برای انتخاب فیلدهای مورد نیاز استفاده کنند.

مرحله 4.1.1 را در فایل StarterView.kt برای رندر کردن همه دستگاه‌های آغازگر و پیاده‌سازی callback کلیک در یک DropdownMenu از حالت نظر خارج کنید:

val deviceVMs: List = structureVM.deviceVMs.collectAsState().value
...
DropdownMenu(expanded = expandedDeviceSelection, onDismissRequest = { expandedDeviceSelection = false }) {
// TODO: 4.1.1 - Starter device selection dropdown
// for (deviceVM in deviceVMs) {
//     DropdownMenuItem(
//         text = { Text(deviceVM.name) },
//         onClick = {
//             scope.launch {
//                 starterDeviceVM.value = deviceVM
//                 starterType.value = deviceVM.type.value
//                 starterTrait.value = null
//                 starterOperation.value = null
//             }
//             expandedDeviceSelection = false
//         }
//     )
// }
}

مرحله 4.1.2 را در فایل StarterView.kt برای رندر کردن تمام ویژگی‌های دستگاه شروع و اجرای callback کلیک در یک DropdownMenu از حالت نظر خارج کنید:

// Selected starter attributes for StarterView on screen:
val starterDeviceVM: MutableState = remember {
mutableStateOf(starterVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.1.2 - Starter device traits selection dropdown
// val deviceTraits = starterDeviceVM.value?.traits?.collectAsState()?.value!!
// for (trait in deviceTraits) {
//     DropdownMenuItem(
//         text = { Text(trait.factory.toString()) },
//         onClick = {
//             scope.launch {
//                 starterTrait.value = trait.factory
//                 starterOperation.value = null
//             }
//             expandedTraitSelection = false
//         }
//     )
}
}

مرحله 4.1.3 را در فایل StarterView.kt برای رندر کردن تمام عملیات صفت انتخاب شده و اجرای callback کلیک در یک DropdownMenu از حالت نظر خارج کنید:

val starterOperation: MutableState = remember {
  mutableStateOf(starterVM.operation.value) }
  ...
  DropdownMenu(expanded = expandedOperationSelection, onDismissRequest = { expandedOperationSelection = false }) {
    // ...
    if (!StarterViewModel.starterOperations.containsKey(starterTrait.value))
    return@DropdownMenu
    // TODO: 4.1.3 - Starter device trait operations selection dropdown
      // val operations: List = StarterViewModel.starterOperations.get(starterTrait.value ?: OnOff)?.operations!!
    //  for (operation in operations) {
    //      DropdownMenuItem(
    //          text = { Text(operation.toString()) },
    //          onClick = {
    //              scope.launch {
    //                  starterOperation.value = operation
    //              }
    //              expandedOperationSelection = false
    //          }
    //      )
    //  }
}

مرحله 4.1.4 را در فایل StarterView.kt خارج کنید تا همه مقادیر صفت انتخاب شده را رندر کنید و در یک DropdownMenu پاسخ تماس کلیک کنید:

when (starterTrait.value) {
  OnOff -> {
        ...
    DropdownMenu(expanded = expandedBooleanSelection, onDismissRequest = { expandedBooleanSelection = false }) {
// TODO: 4.1.4 - Starter device trait values selection dropdown
//             for (value in StarterViewModel.valuesOnOff.keys) {
//                 DropdownMenuItem(
//                     text = { Text(value.toString()) },
//                     onClick = {
//                         scope.launch {
//                             starterValueOnOff.value = StarterViewModel.valuesOnOff.get(value)
//                         }
//                         expandedBooleanSelection = false
//                     }
//                 )
//             }
             }
              ...
          }
           LevelControl -> {
              ...
      }
   }

مرحله 4.1.5 را در فایل StarterView.kt حذف کنید تا همه متغیرهای ViewModel شروع کننده در ViewModel شروع اتوماسیون پیش نویس ( draftVM.starterVMs ) ذخیره شوند.

val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
// Save starter button:
Button(
enabled = isOptionsSelected && isValueProvided,
onClick = {
  scope.launch {
  // TODO: 4.1.5 - store all starter ViewModel variables into draft ViewModel
  // starterVM.deviceVM.emit(starterDeviceVM.value)
  // starterVM.trait.emit(starterTrait.value)
  // starterVM.operation.emit(starterOperation.value)
  // starterVM.valueOnOff.emit(starterValueOnOff.value!!)
  // starterVM.valueLevel.emit(starterValueLevel.value!!)
  // starterVM.valueBooleanState.emit(starterValueBooleanState.value!!)
  // starterVM.valueOccupancy.emit(starterValueOccupancy.value!!)
  // starterVM.valueThermostat.emit(starterValueThermostat.value!!)
  //
  // draftVM.starterVMs.value.add(starterVM)
  // draftVM.selectedStarterVM.emit(null)
  }
})
{ Text(stringResource(R.string.starter_button_create)) }

اجرای برنامه و انتخاب یک اتوماسیون و استارت جدید باید نمای زیر را نشان دهد:

79beb3b581ec71ec.png

برنامه Sample فقط از استارت‌ها بر اساس ویژگی‌های دستگاه پشتیبانی می‌کند.

اقدامات را تنظیم کنید

عمل اتوماسیون منعکس کننده هدف مرکزی یک اتوماسیون است، اینکه چگونه بر تغییر در دنیای فیزیکی تأثیر می گذارد. در Sample App، اقدامات اتوماسیون را با استفاده از کلاس ActionViewModel ضبط می کنیم و نمای ویرایشگر را با استفاده از کلاس ActionView نمایش می دهیم.

برنامه Sample از موجودیت های Home API زیر برای تعریف گره های اقدام اتوماسیون استفاده می کند:

  • دستگاه
  • صفت
  • فرمان
  • مقدار (اختیاری)

هر عمل فرمان دستگاه از یک فرمان استفاده می کند، اما برخی از آنها به یک مقدار پارامتر مرتبط با آن نیز نیاز دارند، مانند MoveToLevel() و درصد هدف.

دستگاه و صفت را می توان از اشیاء بازگردانده شده توسط Devices API انتخاب کرد.

برنامه یک لیست از پیش تعریف شده از دستورات را تعریف می کند:

   // List of operations available when creating automation starters:
enum class Action {
  ON,
  OFF,
  MOVE_TO_LEVEL,
  MODE_HEAT,
  MODE_COOL,
  MODE_OFF,
}

برنامه عملیات پشتیبانی شده را برای هر ویژگی پشتیبانی شده پیگیری می کند:

 // List of operations available when comparing booleans:
object OnOffActions : Actions(listOf(
    Action.ON,
    Action.OFF,
))
// List of operations available when comparing booleans:
object LevelActions : Actions(listOf(
    Action.MOVE_TO_LEVEL
))
// List of operations available when comparing booleans:
object ThermostatActions : Actions(listOf(
    Action.MODE_HEAT,
    Action.MODE_COOL,
    Action.MODE_OFF,
))
// Map traits and the comparison operations they support:
val actionActions: Map Trait>, Actions> = mapOf(
    OnOff to OnOffActions,
    LevelControl to LevelActions,
 // BooleanState - No Actions
 // OccupancySensing - No Actions
    Thermostat to ThermostatActions,
)

برای دستوراتی که یک یا چند پارامتر را می گیرند، یک متغیر نیز وجود دارد:

   val valueLevel: MutableStateFlow

API مجموعه ای از عناصر نمایش را نمایش می دهد که کاربران می توانند از آنها برای انتخاب فیلدهای مورد نیاز استفاده کنند.

مرحله 4.2.1 را در فایل ActionView.kt لغو کامنت کنید تا همه دستگاه‌های اکشن رندر شوند و برای تنظیم actionDeviceVM در منوی DropdownMenu کلیک کنید.

val deviceVMs = structureVM.deviceVMs.collectAsState().value
...
DropdownMenu(expanded = expandedDeviceSelection, onDismissRequest = { expandedDeviceSelection = false }) {
// TODO: 4.2.1 - Action device selection dropdown
// for (deviceVM in deviceVMs) {
//     DropdownMenuItem(
//         text = { Text(deviceVM.name) },
//         onClick = {
//             scope.launch {
//                 actionDeviceVM.value = deviceVM
//                 actionTrait.value = null
//                 actionAction.value = null
//             }
//             expandedDeviceSelection = false
//         }
//     )
// }
}

مرحله 4.2.2 را در فایل ActionView.kt برای رندر کردن تمام صفات actionDeviceVM و اجرای callback کلیک در یک DropdownMenu برای تنظیم actionTrait که نشان دهنده صفتی است که فرمان به آن تعلق دارد، خارج کنید.

val actionDeviceVM: MutableState = remember {
mutableStateOf(actionVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.2.2 - Action device traits selection dropdown
// val deviceTraits: List = actionDeviceVM.value?.traits?.collectAsState()?.value!!
// for (trait in deviceTraits) {
//     DropdownMenuItem(
//         text = { Text(trait.factory.toString()) },
//         onClick = {
//             scope.launch {
//                 actionTrait.value = trait
//                 actionAction.value = null
//             }
//             expandedTraitSelection = false
//         }
//     )
// }
}

مرحله 4.2.3 را در فایل ActionView.kt برای رندر کردن همه اقدامات موجود در actionTrait و اجرای callback کلیک در یک DropdownMenu برای تنظیم actionAction که نشان دهنده عملکرد اتوماسیون انتخابی است، خارج کنید.

DropdownMenu(expanded = expandedActionSelection, onDismissRequest = { expandedActionSelection = false }) {
// ...
if (!ActionViewModel.actionActions.containsKey(actionTrait.value?.factory))
return@DropdownMenu
// TODO: 4.2.3 - Action device trait actions (commands) selection dropdown
// val actions: List = ActionViewModel.actionActions.get(actionTrait.value?.factory)?.actions!!
// for (action in actions) {
//     DropdownMenuItem(
//         text = { Text(action.toString()) },
//         onClick = {
//             scope.launch {
//                 actionAction.value = action
//             }
//             expandedActionSelection = false
//         }
//     )
// }
}

مرحله 4.2.4 را در فایل ActionView.kt برای رندر کردن مقادیر موجود عملکرد صفت (فرمان) خارج کنید و مقدار را در actionValueLevel در callback تغییر مقدار ذخیره کنید:

when (actionTrait.value?.factory) {
LevelControl -> {
// TODO: 4.2.4 - Action device trait action(command) values selection widget
// Column (Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth()) {
//   Text(stringResource(R.string.action_title_value), fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
//  }
//
//  Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
//      LevelSlider(value = actionValueLevel.value?.toFloat()!!, low = 0f, high = 254f, steps = 0,
//          modifier = Modifier.padding(top = 16.dp),
//          onValueChange = { value : Float -> actionValueLevel.value = value.toUInt().toUByte() }
//          isEnabled = true
//      )
//  }
...
}

مرحله 4.2.5 را در فایل ActionView.kt حذف کنید تا همه متغیرهای Action ViewModel در پیش نویس اتوماسیون اکشن ViewModel ( draftVM.actionVMs ) ذخیره شوند:

val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
// Save action button:
Button(
  enabled = isOptionsSelected,
  onClick = {
  scope.launch {
  // TODO: 4.2.5 - store all action ViewModel variables into draft ViewModel
  // actionVM.deviceVM.emit(actionDeviceVM.value)
  // actionVM.trait.emit(actionTrait.value)
  // actionVM.action.emit(actionAction.value)
  // actionVM.valueLevel.emit(actionValueLevel.value)
  //
  // draftVM.actionVMs.value.add(actionVM)
  // draftVM.selectedActionVM.emit(null)
  }
})
{ Text(stringResource(R.string.action_button_create)) }

اجرای برنامه و انتخاب یک اتوماسیون و اقدام جدید باید به نمای زیر منجر شود:

6efa3c7cafd3e595.png

ما فقط از اقدامات مبتنی بر ویژگی‌های دستگاه در برنامه نمونه پشتیبانی می‌کنیم.

یک اتوماسیون پیش نویس ارائه دهید

هنگامی که DraftViewModel کامل شد، می توان آن را توسط HomeAppView.kt رندر کرد:

fun HomeAppView (homeAppVM: HomeAppViewModel) {
  ...
  // If a draft automation is selected, show the draft editor:
  if (selectedDraftVM != null) {
    DraftView(homeAppVM)
  }
  ...
}

در DraftView.kt :

fun DraftView (homeAppVM: HomeAppViewModel) {
   val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
    ...
// Draft Starters:
   DraftStarterList(draftVM)
// Draft Actions:
   DraftActionList(draftVM)
}

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

اکنون که نحوه ایجاد استارترها و اقدامات را یاد گرفتید، آماده ایجاد پیش نویس اتوماسیون و ارسال آن به Automation API هستید. API یک تابع createAutomation() دارد که یک پیش نویس اتوماسیون را به عنوان آرگومان می گیرد و یک نمونه اتوماسیون جدید را برمی گرداند.

آماده سازی پیش نویس اتوماسیون در کلاس DraftViewModel در برنامه نمونه انجام می شود. به تابع getDraftAutomation() نگاه کنید تا در مورد نحوه ساختار پیش نویس اتوماسیون با استفاده از متغیرهای شروع و عمل در بخش قبل بیشتر بدانید.

مرحله 4.4.1 را در فایل DraftViewModel.kt برای ایجاد عبارات "انتخاب" مورد نیاز برای ایجاد نمودار اتوماسیون زمانی که صفت شروع کننده OnOff است، از حالت نظر خارج کنید:

val starterVMs: List = starterVMs.value
val actionVMs: List = actionVMs.value
    ...
fun getDraftAutomation() : DraftAutomation {
    ...
  val starterVMs: List = starterVMs.value
    ...
  return automation {
    this.name = name
    this.description = description
    this.isActive = true
    // The sequential block wrapping all nodes:
    sequential {
    // The select block wrapping all starters:
      select {
    // Iterate through the selected starters:
        for (starterVM in starterVMs) {
        // The sequential block for each starter (should wrap the Starter Expression!)
          sequential {
              ...
              val starterTrait: TraitFactory Trait> = starterVM.trait.value!!
              ...
              when (starterTrait) {
                  OnOff -> {
        // TODO: 4.4.1 - Set starter expressions according to trait type
        //   val onOffValue: Boolean = starterVM.valueOnOff.value
        //   val onOffExpression: TypedExpression OnOff> =
        //       starterExpression as TypedExpression OnOff>
        //   when (starterOperation) {
        //       StarterViewModel.Operation.EQUALS ->
        //           condition { expression = onOffExpression.onOff equals onOffValue }
        //       StarterViewModel.Operation.NOT_EQUALS ->
        //           condition { expression = onOffExpression.onOff notEquals onOffValue }
        //       else -> { MainActivity.showError(this, "Unexpected operation for OnOf
        //   }
        }
   LevelControl -> {
     ...
// Function to allow manual execution of the automation:
manualStarter()
     ...
}

مرحله 4.4.2 را در فایل DraftViewModel.kt برای ایجاد عبارات موازی مورد نیاز برای ایجاد نمودار اتوماسیون، زمانی که صفت اقدام انتخاب شده LevelControl و اقدام انتخاب شده MOVE_TO_LEVEL است، خارج کنید:

val starterVMs: List = starterVMs.value
val actionVMs: List = actionVMs.value
    ...
fun getDraftAutomation() : DraftAutomation {
      ...
  return automation {
    this.name = name
    this.description = description
    this.isActive = true
    // The sequential block wrapping all nodes:
    sequential {
          ...
    // Parallel block wrapping all actions:
      parallel {
        // Iterate through the selected actions:
        for (actionVM in actionVMs) {
          val actionDeviceVM: DeviceViewModel = actionVM.deviceVM.value!!
        // Action Expression that the DSL will check for:
          action(actionDeviceVM.device, actionDeviceVM.type.value.factory) {
            val actionCommand: Command = when (actionVM.action.value) {
                  ActionViewModel.Action.ON -> { OnOff.on() }
                  ActionViewModel.Action.OFF -> { OnOff.off() }
    // TODO: 4.4.2 - Set starter expressions according to trait type
    // ActionViewModel.Action.MOVE_TO_LEVEL -> {
    //     LevelControl.moveToLevelWithOnOff(
    //         actionVM.valueLevel.value!!,
    //         0u,
    //         LevelControlTrait.OptionsBitmap(),
    //         LevelControlTrait.OptionsBitmap()
    //     )
    // }
      ActionViewModel.Action.MODE_HEAT -> { SimplifiedThermostat
      .setSystemMode(SimplifiedThermostatTrait.SystemModeEnum.Heat) }
          ...
}

آخرین مرحله برای تکمیل اتوماسیون، پیاده سازی تابع getDraftAutomation برای ایجاد AutomationDraft.

مرحله 4.4.3 را در فایل HomeAppViewModel.kt حذف کنید تا با فراخوانی Home APIها و مدیریت استثناها، اتوماسیون ایجاد شود:

fun createAutomation(isPending: MutableState) {
  viewModelScope.launch {
    val structure : Structure = selectedStructureVM.value?.structure!!
    val draft : DraftAutomation = selectedDraftVM.value?.getDraftAutomation()!!
    isPending.value = true
    // TODO: 4.4.3 - Call the Home API to create automation and handle exceptions
    // // Call Automation API to create an automation from a draft:
    // try {
    //     structure.createAutomation(draft)
    // }
    // catch (e: Exception) {
    //     MainActivity.showError(this, e.toString())
    //     isPending.value = false
    //     return@launch
    // }
    // Scrap the draft and automation candidates used in the process:
    selectedCandidateVMs.emit(null)
    selectedDraftVM.emit(null)
    isPending.value = false
  }
}

اکنون برنامه را اجرا کنید و تغییرات را در دستگاه خود مشاهده کنید!

پس از انتخاب یک شروع و یک اقدام، آماده ایجاد اتوماسیون هستید:

ec551405f8b07b8e.png

مطمئن شوید که اسم اتوماسیون خود را منحصر به فرد می‌گذارید، سپس روی دکمه Create Automation ضربه بزنید، که باید APIها را فراخوانی کند و شما را با اتوماسیون خود به نمای فهرست اتوماسیون بازگرداند:

8eebc32cd3755618.png

روی اتوماسیونی که ایجاد کردید ضربه بزنید و ببینید که چگونه توسط API ها بازگردانده می شود.

931dba7c325d6ef7.png

توجه داشته باشید که API مقداری را برمی گرداند که نشان می دهد یک اتوماسیون معتبر و در حال حاضر فعال است یا خیر . امکان ایجاد اتوماسیون‌هایی وجود دارد که وقتی در سمت سرور تجزیه می‌شوند، از اعتبارسنجی عبور نمی‌کنند. اگر تجزیه و تحلیل اتوماسیون اعتبار سنجی ناموفق باشد، isValid روی false تنظیم می شود که نشان می دهد اتوماسیون نامعتبر و غیرفعال است. اگر اتوماسیون شما نامعتبر است، قسمت automation.validationIssues را برای جزئیات بررسی کنید.

مطمئن شوید که اتوماسیون شما معتبر و فعال است و سپس می توانید اتوماسیون را امتحان کنید.

اتوماسیون خود را امتحان کنید

اتوماسیون ها به دو صورت قابل اجرا هستند:

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

اگر یک اتوماسیون پیش نویس دارای یک manualStarter() باشد که در بلوک DSL پیش نویس اتوماسیون تعریف شده است، موتور اتوماسیون از اجرای دستی برای آن اتوماسیون پشتیبانی می کند. این قبلاً در نمونه کدهای موجود در Sample App وجود دارد.

از آنجایی که هنوز در صفحه نمایش اتوماسیون دستگاه تلفن همراه خود هستید، روی دکمه اجرای دستی ضربه بزنید. این باید automation.execute() را فراخوانی کند که دستور عمل شما را در دستگاهی که هنگام تنظیم اتوماسیون انتخاب کرده اید اجرا می کند.

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

به برگه Devices بروید، دستگاه اقدام و ویژگی را انتخاب کنید و آن را روی مقدار دیگری تنظیم کنید (به عنوان مثال، LevelControl ( light2 ) را روی 50٪ تنظیم کنید، همانطور که در تصویر زیر نشان داده شده است:

d0357ec71325d1a8.png

اکنون سعی می کنیم با استفاده از دستگاه استارت، اتوماسیون را راه اندازی کنیم. دستگاه شروعی را که هنگام ایجاد اتوماسیون انتخاب کرده اید انتخاب کنید. خصیصه ای را که انتخاب کرده اید تغییر دهید (به عنوان مثال، OnOff starter outlet1 را روی On قرار دهید):

230c78cd71c95564.png

خواهید دید که این نیز اتوماسیون را اجرا می‌کند و صفت LevelControl دستگاه اکشن light2 را به مقدار اصلی، 100% تنظیم می‌کند:

1f00292128bde1c2.png

تبریک می‌گوییم، شما با موفقیت از Home API برای ایجاد اتوماسیون استفاده کردید!

برای کسب اطلاعات بیشتر درباره Automation API، Android Automation API را ببینید.

5. قابلیت ها را کشف کنید

APIهای Home شامل یک API اختصاصی به نام Discovery API هستند که توسعه‌دهندگان می‌توانند از آن برای بررسی ویژگی‌های قابلیت اتوماسیون در یک دستگاه خاص استفاده کنند. برنامه Sample مثالی را ارائه می دهد که در آن می توانید از این API برای کشف دستورات موجود استفاده کنید.

کشف دستورات

در این بخش، نحوه کشف CommandCandidates پشتیبانی شده و نحوه ایجاد یک اتوماسیون بر اساس گره های کاندید کشف شده را مورد بحث قرار می دهیم.

در Sample App، device.candidates() را فراخوانی می کنیم تا لیستی از نامزدها را دریافت کنیم که ممکن است شامل نمونه هایی از CommandCandidate ، EventCandidate یا TraitAttributesCandidate باشد.

به فایل HomeAppViewModel.kt بروید و مرحله 5.1.1 را حذف کنید تا لیست نامزدها را بازیابی کنید و با نوع Candidate فیلتر کنید:

   fun showCandidates() {

   ...
// TODO: 5.1.1 - Retrieve automation candidates, filtering to include CommandCandidate types only
// // Retrieve a set of initial automation candidates from the device:
// val candidates: Set = deviceVM.device.candidates().first()
//
// for (candidate in candidates) {
//     // Check whether the candidate trait is supported:
//     if(candidate.trait !in HomeApp.supportedTraits)
//         continue
//     // Check whether the candidate type is supported:
//     when (candidate) {
//         // Command candidate type:
//         is CommandCandidate -> {
//             // Check whether the command candidate has a supported command:
//             if (candidate.commandDescriptor !in ActionViewModel.commandMap)
//                 continue
//         }
//         // Other candidate types are currently unsupported:
//         else -> { continue }
//     }
//
//     candidateVMList.add(CandidateViewModel(candidate, deviceVM))
// }
...
           // Store the ViewModels:
selectedCandidateVMs.emit(candidateVMList)
}

ببینید چگونه برای CommandCandidate. نامزدهای بازگشتی توسط API به انواع مختلفی تعلق دارند. برنامه Sample از CommandCandidate پشتیبانی می کند. مرحله 5.1.2 را در commandMap تعریف شده در ActionViewModel.kt برای تنظیم این ویژگی‌های پشتیبانی شده از حالت نظر خارج کنید:

    // Map of supported commands from Discovery API:
val commandMap: Map Action> = mapOf(
    // TODO: 5.1.2 - Set current supported commands
    // OnOffTrait.OnCommand to Action.ON,
    // OnOffTrait.OffCommand to Action.OFF,
    // LevelControlTrait.MoveToLevelWithOnOffCommand to Action.MOVE_TO_LEVEL
)

اکنون که می‌توانیم Discovery API را فراخوانی کنیم و نتایجی را که در Sample App پشتیبانی می‌کنیم فیلتر کنیم، در مورد اینکه چگونه می‌توانیم این را در ویرایشگر خود ادغام کنیم، بحث خواهیم کرد.

8a2f0e8940f7056a.png

برای کسب اطلاعات بیشتر درباره Discovery API، به Leverage device discovery در Android مراجعه کنید.

ویرایشگر را یکپارچه کنید

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

فایل CandidatesView.kt حاوی کلاس view است که نامزدهای کشف شده را نمایش می دهد. برای فعال کردن تابع .clickable{} CandidateListItem که homeAppVM.selectedDraftVM به عنوان candidateVM تنظیم می کند، مرحله 5.2.1 را از نظر خارج کنید:

fun CandidateListItem (candidateVM: CandidateViewModel, homeAppVM: HomeAppViewModel) {
    val scope: CoroutineScope = rememberCoroutineScope()
    Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
        Column (Modifier.fillMaxWidth().clickable {
        // TODO: 5.2.1 - Set the selectedDraftVM to the selected candidate
        // scope.launch { homeAppVM.selectedDraftVM.emit(DraftViewModel(candidateVM)) }
        }) {
            ...
        }
    }
}

مشابه مرحله 4.3 در HomeAppView.kt ، هنگامی که DraftVM selectedDraftVM تنظیم می شود، DraftView(...) in DraftView.kt` ​​رندر می کند:

fun HomeAppView (homeAppVM: HomeAppViewModel) {
   ...
  val selectedDraftVM: DraftViewModel? by homeAppVM.selectedDraftVM.collectAsState()
...
  // If a draft automation is selected, show the draft editor:
  if (selectedDraftVM != null) {
  DraftView(homeAppVM)
  }
   ...
}

با ضربه زدن روی light2 - MOVE_TO_LEVEL ، که در بخش قبل نشان داده شده است، دوباره آن را امتحان کنید، که از شما می‌خواهد یک اتوماسیون جدید بر اساس دستور نامزد ایجاد کنید:

15e67763a9241000.png

اکنون که با ایجاد اتوماسیون در برنامه نمونه آشنا شدید، می توانید اتوماسیون ها را در برنامه های خود ادغام کنید.

6. نمونه های اتوماسیون پیشرفته

قبل از اینکه به پایان برسیم، چند نمونه دیگر از اتوماسیون DSL را مورد بحث قرار خواهیم داد. اینها برخی از قابلیت های پیشرفته ای را که می توانید با API ها به دست آورید، نشان می دهد.

زمان روز به عنوان شروع کننده

علاوه بر ویژگی‌های دستگاه، APIهای Google Home ویژگی‌های مبتنی بر ساختار مانند Time را ارائه می‌دهند. شما می توانید اتوماسیونی ایجاد کنید که دارای یک شروع کننده مبتنی بر زمان باشد، مانند موارد زیر:

automation {
  name = "AutomationName"
  description = "An example automation description."
  isActive = true
  description = "Do ... actions when time is up."
  sequential {
    // starter
    val starter = starter<_>(structure, Time.ScheduledTimeEvent) {
      parameter(
        Time.ScheduledTimeEvent.clockTime(
          LocalTime.of(hour, min, sec, 0)
        )
      )
    }
        // action
  ...
  }
}

Assistant Broadcast as Action

ویژگی AssistantBroadcast یا به عنوان یک ویژگی در سطح دستگاه در SpeakerDevice (اگر بلندگو از آن پشتیبانی می کند) یا به عنوان یک ویژگی در سطح ساختار در دسترس است (زیرا بلندگوهای Google و دستگاه های تلفن همراه Android می توانند پخش های Assistant را پخش کنند). به عنوان مثال:

automation {
  name = "AutomationName"
  description = "An example automation description."
  isActive = true
  description = "Broadcast in Speaker when ..."
  sequential {
    // starter
      ...
    // action
    action(structure) {
      command(
      AssistantBroadcast.broadcast("Time is up!!")
      )
    }
  }
}

DelayFor و suppressFor استفاده کنید

Automation API همچنین اپراتورهای پیشرفته‌ای مانند delayFor را ارائه می‌کند که برای به تاخیر انداختن دستورات است و suppressFor ، که می‌تواند اتوماسیون را از ایجاد خودکار توسط رویدادهای مشابه در یک بازه زمانی معین سرکوب کند. در اینجا چند نمونه با استفاده از این عملگرها آورده شده است:

sequential {
  val starterNode = starter<_>(device, OccupancySensorDevice, MotionDetection)
  // only proceed if there is currently motion taking place
  condition { starterNode.motionDetectionEventInProgress equals true }
   // ignore the starter for one minute after it was last triggered
    suppressFor(Duration.ofMinutes(1))
  
    // make announcements three seconds apart
    action(device, SpeakerDevice) {
      command(AssistantBroadcast.broadcast("Intruder detected!"))
    }
    delayFor(Duration.ofSeconds(3))
    action(device, SpeakerDevice) {
    command(AssistantBroadcast.broadcast("Intruder detected!"))
  }
    ...
}

از AreaPresenceState در استارت استفاده کنید

AreaPresenceState یک ویژگی در سطح ساختار است که تشخیص می دهد آیا کسی در خانه است یا خیر.

به عنوان مثال، مثال زیر قفل خودکار درها را هنگامی که شخصی بعد از ساعت 10 شب در خانه است نشان می دهد:

automation {
  name = "Lock the doors when someone is home after 10pm"
  description = "1 starter, 2 actions"
  sequential {
    val unused =
      starter(structure, event = Time.ScheduledTimeEvent) {
        parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(22, 0, 0, 0)))
      }
    val stateReaderNode = stateReader<_>(structure, AreaPresenceState)
    condition {
      expression =
        stateReaderNode.presenceState equals
          AreaPresenceStateTrait.PresenceState.PresenceStateOccupied
    }
    action(structure) { command(AssistantBroadcast.broadcast("Locks are being applied")) }
    for (lockDevice in lockDevices) {
      action(lockDevice, DoorLockDevice) {
        command(Command(DoorLock, DoorLockTrait.LockDoorCommand.requestId.toString(), mapOf()))
      }
    }
  }

اکنون که با این قابلیت های اتوماسیون پیشرفته آشنا شدید، بیرون بروید و برنامه های عالی ایجاد کنید!

7. تبریک می گویم!

تبریک می گویم! قسمت دوم توسعه یک برنامه Android را با استفاده از APIهای Google Home با موفقیت به پایان رساندید. در سراسر این کد، شما APIهای Automation و Discovery را بررسی کردید.

امیدواریم از ساخت برنامه‌هایی لذت ببرید که به طور خلاقانه دستگاه‌ها را در اکوسیستم Google Home کنترل می‌کنند و با استفاده از Home API سناریوهای اتوماسیون هیجان‌انگیز می‌سازند!

مراحل بعدی

  • عیب‌یابی را بخوانید تا با نحوه اشکال‌زدایی مؤثر برنامه‌ها و عیب‌یابی مشکلات مربوط به Home API آشنا شوید.
  • می‌توانید با هر توصیه‌ای با ما تماس بگیرید، یا هر گونه مشکلی را از طریق موضوع پشتیبانی ردیاب مشکل ، خانه هوشمند گزارش دهید.