Доступ к устройствам и метаданным устройств для Android

Доступ к API устройств можно получить через Home API для Android. Импортируйте в приложение следующие пакеты:

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

Чтобы использовать определенные типы устройств или характеристики с Device API, их необходимо импортировать по отдельности.

Например, чтобы использовать типаж Matter On/Off и тип устройства On/Off Plug-in Unit, импортируйте в приложение следующие пакеты:

import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOffPluginUnitDevice

Подробнее о модели данных в Android…

Обработка ошибок

Любой метод в Home API может выдать ошибку HomeException, поэтому мы рекомендуем использовать блок try-catch, чтобы отлавливать HomeException во всех вызовах.

При обработке HomeException проверьте поля error.code и error.message, чтобы узнать, что пошло не так. Также могут быть дополнительные коды ошибок, поэтому вызовите метод getSubErrorCodes() и проверьте результат.

Если исключение не будет обработано, приложение завершит работу со сбоем.

Подробнее об обработке ошибок…

Пример приведен в разделе Отправка команды на устройство.

Примеры звонков

Как получить список устройств

Получив ссылку на экземпляр Structure, вызовите devices(), чтобы получить Flow устройств, доступных вам в этой структуре:

// Get a flow of all devices accessible to the user
val allDevicesFlow: HomeObjectsFlow<HomeDevice> = home.devices()

// Calling list() on a HomeObjectsFlow returns the first Set of elements.
val allDevices: Set<HomeDevice> = allDevicesFlow.list()

Там вы можете посмотреть состояние каждого устройства и отправить на него команды.

В версии 1.8 Home API вы можете сделать так, чтобы API представлял каждое составное устройство как одно. Для этого задайте параметру enableMultipartDevices метода devices() значение true. Подробнее об устройствах с несколькими частями на Android…

Прочитать состояние устройства

Посмотрите пример проверки атрибута OnOff из типа On/Off устройства. Используя модель данных признаков Home API, в которой этот признак идентифицируется как OnOff, вы можете получить данные признака через класс standardTraits типа устройства:

// Assuming we have a device.
val deviceFlow = home.devices().itemFlow(myDeviceId)

val device = deviceFlow.first()

// Get a flow of a standard trait on the type. distinctUntilChanged() is needed to only trigger
// on the specific trait changes and not the whole type.
val onOffTraitFlow: Flow<OnOff?> =
  device.type(DimmableLightDevice).map { it.standardTraits.onOff }.distinctUntilChanged()

val onOffTrait: OnOff = onOffTraitFlow.first()!!

Подробнее о функции flow в Kotlin читайте в distinctUntilChanged.

Как аннулировать состояние в подписке на признак

Интерфейс TraitStateInvalidation позволяет аннулировать состояние, полученное через подписки на целевое устройство, если оно сообщается неправильно. Например, состояние может быть указано неверно, если в характеристиках Matter используются атрибуты с качеством C или если проблема вызвана реализацией устройства.

Этот API принудительно считывает текущее состояние характеристики и возвращает результат через существующие потоки характеристик.

Получите типаж, а затем запустите для него forceRead:

val onOffTrait = device.?type(DimmableLightDevice)?.map{it.trait(OnOff)}.first()
onOffTrait.forceRead()

Получить список характеристик типа устройства

Типы устройств следует использовать в качестве точки входа для чтения характеристик, поскольку они разбивают устройство на функциональные части (например, конечные точки в Matter).

Они также учитывают конфликты признаков в случае, если устройство имеет два типа устройств, оба из которых могут иметь один и тот же признак. Например, если устройство является одновременно колонкой и лампой с регулируемой яркостью, у него будет два признака On/Off и два признака Level Control.

Чтобы получить список доступных функций для типа устройства "Светильник с регулируемой яркостью":

// Get all types available on this device. Requires the types to be part of the registry during
// SDK initialization.
val typesFlow: Flow<Set<DeviceType>> = device.types()

// Get a snapshot of all types.
val types: Set<DeviceType> = typesFlow.first()

// Get the DimmableLightDevice instance from the set of types.
val dimmableLightDevice = types.filterIsInstance<DimmableLightDevice>().firstOrNull()

// Get all traits in the type + traits registered
val allTraits: Set<Trait> = dimmableLightDevice!!.traits()

Другой тип конфликта возникает, когда у устройства есть две характеристики с одинаковым названием. Например, onOff может относиться к экземпляру стандартной характеристики OnOff или к экземпляру характеристики OnOff, определенной производителем. Чтобы устранить любую потенциальную двусмысленность в отношении того, какая черта предназначена, перед экземпляром Trait, на который ссылается устройство, должно стоять уточняющее пространство имен. Для стандартных признаков, то есть аналогичных стандартным кластерам Matter, используйте standardTraits. Для признаков Google используйте googleTraits:

// Accessing standard traits on the type.
val onOffTrait: OnOff? = dimmableLightDevice.standardTraits.onOff
val levelControlTrait: LevelControl? = dimmableLightDevice.standardTraits.levelControl

Чтобы получить доступ к признаку, специфичному для производителя, сошлитесь на него напрямую:

// Accessing a custom trait on the type.
val customTrait = dimmableLightDevice.trait(MyCustomTrait)

Получить список устройств с определенным свойством

Функция filter в Kotlin позволяет уточнять вызовы API. Например, чтобы получить список устройств в доме, у которых есть функция On/Off, выполните следующий запрос:

// Get all devices that support OnOff
val onOffDevices: Flow<List<HomeDevice>> =
  home.devices().map { devices -> devices.filter { it.has(OnOff) } }

Полный список доступных в Home API функций приведен в интерфейсе Trait.

Как получить список устройств одного типа

Чтобы получить список устройств, представляющих все источники света в доме:

// Get a list of devices with similar device types (lights)
val lightDevices =
  home.devices().map { devices ->
    devices.filter {
      it.has(DimmableLightDevice) ||
        it.has(OnOffLightDevice) ||
        it.has(ColorTemperatureLightDevice) ||
        it.has(ExtendedColorLightDevice)
    }
  }

В Home API есть несколько типов устройств, которые могут представлять основной тип устройства. Например, типа устройства "Светильник" нет. Вместо этого есть четыре разных типа устройств, которые могут представлять свет, как показано в предыдущем примере. Поэтому, чтобы получить полное представление о типе устройства более высокого уровня в доме, в отфильтрованные потоки необходимо включить несколько типов устройств.

Полный список типов устройств, доступных в Home API, можно найти в интерфейсе DeviceType.

Как получить идентификатор производителя или продукта для устройства

Типаж BasicInformation содержит такую информацию, как идентификатор поставщика, идентификатор товара, название товара и серийный номер устройства:

// Get device basic information. All general information traits are on the RootNodeDevice type.
val basicInformation = device.type(RootNodeDevice).first().standardTraits.basicInformation!!
println("vendorName ${basicInformation.vendorName}")
println("vendorId ${basicInformation.vendorId}")
println("productId ${basicInformation.productId}")

Идентификация устройств в облаке для производителей устройств

Если вы производите устройства Cloud-to-cloud, то, чтобы идентифицировать устройства Cloud-to-cloud с помощью типажа BasicInformation, вы можете включить в ответ SYNC следующие строковые поля:

  • Идентификатор поставщика, выданный Connectivity Standards Alliance (Alliance): "matterOriginalVendorId": "0xfff1",

  • Идентификатор товара, который однозначно определяет товар поставщика: "matterOriginalProductId": "0x1234",

  • Уникальный идентификатор устройства, который создается производителем: "matterUniqueId": "matter-device-id",

При заполнении этих строковых полей используйте Matter идентификаторы поставщика и товара, если они у вас есть. Если вы не являетесь участником Alliance и вам не назначены эти идентификаторы, вы можете оставить поля matterOriginalVendorId и matterOriginalProductId пустыми и указать matterUniqueId в качестве идентификатора.

В примере ответа SYNC показано использование этих полей:

{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "agentUserId": "1836.15267389",
    "devices": [
      {
        "id": "456",
        "type": "action.devices.types.LIGHT",
        "traits": [
          "action.devices.traits.OnOff",
          "action.devices.traits.Brightness",
          "action.devices.traits.ColorSetting",
        ],
        "willReportState": true,
        "deviceInfo": { ... },
        "matterOriginalVendorId": "0xfff1",
        "matterOriginalProductId": "0x1234",
        "matterUniqueId": "matter-device-id",
        "otherDeviceIds": [
          {
            "deviceId": "local-device-id",
          }
        ]
      }
    ]
  }
}

Подробную информацию можно найти в документации по Cloud-to-cloud SYNC.

Метаданные устройств и признаков

С устройствами и признаками в Home API связаны метаданные, которые помогают управлять взаимодействием с пользователем в приложении.

Каждый признак в Home API содержит свойство sourceConnectivity, в котором есть информация о статусе подключения признака и его локальности (локальная или удаленная маршрутизация).

Получение основного типа устройства

Некоторые устройства могут быть представлены в Home API как устройства нескольких типов. Чтобы пользователи видели в приложении подходящие варианты (например, управление устройством и предлагаемые автоматизации) для своих устройств, полезно проверить, какой тип устройства является основным для устройства.

Сначала получите типы устройства с помощью type(), а затем определите основные типы:

val types = device.types().first()
val primaryTypes = types.filter { it.metadata.isPrimaryType }

Как проверить, доступна ли функция онлайн

Чтобы проверить подключение функции, используйте метод connectivityState():

val onOffConnectivity = onOffTrait?.metadata?.sourceConnectivity?.connectivityState

Некоторые характеристики, обычно характеристики smart home Google, могут отображаться как офлайн, если устройство не подключено к интернету. Это связано с тем, что эти функции являются облачными и не имеют локальной маршрутизации.

Как проверить подключение устройства

Подключение устройства проверяется на уровне типа устройства, поскольку некоторые устройства поддерживают несколько типов. Возвращаемое состояние представляет собой комбинацию состояний подключения для всех характеристик на этом устройстве.

val lightConnectivity = dimmableLightDevice.metadata.sourceConnectivity.connectivityState

Если у вас есть устройства разных типов и нет подключения к интернету, может отображаться статус PARTIALLY_ONLINE. Matter стандартных функций могут оставаться доступными благодаря локальной маршрутизации, но облачные функции будут отключены.

Как узнать IP-адрес устройства

Чтобы найти IP-адрес устройства, используйте атрибут networkInterfaces типажа GeneralDiagnostics. Адреса возвращаются в виде массивов байтов, которые можно преобразовать в стандартные строки IPv4 или IPv6:

val ipAddresses =
  trait.networkInterfaces?.flatMap { networkInterface ->
    (networkInterface.ipv4Addresses + networkInterface.ipv6Addresses).mapNotNull { bytes ->
      try {
        java.net.InetAddress.getByAddress(bytes).hostAddress
      } catch (e: java.net.UnknownHostException) {
        null
      }
    }
  } ?: emptyList()

Как проверить маршрутизацию трафика для свойства

Местоположение для свойства также доступно в Home API. dataSourceLocality указывает, как передается свойство: удаленно (через облако), локально (через локальный контроллер) или напрямую (от устройства к устройству, без контроллера).

Неизвестное значение UNSPECIFIED возможно, например, когда приложение загружается и ещё не подключилось к концентратору или серверу. Эти устройства недоступны и не будут отвечать на запросы взаимодействия от команд или событий. Клиент сам решает, как поступать с такими устройствами.

val onOffLocality = onOffTrait?.metadata?.sourceConnectivity?.dataSourceLocality

Как проверить маршрутизацию сети для устройства

Как и подключение, местоположение проверяется на уровне типа устройства. Возвращаемое состояние представляет собой комбинацию местоположения для всех характеристик на этом устройстве.

val lightLocality = dimmableLightDevice.metadata.sourceConnectivity.dataSourceLocality

Состояние MIXED может наблюдаться в сценарии, похожем на сценарий для подключения PARTIALLY_ONLINE: некоторые характеристики являются облачными, а другие – локальные.

Как изменить название устройства

Чтобы изменить название устройства, вызовите метод setName().

mixerDevice.setName("Grendel")

Названия, превышающие ограничение в 60 кодовых точек Unicode (символов), будут усечены без появления ошибок. Разработчики сами решают, как поступать с длинными названиями. Например, они могут предупреждать пользователей о том, что название будет усечено.

Как проверить, в какую группу входит устройство

Физическое устройство может принадлежать к одной или нескольким группам устройств. Чтобы узнать, к каким группам относится устройство, проверьте его свойство GroupMembership.

val groupIds = device.trait(GroupMembershipTrait)?.groupObjectIds.orEmpty()
for (groupId in groupIds) {
  println("HomeAPI", "Device belongs to group: $groupId")
}

И наоборот, вы можете наблюдать за всеми устройствами, принадлежащими к DeviceGroup, вызвав поток devices() для DeviceGroup:

group.devices(enableMultipartDevices = true).collect { members ->
  println("HomeAPI", "Group contains ${members.size} devices")
}

Чтобы узнать, какие типы устройств поддерживаются в DeviceGroup, и отправлять команды группе как единому объекту, прочитайте статью Как управлять группой устройств.