Руководство по устройствам Doorbell для Android

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

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

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

Дверной звонок

GoogleDoorbellDevice

home.matter.6006.types.0113

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

Обязательные характеристики
     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

быстрые ответы.

Функция быстрых ответов позволяет пользователю отправлять на дверной звонок заранее заданные сообщения.

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

Функция быстрых ответов реализована с помощью черты PresetMessage.

Как воспроизвести заранее записанное сообщение

Чтобы воспроизвести стандартное сообщение, вызовите метод playPresetMessage, передав ему одно из строковых значений, найденных в свойстве availablePhraseTypes.

import com.google.home.HomeDevice
import com.google.home.HomeException
import com.google.home.google.GoogleDoorbellDevice
import com.google.home.google.PresetMessage
import kotlinx.coroutines.flow.first

suspend fun playDoorbellPresetMessage(device: HomeDevice, phraseTypeString: String) {
    try {
        // 1. Retrieve the first snapshot of the doorbell device type
        val doorbellDevice = device.type(GoogleDoorbellDevice).first()

        // 2. Extract the PresetMessage trait
        val presetMessageTrait = doorbellDevice.trait(PresetMessage)
        if (presetMessageTrait == null) {
            println("PresetMessage trait is not supported on this doorbell device.")
            return
        }

        // 3. (Optional) Check available phrase types supported by the device
        val availablePhrases = presetMessageTrait.availablePhraseTypes
        if (availablePhrases != null) {
            val phraseTypesList = availablePhrases.map { it.phraseType }
            println("Supported quick response phrases: $phraseTypesList")
        }

        // 4. Send the playPresetMessage command to the doorbell
        presetMessageTrait.playPresetMessage(phraseType = phraseTypeString)
        println("Preset message command sent successfully.")

    } catch (e: HomeException) {
        println("Home API error occurred: ${e.message}")
    } catch (e: Exception) {
        println("Unexpected failure: ${e.message}")
    }
}

Как выбрать язык озвучивания

Установить для устройства активный разговорный язык с определенной локалью (например, "en_US") с помощью метода setActiveLocale из признака LocalizationConfiguration.

import java.util.Locale

// Convert underscore format (en_US) to Java Locale
fun String.toLocale(): Locale = Locale.forLanguageTag(this.replace('_', '-'))

// Setting the active language
val trait: LocalizationConfiguration = device.getTrait(LocalizationConfiguration)
val selectedLocale = "en_US" // Target locale string
trait.update {
    setActiveLocale(selectedLocale)
}

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

Чтобы узнать, когда устройство в последний раз подключалось к облаку, используйте атрибут 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)
}

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

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

   Реализовано в тестовом приложении для Android   
val doorbellConnectivity = doorbellDevice.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) в метод startLiveView() признака WebRtcLiveView. В ответ вы получите WebRtcLiveViewTrait.StartLiveViewCommand.Response, содержащий три значения:

  • SDP для сеанса.
  • Длительность сеанса в секундах.
  • Идентификатор сеанса, который можно использовать для его продления или завершения.
suspend fun getWebRtcLiveViewTrait(cameraDevice: HomeDevice) {
 return cameraDevice.type(GoogleDoorbellDevice).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   

Чтобы запустить TalkBack, вызовите метод 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)
  }
}

Как включить и отключить возможность записи

Чтобы включить запись с камеры, передайте значение 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()
    }
  }

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

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

Динамическое изменение качества с помощью функции 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(GoogleDoorbellDevice)
        .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(GoogleDoorbellDevice).trait(WebRtcLiveView).first() ?: return
    try {
        trait.changeLiveViewQuality(mediaSessionId, qualityHint)
    } catch (e: Exception) {
        // Handle error
    }
}

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

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

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

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

Эта функция реализована путем обновления атрибута 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 приводится описание батареи, в том числе ее форм-фактор, химический состав и предпочтительный производитель.

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

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

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

Чтобы включить или отключить микрофон устройства, обновите атрибут 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 Обнаруживает сигналы тревоги, связанные с угарным газом, которые обычно распознаются по звуковому сигналу T4 (четыре коротких звуковых сигнала, за которыми следует пауза).

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

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

При разработке приложений для 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 (запись на основе событий) Ebr Запись запускается при обнаружении движения или человека. Продолжительность видео зависит от длительности мероприятия и подписки.
ETR (запись по триггеру события) Etr Короткая запись предварительного просмотра (например, 10 секунд), запускаемая событиями.
Трансляция с камеры 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())
}

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

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

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

Чтобы включить или отключить ночное видение для камеры, используйте 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 (см. Cloud Monitoring for Home APIs).

Чтобы включить аналитику для устройства, задайте для свойства 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

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

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

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

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

// Get a list of chimes and identify the currently selected one
private val doorbellChimeTraitFlow: Flow =
    device.traitFromType(Chime, GoogleDoorbellDevice)
val chimeSounds = doorbellChimeTraitFlow.first().installedChimeSounds ?: emptyList()

Затем обновите атрибут selectedChime признака Chime, используя встроенную функцию setSelectedChime Kotlin:

// Set the chime using the chimeId from the installed list
chimeSounds.firstOrNull { it.name == name }?.let { setSelectedChime(it.chimeId) }

Как использовать внешний звонок

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

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

Чтобы указать тип установленного внешнего звонка, используйте ExternalChimeType, чтобы обновить атрибут externalChime признака Chime с помощью встроенной функции Kotlin setExternalChime:

// Indicate the external chime is mechanical
chime.update {
  setExternalChime(ChimeTrait.ExternalChimeType.Mechanical)
}

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

Продолжительность звонка внешнего звонка в секундах можно настроить с помощью API Google Home. Если внешний звонок поддерживает настройку длительности, пользователь может захотеть ее задать.

Значение зависит от характеристик внешнего звонка и рекомендованной длительности сигнала.

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

// Change the external chime duration
chime.update {
  setExternalChimeDurationSeconds(newDuration.toUShort())
}

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

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

У некоторых дверных звонков есть мелодии, которые доступны пользователям только в течение ограниченного времени. Например, мелодии, связанные с праздниками. Это называется темами звонков.

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

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

// Get themes from the ChimeThemes trait
fun List<ChimeThemesTrait.ThemeStruct>.filterTimeboxedThemes():
    List<ChimeThemesTrait.ThemeStruct> {
  val now = timeSource.instant().epochSecond.toULong()
  return filter { chimeStruct: ChimeThemesTrait.ThemeStruct ->
    val startTime: ULong = chimeStruct.startTimeSeconds.getOrNull() ?: 0UL
    val endTime: ULong = chimeStruct.endTimeSeconds.getOrNull() ?: MAX_VALUE
    startTime <= now && now <= endTime
  }
}

val availableThemes =
  doorbellChimeThemesTraitFlow
    .first()
    .getAvailableThemes()
    .themes
    .filterTimeboxedThemes()

Если вы знаете название нужной темы, например Christmas, вы можете выбрать ее с помощью функции setSelectedTimeboxedThemeName() в рамках признака ChimeThemes:

// Select a theme using the ChimeThemes trait
val themeToSelect = "Christmas"
if (themeToSelect in availableThemeNames) {
  doorbellChimeThemesTraitFlow.first().setSelectedTimeboxedThemeName(themeToSelect)
}

Настройки объявлений для посетителей

Вы можете запрашивать и изменять настройки уведомлений о посетителях для дверных звонков с помощью черты VisitorAnnouncement API Home. Этот признак определяет, будет ли объявляться о присутствии посетителя на умных колонках или дисплеях Google, когда кто-то звонит в дверной звонок.

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

// Read the current visitor announcements state
val isEnabled = visitorAnnouncementTrait.visitorAnnouncementsEnabled

// Toggle the visitor announcements setting
visitorAnnouncementTrait.update {
    setVisitorAnnouncementsEnabled(true)
}

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

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

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