Руководство по использованию камеры на устройстве Android

Тип устройства "Камера" реализуется с помощью трех признаков: PushAvStreamTransport, который обрабатывает передачу аудио- и видеопотоков с использованием протоколов на основе push-уведомлений, WebRtcLiveView, который обеспечивает возможность управления прямыми трансляциями и обратной связью, и CameraSnapshot, который обеспечивает кэшированные снимки предварительного просмотра и снимки в реальном времени.

Прежде чем использовать функции или обновлять атрибуты, всегда проверяйте, поддерживаются ли они на устройстве. Подробнее о том, как управлять устройствами на платформеAndroid…

Тип устройства Home API Черты характера Пример приложения на Kotlin История успеха

Камера

GoogleCameraDevice

home.matter.6006.types.0158

Устройство, которое делает фотографии или записывает видео. Камеры могут поддерживать доступные трансляции, двустороннюю связь или события обнаружения.

Обязательные характеристики
     google PushAvStreamTransport
     google WebRtcLiveView

Камера

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

   Реализовано в примере приложения для Android   

Функция BasicInformation содержит информацию об устройстве, например название и идентификатор поставщика, идентификатор и название товара (включая сведения о модели), а также версию ПО.

// Get device basic information. All general information traits are on the RootNodeDevice type.
    device.type(RootNodeDevice).first().standardTraits.basicInformation?.let { basicInformation ->
        println("vendorName ${basicInformation.vendorName}")
        println("vendorId ${basicInformation.vendorId}")
        println("productId ${basicInformation.productId}")
        println("productName ${basicInformation.productName}")
        println("softwareVersion ${basicInformation.softwareVersion}")
    }

Получить серийный номер

Чтобы получить серийный номер устройства, используйте команду GetSerialNumber типажа ExtendedBasicInformation. В примере ниже серийный номер сохраняется в переменной serialNumber:

val basicInfo: ExtendedBasicInformation = device.getTrait(ExtendedBasicInformation)
val serialNumber = basicInfo.getSerialNumber().serialNumber

Получить время последнего подключения устройства к облаку

Чтобы узнать, когда устройство в последний раз подключалось к облаку, используйте атрибут lastContactTimestamp типа ExtendedGeneralDiagnostics.

fun getLastContactTimeStamp(trait: ExtendedGeneralDiagnostics): java.time.Instant {
  val timestamp = trait.lastContactTimestamp
  return Instant.ofEpochSecond(timestamp.toLong())
}

Настройки типа крепления камеры

Типаж Mount содержит настройки и информацию о статусе крепления камеры. Вы можете прочитать такие атрибуты, как состояние подключения, тип обнаружения и название типа подключения. Кроме того, вы можете использовать свойство Mount, чтобы переопределить конфигурацию типа подключения по умолчанию.

// Get the Mount trait
val mountTrait: Mount = device.getTrait(Mount)

// Read the current mount state and detection type
val mountState = mountTrait.mountState
val mountDetectionType = mountTrait.mountDetectionType

// Read the current mount type name
val mountTypeName = mountTrait.mountTypeName

// Update the mount type override
mountTrait.update {
  setMountTypeOverride(MountTrait.MountTypeOverrideEnum.Official)
}

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

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

val cameraConnectivity = cameraDevice.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()

Как начать трансляцию

   Реализовано в примере приложения для Android   

Чтобы начать трансляцию, отправьте строку SDP (Session Description Protocol) в метод startLiveView() типажа WebRtcLiveView. Метод возвращает объект WebRtcLiveViewTrait.StartLiveViewCommand.Response, содержащий три значения:

  • SDP для сеанса.
  • Длительность сеанса в секундах.
  • Идентификатор сеанса, который можно использовать для продления или завершения сеанса.
suspend fun getWebRtcLiveViewTrait(cameraDevice: HomeDevice) {
 return cameraDevice.type(GoogleCameraDevice).trait(WebRtcLiveView).first {
    it?.metadata?.sourceConnectivity?.connectivityState == ConnectivityState.ONLINE
  }

}

// Start the live view
suspend fun startCameraStream(trait: WebRtcLiveView, offerSdp: String) {
  val response = trait.startLiveView(offerSdp)
  // Response contains three fields (see below)
  return response
}
  ...

// This is used to manage the WebRTC connection
val peerConnection: RTCPeerConnection = ...

   ...

val startResponse = startCameraStream(sdp)
val answerSdp = startResponse?.answerSdp
val sessionDuration = startResponse?.liveSessionDurationSeconds
val mediaSessionId = startResponse?.mediaSessionId

peerConnection.setRemoteDescription(SessionDescription.Type.ANSWER,
                                    answerSdp)

Как продлить трансляцию

   Реализовано в примере приложения для Android   

У трансляций есть заданная продолжительность, по истечении которой они завершаются. Чтобы продлить активную трансляцию, отправьте запрос на продление с помощью метода WebRtcLiveView.extendLiveView():

// Assuming camera stream has just been started
suspend fun scheduleExtension(trait: WebRtcLiveView, mediaSessionId: String, liveSessionDurationSeconds: UShort ) {
  delay(liveSessionDurationSeconds - BUFFER_SECONDS * 1000)
  val response = trait.extendLiveView(mediaSessionId)
  // returns how long the session will be live for
  return response.liveSessionDurationSeconds
}

Как включить и отключить TalkBack

   Реализовано в примере приложения для Android   

Чтобы начать обратную связь, вызовите метод startTalkback() типажа WebRtcLiveView. Чтобы остановить, нажмите stopTalkback().

// Make sure camera stream is on
suspend fun setTalkback(isOn: Boolean, trait: WebRtcLiveView, mediaSessionId: String) {
  if(isOn) {
    trait.startTalkback(mediaSessionId)
  } else {
    trait.stopTalkback(mediaSessionId)
  }
}

Управление трансляцией

Настройка качества трансляции позволяет оптимизировать использование пропускной способности в зависимости от контекста просмотра клиента (например, переключение на более низкое разрешение при отображении меньшего фрагмента предварительного просмотра, в виде сетки или режима "картинка в картинке").

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

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

  • Получение поддерживаемых вариантов качества: получение доступных разрешений потоковой передачи, поддерживаемых устройством. Код запрашивает атрибут supportedQualityHints функции WebRtcLiveView в потоке типа устройства и предоставляет поддерживаемые качества в виде StateFlow, содержащего список значений QualityHint (например, QUALITY_HINT_SD, QUALITY_HINT_HD, QUALITY_HINT_FHD, QUALITY_HINT_QHD или QUALITY_HINT_UHD).

  • Изменить качество трансляции. Примените выбранный QualityHint, чтобы изменить разрешение активной трансляции (например, переключиться со стандартного разрешения на высокое). Функция changeQuality разрешает свойство прямой трансляции устройства и вызывает changeLiveViewQuality с активным mediaSessionId и выбранной конфигурацией QualityHint.

// Assuming you have a HomeDevice instance 'device'
val availableQualityHints: StateFlow<List> =
    device.type(GoogleCameraDevice)
        .trait(WebRtcLiveView)
        .map { trait -> 
            trait?.supportedQualityHints ?: emptyList() 
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

// Assuming you have a HomeDevice instance 'device'
suspend fun changeQuality(mediaSessionId: String, qualityHint: QualityHint) {
    // Get the trait from the device
    val trait = device.type(GoogleCameraDevice).trait(WebRtcLiveView).first() ?: return
    try {
        trait.changeLiveViewQuality(mediaSessionId, qualityHint)
    } catch (e: Exception) {
        // Handle error
    }
}

Как включить или отключить функцию записи

Чтобы включить запись с камеры, передайте TransportStatusEnum.Active в метод setTransportStatus() типажа PushAvStreamTransport. Чтобы отключить возможность записи, передайте значение TransportStatusEnum.Inactive. В примере ниже мы объединяем эти вызовы в один, который использует Boolean для переключения возможности записи:

// Start or stop recording for all connections.
suspend fun setCameraRecording(trait: PushAvStreamTransport, isOn: Boolean) {
  if(isOn) {
    trait.setTransportStatus(TransportStatusEnum.Active)
  } else {
    trait.setTransportStatus(TransportStatusEnum.Inactive)
  }
}

Включение или отключение возможности записи на камере аналогично включению или отключению видео на камере. Когда видео на камере включено, она ведет запись (для целей событий и связанных с ними клипов).

Если возможность записи отключена (видео с камеры отключено):

  • Камера может по-прежнему отображаться как подключенная к интернету в соответствии с connectivityState типа устройства.
  • Прямая трансляция недоступна, и камера не обнаруживает события в облаке.

Проверьте, включена ли возможность записи

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

// Get the on/off state
suspend fun onOffState(pushAvStreamTransport: PushAvStreamTransport) {
  return pushAvStreamTransport
    .currentConnections?.any { it.transportStatus == TransportStatusEnum.Active } ?: false
}

// Check if the camera's recording capability is enabled
fun PushAvStreamTransport.recordModeActive(): Boolean {
  return currentConnections?.any { it.transportStatus == TransportStatusEnum.Active } ?: false
}

Другой способ – использовать функцию findTransport() с предикатом:

// Fetch the current connections
suspend fun queryRecordModeState(trait: PushAvStreamTransport) {
  return trait.findTransport().let {
      it.transportConfigurations.any { it.transportStatus == TransportStatusEnum.Active
    }
}

Получать снимки с камеры

   Реализовано в примере приложения для Android   

Используйте типаж CameraSnapshot, чтобы получать статические изображения с камеры. Такие снимки полезны в нескольких сценариях:

  • В качестве заполнителя в интерфейсе, пока загружается трансляция WebRTC.
  • В виде статического изображения для предварительного просмотра, которое при взаимодействии пользователя с ним заменяется прямой трансляцией.
  • В качестве фона для рисования и настройки пользовательских зон слежения.

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

Как получить снимок предварительного просмотра из кеша

Предварительный снимок – это кешированное изображение, которое SDK может получить, не активируя камеру. Это самый быстрый способ получить статичное изображение. Он идеально подходит для обзорных страниц камер или сеток панелей управления.

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

// Get a flow of the CameraSnapshot trait
val cameraSnapshotFlow: Flow<CameraSnapshot?> = cameraDevice
  .type(GoogleCameraDevice)
  .trait(CameraSnapshot)

// Get the current trait state
val cameraSnapshot = cameraSnapshotFlow.firstOrNull()
if (cameraSnapshot != null) {
  val previewImageUrl = cameraSnapshot.previewImageUrl
  // Load the preview image from the URL
} else {
  // CameraSnapshot trait not supported on this device
}

В зависимости от конфигурации и активности камеры URL предпросмотра указывает на кешированное изображение, созданное на основе недавней видеозаписи (первый I-кадр последнего видеофрагмента), или на снимок, периодически загружаемый камерой.

Чтобы указать актуальность изображения, облако Google Home встраивает заголовок X-Home-Image-Captured-At в обслуживаемое изображение JPEG/PNG в формате UTC. Вы можете проанализировать этот заголовок на стороне клиента, чтобы показать в приложении, когда был сделан снимок.

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

Если для пути пользователя требуется текущее изображение с камеры, например при настройке зон слежения, вы можете запросить мгновенный снимок. При запуске мгновенного снимка камера выходит из спящего режима, делает снимок и загружает его в облако.

Чтобы запросить снимок в реальном времени, используйте команду getLiveSnapshot():

try {
  val response = cameraSnapshot.getLiveSnapshot()
  val snapshotUrl = response.snapshotUrl
  // Load the live snapshot image from the URL
} catch (e: Exception) {
  Log.e("CameraSnapshot", "Failed to get live snapshot: ${e.message}")
}

Аутентификация запросов на создание снимков

   Реализовано в примере приложения для Android   

Все URL снимков указывают на защищенные медиафайлы и требуют токена OAuth 2.0 для области действия Home APIs v2. Чтобы скачать и обработать изображения, токен OAuth необходимо вставить в качестве токена на предъявителя в заголовок Authorization HTTP-запроса.

В примере ниже показано, как настроить защищенный сетевой запрос для скачивания снимка.

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
 * Downloads a snapshot from a URL using an OAuth access token.
 */
suspend fun loadSnapshotImage(urlString: String, oauthToken: String): Bitmap? =
  withContext(Dispatchers.IO) {
    var connection: HttpURLConnection? = null
    try {
      val url = URL(urlString)
      connection = url.openConnection() as HttpURLConnection
      // 1. Configure the HTTP GET request with the Bearer token
      connection.setRequestProperty("Authorization", "Bearer $oauthToken")

      // 2. Fetch the image data
      if (connection.responseCode == HttpURLConnection.HTTP_OK) {
        connection.inputStream.use { inputStream ->
          BitmapFactory.decodeStream(inputStream)
        }
      } else {
        null
      }
    } catch (e: IOException) {
      e.printStackTrace()
      null
    } finally {
      connection?.disconnect()
    }
  }

Настройки батареи

Home API позволяют управлять различными настройками батареи.

Как настроить расход заряда батареи

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

Для этого нужно обновить атрибут currentEnergyBalance типажа EnergyPreference. Атрибут принимает целочисленный индекс, соответствующий определенному профилю, заданному в списке energyBalances устройства (например, 0 для EXTENDED, 1 для BALANCED и 2 для PERFORMANCE).

Значение null для currentEnergyBalance указывает, что на устройстве используется пользовательский профиль. Это состояние доступно только для чтения.

Ниже приведен пример структуры, которую будет использовать атрибут currentEnergyBalance, а затем фактический фрагмент кода, в котором используется этот атрибут.

// Example energyBalances list
energy_balances: [
  {
    step: 0,
    label: "EXTENDED"
  },
  {
    step: 50,
    label: "BALANCED"
  },
  {
    step: 100,
    label: "PERFORMANCE"
  }
]
// The index parameter must be within the UByte range (0-255).
suspend fun setEnergyBalance(trait: EnergyPreference, index: Int) {
  trait.update { setCurrentEnergyBalance(index.toUByte()) }
}

// Setting the battery usage to more recording ie performance
setEnergyBalance(energyPreference, 2)

Включить автоматический переход в режим энергосбережения

Чтобы настроить эту функцию, обновите атрибут currentLowPowerModeSensitivity типажа EnergyPreference. В этом атрибуте используется индекс для выбора уровня чувствительности, где 0 обычно означает Disabled, а 1 – Enabled или Automatic.

suspend fun setAutomaticBatterySaver(enable: Boolean, trait: EnergyPreference) {
  // 0 is Disabled, 1 is Enabled
  val value = if (enable) 1.toUByte() else 0.toUByte()
  trait.update { setCurrentLowPowerModeSensitivity(value) }
}

Получить состояние зарядки батареи

Чтобы узнать текущее состояние зарядки устройства (заряжается, полностью заряжено или не заряжается), используйте атрибут batChargeState типажа PowerSource.

// Get the battery charging state
val batteryChargeState = powerSource.batChargeState

when (batteryChargeState) {
    PowerSourceTrait.BatChargeStateEnum.IsCharging -> "Charging"
    PowerSourceTrait.BatChargeStateEnum.IsAtFullCharge -> "Full"
    PowerSourceTrait.BatChargeStateEnum.IsNotCharging -> "Not Charging"
    else -> "Unknown"
}

Узнать уровень заряда батареи

Чтобы получить текущий уровень заряда батареи, используйте атрибут batChargeLevel типажа PowerSource. Уровень может быть OK, Warning (низкий) или Critical.

// Get the battery charge level
val batteryLevel = powerSourceTrait.batChargeLevel

when (batteryLevel) {
    PowerSourceTrait.BatChargeLevelEnum.OK -> "OK"
    PowerSourceTrait.BatChargeLevelEnum.Warning -> "Warning"
    PowerSourceTrait.BatChargeLevelEnum.Critical -> "Critical"
    else -> "Unknown"
}

Подготовьте источник питания

Чтобы определить, какой источник питания использует устройство, используйте атрибуты BatPresent и wiredPresent типажа PowerSource.

  val trait: PowerSource
  val isWired = trait.wiredPresent
  val hasBattery = trait.batPresent

Проверьте, нужно ли заменить батарею.

Атрибут batReplaceability указывает, можно ли заменить батарею и как это сделать.

Атрибут batReplacementNeeded типажа PowerSource сообщает, нужно ли заменить батарею.

// Get the battery replacement status
val batteryStatus = powerSourceTrait.batReplacementNeeded

if (batteryStatus) {
  println("Replace the battery.")
}

Если батарею может заменить пользователь, в batReplacementDescription описывается батарея, в том числе ее форм-фактор, химический состав и предпочтительный производитель.

Настройки звука

Home API позволяют управлять различными настройками звука.

Включить или выключить микрофон

Чтобы включить или выключить микрофон устройства, обновите атрибут microphoneMuted типа CameraAvStreamManagement с помощью встроенной функции Kotlin setMicrophoneMuted:

// Turn the device's microphone on or off
suspend fun turnOffMicrophone(disableMicrophone: Boolean, trait: CameraAvStreamManagement) {
  trait.update { setMicrophoneMuted(disableMicrophone) }
}

Как включить или отключить запись аудио

Чтобы включить или отключить запись звука на устройстве, обновите атрибут recordingMicrophoneMuted типа CameraAvStreamManagement с помощью встроенной функции Kotlin setRecordingMicrophoneMuted:

// Turn audio recording on or off for the device
suspend fun turnOffAudioRecording(disableAudioRecording: Boolean, trait: CameraAvStreamManagement) {
  trait.update { setRecordingMicrophoneMuted(disableAudioRecording) }
}

Как настроить громкость динамика

Чтобы настроить громкость динамика на устройстве, обновите атрибут speakerVolumeLevel типа CameraAvStreamManagement с помощью встроенной функции Kotlin setSpeakerVolumeLevel:

// Adjust the camera speaker volume
suspend fun adjustSpeakerVolume(volume: Int, trait: CameraAvStreamManagement) {
  trait.update { setSpeakerVolumeLevel(volume.toUbyte()) }
}

Настройки зон слежения

   Реализовано в примере приложения для Android   

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

Пользователь настраивает зоны слежения в партнерском приложении, выделяя определенные области в зоне обзора камеры. Эти определенные пользователем зоны затем преобразуются в структуры, используемые этим признаком. Подробнее о том, как настроить и использовать зоны слежения…

Обычно зоны слежения определяются с помощью двумерных декартовых координат. Типаж предоставляет TwoDCartesianVertexStruct для вершин и TwoDCartesianZoneStruct для определения зоны (название, вершины, цвет и использование).

Проверьте зоны слежения

Чтобы показать зоны слежения, проверьте атрибут zones свойства ZoneManagement.

// 1. Obtain the trait flow from the device
private val zoneManagementFlow: Flow =
  device.type(CAMERA_TYPE).flatMapLatest { it.trait(ZoneManagement) }

// 2. Map the flow to the list of zone structures
val activityZones: Flow<List<ZoneManagementTrait.ZoneInformationStruct>> =
  zoneManagementFlow.map { trait ->
    trait.zones ?: emptyList()
  }

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

Чтобы создать новую зону, используйте команду createTwoDCartesianZone. Эта команда принимает TwoDCartesianZoneStruct, который определяет название, вершины, цвет и использование зоны.

В примере ниже показано, как создать зону "Крыльцо" с четырьмя вершинами, окрашенную в лососевый цвет (#F439A0) и используемую для обнаружения движения.

import com.google.home.google.ZoneManagement
import com.google.home.google.ZoneManagementTrait
import com.google.home.matter.serialization.OptionalValue

/**
 * Creates a custom activity zone named "Front Porch" with a salmon color
 * configured for motion detection.
 */
suspend fun createFrontPorchZone(zoneManagement: ZoneManagement) {
  // 1. Define the vertices for the zone (2D Cartesian coordinates)
  // Values are typically scaled to a maximum defined by the device's twoDCartesianMax attribute.
  val vertices =
    listOf(
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 260u, y = 422u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 1049u, y = 0u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 2048u, y = 0u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 2048u, y = 950u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 1630u, y = 1349u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 880u, y = 2048u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 0u, y = 2048u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 638u, y = 1090u)
    )

  // 2. Define the zone structure
  val newZone =
    ZoneManagementTrait.TwoDCartesianZoneStruct(
      name = "Front Porch",
      vertices = vertices,
      // Usage defines what the zone filters (for example, Motion, Person, Vehicle)
      use = listOf(ZoneManagementTrait.ZoneUseEnum.Motion),
      // Color is typically a hex string (for example, Salmon/Pink)
      color = OptionalValue.present("#F439A0")
    )

  try {
    // 3. Execute the command to add the zone to the device
    zoneManagement.createTwoDCartesianZone(newZone)
    println("Successfully created activity zone.")
  } catch (e: Exception) {
    // Handle potential HomeException or Timeout
    println("Failed to create activity zone: ${e.message}")
  }
}

Как изменить зону слежения

Чтобы обновить существующую зону, используйте команду updateTwoDCartesianZone. Для этой команды требуется zoneId и обновленный файл TwoDCartesianZoneStruct.

private suspend fun ZoneManagement.updateZone(
  zoneId: UShort,
  zone: ZoneManagementTrait.TwoDCartesianZoneStruct
) {
  // Execute the command to update the zone
  this.updateTwoDCartesianZone(zoneId = zoneId, zone = zone)
}

Как удалить зону слежения

Чтобы удалить зону, используйте команду removeZone с определенным значением zoneId.

private suspend fun ZoneManagement.deleteZone(zoneId: UShort) {
  // Execute the command to remove the zone
  this.removeZone(zoneId = zoneId)
}

Триггеры звуковых событий

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

Для распознавания звуков с помощью EventTriggerTypeEnum доступны следующие типы триггеров:

Режим Перечисляемое значение Описание
Звук Sound Распознавание звуков.
Речь PersonTalking Распознает речь.
Лай собаки DogBark Распознает собачий лай.
Звук бьющегося стекла GlassBreak Распознает звук разбитого стекла.
Датчик дыма SmokeAlarm Распознает сигналы датчиков дыма, которые часто издают звуки по шаблону T3 (три коротких сигнала, за которыми следует пауза).
датчик угарного газа CoAlarm Распознает сигналы датчиков угарного газа (CO), которые обычно представляют собой четыре коротких звуковых сигнала, за которыми следует пауза.

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

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

При разработке для Android с использованием Kotlin Flows вы обычно наблюдаете за свойством AvStreamAnalysis из HomeDevice.

// Example structure to store the
data class EventTriggerAttribute(val type: EventTriggerTypeEnum, val enabled: Boolean)

// 1. Obtain the trait flow from the device
private val avStreamAnalysisFlow: Flow<AvStreamAnalysis> =
  device.traitFromType(AvStreamAnalysis, CAMERA_TYPES.first { device.has(it) })

// 2. Map the flow to a list of sound event attributes
val soundEventTriggersState: Flow<List<EventTriggerAttribute>> =
  avStreamAnalysisFlow.map { trait ->
    // Get raw lists from the trait attributes
    val supported = trait.supportedEventTriggers ?: emptyList()
    val enabled = trait.enabledEventTriggers ?: emptyList()

    // Define sound-specific triggers to filter for
    val soundTypes = setOf(
      EventTriggerTypeEnum.Sound,
      EventTriggerTypeEnum.PersonTalking,
      EventTriggerTypeEnum.DogBark,
      EventTriggerTypeEnum.GlassBreak,
      EventTriggerTypeEnum.SmokeAlarm,
      EventTriggerTypeEnum.CoAlarm,
    )

    // Filter and associate status
    supported
      .filter { soundTypes.contains(it) }
      .map { type ->
        EventTriggerAttribute(
          type = type,
          enabled = enabled.contains(type)
        )
      }
  }

Обновить набор включенных триггеров

Чтобы обновить набор включенных триггеров, используйте команду SetOrUpdateEventDetectionTriggers, которая принимает список структур EventTriggerEnablement.

private suspend fun AvStreamAnalysis.updateEventTriggers(
  eventTriggers: List<EventTriggerAttribute>
) {
  val toUpdate = eventTriggers.map {
    EventTriggerEnablement(
      eventTriggerType = it.type,
      enablementStatus = if (it.enabled) {
        EnablementStatusEnum.Enabled
      } else {
        EnablementStatusEnum.Disabled
      },
    )
  }

  // Execute the command on the device
  setOrUpdateEventDetectionTriggers(toUpdate)
}

Режимы записи

   Реализовано в примере приложения для Android   

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

RecordingModeEnum определяет доступные стратегии записи:

Режим Перечисляемое значение Описание
Отключено Disabled Запись полностью отключена. В основном используется устаревшими устройствами.
НЗ (непрерывная запись) Cvr Видео записывается круглосуточно. Требуется подписка (например, Google Home Premium).
Запись событий Ebr Запись запускается событиями (человек, движение). Длительность видео зависит от продолжительности события и подписки.
ETR (запись по событию) Etr Короткая запись (например, 10 секунд), которая запускается событиями.
Улицы в AR-режиме LiveView Запись отключена, но пользователи по-прежнему могут смотреть трансляцию.
Картинки Images При возникновении событий вместо видео записываются снимки.

Проверьте режимы записи

Чтобы посмотреть текущую конфигурацию записи, проверьте атрибуты типажа RecordingMode:

// 1. Obtain the trait flow from the device
private val recordingModeTraitFlow: Flow =
    device.traitFromType(RecordingMode, CAMERA_TYPES.first { device.has(it) })

// 2. Map the flow to recording mode options
data class RecordingModeOptions(
    val recordingMode: RecordingModeTrait.RecordingModeEnum,
    val index: Int,
    val available: Boolean,
    val readableString: String,
)

private val recordingModeOptions: Flow<List> =
    recordingModeTraitFlow.map { trait ->
        val supported = trait.supportedRecordingModes?.map { it.recordingMode } ?: emptyList()
        val available = trait.availableRecordingModes?.map { it.toInt() } ?: emptyList()

        supported.withIndex().map { (index, mode) ->
            RecordingModeOptions(
                recordingMode = mode,
                index = index,
                available = available.contains(index),
                readableString = mode.toReadableString(),
            )
        }
    }

Как изменить режим записи

Перед обновлением убедитесь, что выбранный индекс из атрибута supportedRecordingModes присутствует в атрибуте availableRecordingModes.

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

private suspend fun RecordingMode.updateRecordingMode(index: Int) {
    // Execute the command to update the selected mode
    this.setSelectedRecordingMode(index.toUByte())
}

Другие настройки

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

Измените ориентацию изображения

Ориентацию изображения (видео) с камеры можно изменить. Видео можно повернуть только на 180 градусов.

Чтобы изменить ориентацию изображения камеры, обновите атрибут imageRotation типажа CameraAvStreamManagement с помощью встроенной функции Kotlin setImageRotation:

// Change the camera's image orientation
val isRotated = false

cameraAvStreamManagement.update { setImageRotation(if (isRotated) 180.toUShort() else 0.toUShort()) }

Как включить или отключить ночную съемку

Чтобы включить или отключить ночное видение для камеры, используйте TriStateAutoEnum для обновления атрибута nightVision типажа CameraAvStreamManagement с помощью встроенной функции Kotlin setNightVision:

// Turn night vision on
cameraAvStreamManagement.update {
  setNightVision(CameraAvStreamManagementTrait.TriStateAutoEnum.On)
}

// Turn night vision off
CameraAvStreamManagement.update {
  setNightVision(CameraAvStreamManagementTrait.TriStateAutoEnum.Off)
}

Как изменить яркость индикатора состояния

Чтобы изменить яркость индикатора состояния, используйте ThreeLevelAutoEnum для обновления атрибута statusLightBrightness типа CameraAvStreamManagement с помощью встроенной функции Kotlin setStatusLightBrightness:

// Set the LED brightness to high
cameraAvStreamManagement.update {
  setStatusLightBrightness(CameraAvStreamManagementTrait.ThreeLevelAutoEnum.High)
}

// Set the LED brightness to low
cameraAvStreamManagement.update {
  setStatusLightBrightness(CameraAvStreamManagementTrait.ThreeLevelAutoEnum.Low)
}

Как изменить область просмотра камеры

Область просмотра камеры – это то же самое, что и функция масштабирования и кадрирования, описанная в статье Как увеличить и улучшить видео с камеры Nest.

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

(x1,y1) -- (x2,y1)
   |          |
(x1,y2) -- (x2,y2)

Значения для ViewportStruct зависят от реализации интерфейса и камеры в приложении. Чтобы настроить область просмотра видео с камеры, обновите атрибут viewport типажа CameraAvStreamManagement с помощью ViewportStruct, используя встроенную функцию Kotlin setViewport:

cameraAvStreamManagement
  .update { setViewport(
    CameraAvStreamManagementTrait.ViewportStruct(
      x1 = horizontalRange.rangeStart.roundToInt().toUShort(),
      x2 = horizontalRange.rangeEnd.roundToInt().toUShort(),
      y1 = verticalRange.rangeStart.roundToInt().toUShort(),
      y2 = verticalRange.rangeEnd.roundToInt().toUShort(),
    )
) }

Как включить или отключить аналитику

Каждое устройство может отдельно согласиться на отправку подробных аналитических данных в облако Google Home (см. Облачный мониторинг для Home API).

Чтобы включить аналитику для устройства, задайте для свойства analyticsEnabled объекта ExtendedGeneralDiagnosticsTrait значение true. Когда вы задаете значение для analyticsEnabled, для другого свойства, logUploadEnabled, автоматически задается значение true, что позволяет загружать файлы журналов аналитики в облако Google Home.

// Enable analytics
extendedGeneralDiagnostics.update {
  setAnalyticsEnabled(true)
}

// Disable analytics
extendedGeneralDiagnostics.update {
  setAnalyticsEnabled(false)
}

Конфигурации передачи и записи

В этом разделе рассматриваются настройки, связанные с качеством трансляции с камеры и активацией событий. Эти настройки управляются свойством PushAvStreamTransport.

Чтение настроек транспорта

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

val trait: PushAvStreamTransport = device.getTrait(PushAvStreamTransport)
val connections = trait.findTransport().transportConfigurations

// Locate the connection designated for recording
val recordingConnection = connections.firstOrNull {
    it.transportOptions.getOrNull()?.streamUsage == StreamUsageEnum.Recording
}

val options = recordingConnection?.transportOptions?.getOrNull()

// 1. Bandwidth Quality (Video Stream ID)
val videoStreamId = options?.videoStreamId?.getOrNull()

// 2. Wake-up Sensitivity (Motion Sensitivity)
val wakeUpSensitivity = options?.triggerOptions?.motionSensitivity?.getOrNull()

// 3. Max Event Length (Motion Trigger Time Control)
val maxEventLength = options?.triggerOptions?.motionTimeControl?.getOrNull()?.maxDuration

Обновить настройки транспорта

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

Чтобы изменить эти настройки, используйте команду modifyPushTransport с TransportOptionsStruct.

val toUpdate = TransportOptionsStruct(
    videoStreamId = OptionalValue.present(2u), // e.g., Max Quality
    triggerOptions = TransportTriggerOptionsStruct(
        motionSensitivity = OptionalValue.present(5u), // e.g., Medium
        motionTimeControl = OptionalValue.present(
            TransportMotionTriggerTimeControlStruct(maxDuration = 30u)
        )
    )
)

if (recordingConnection != null) {
    trait.modifyPushTransport(
        connectionId = recordingConnection.connectionId,
        transportOptions = toUpdate
    )
}

Определите качество пропускной способности

Свойство videoStreamId объекта TransportOptionsStruct соответствует определенной конфигурации видеопотока.

Чтобы получить поддерживаемые видеопотоки, используйте атрибут allocatedVideoStreams, который представляет собой список VideoStreamStructs. из свойства CameraAvStreamManagement для устройства.

Как настроить автоматический выход из спящего режима

Свойство motionSensitivity объекта TransportTriggerOptionsStruct соответствует следующим значениям:

Ярлык Значение (UByte)
Низкая 1u
Средний 5u
Высокий 10u

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

Свойство maxDuration объекта TransportMotionTriggerTimeControlStruct соответствует следующим значениям продолжительности (в секундах):

  • 10u, 15u, 30u, 60u, 120u, 180u

Как вывести устройство из эксплуатации

Чтобы удалить камеру из структуры, вызовите метод device.decommissionDevice(). При выводе из эксплуатации пользователь увидит экран подтверждения от Google с предупреждением о том, что вся непрерывная видеозапись, история событий, клипы и данные библиотеки знакомых лиц будут навсегда удалены из структуры.

Подробные инструкции и примеры кода приведены в статье Как удалить устройство.