Guía del dispositivo de timbre para iOS

El tipo de dispositivo Doorbell se implementa con tres atributos: PushAvStreamTransportTrait, que controla el transporte de transmisiones de audio y video con protocolos basados en push, WebRtcLiveViewTrait, que proporciona la capacidad de controlar transmisiones en vivo y respuestas, y CameraSnapshotTrait, que proporciona instantáneas de vista previa almacenadas en caché y capturas de instantáneas en vivo.

Siempre verifica la compatibilidad de atributos y comandos de un dispositivo antes de usar cualquier función o intentar actualizar los atributos. Consulta Controla dispositivos en iOS para obtener más información.

Tipo de dispositivo de las APIs de Home Rasgos App de ejemplo de Swift Caso de uso

Timbre

GoogleDoorbellDeviceType

home.matter.6006.types.0113

Es un dispositivo que se activa con un botón ubicado fuera de una puerta y que emite una señal audible o visual, que se usa para llamar la atención de una persona que se encuentra del otro lado de la puerta. Los timbres pueden incluir transmisiones en vivo accesibles, comunicación bidireccional o eventos de detección.

Atributos obligatorios
     google PushAvStreamTransportTrait
     google WebRtcLiveViewTrait

Timbre

Obtén información básica sobre un dispositivo

   Implementado en la app de ejemplo para iOS   

El atributo BasicInformation incluye información como el nombre del proveedor, el ID del proveedor, el ID del producto, el nombre del producto (incluye información del modelo) y la versión de software de un dispositivo:

let vendorName = basicInfoTrait.attributes.vendorName!
let vendorID = basicInfoTrait.attributes.vendorID!
let productID = basicInfoTrait.attributes.productID!
let productName = basicInfoTrait.attributes.productName!
let softwareVersion = basicInfoTrait.attributes.softwareVersion!

Obtener número de serie

   Implementado en la app de ejemplo para iOS   

Para obtener el número de serie del dispositivo, usa el comando GetSerialNumber del atributo ExtendedBasicInformation. En el ejemplo, se muestra cómo guardar el número de serie en una variable llamada serialNumber:

// Assuming extendedBasicInformationTrait: Google.ExtendedBasicInformationTrait
let response = try await extendedBasicInformationTrait.getSerialNumber()
let serialNumber = response.serialNumber

Respuestas rápidas

La función de respuestas rápidas permite que el usuario envíe un mensaje predefinido a un dispositivo de timbre.

Esta función solo está disponible en dispositivos de timbre. La lista de mensajes predefinidos está disponible para la app del socio, que puede presentarlos al usuario para que elija uno. Los mensajes predefinidos no se pueden editar.

Las respuestas rápidas se implementan a través del atributo PresetMessage.

Cómo reproducir un mensaje predeterminado

Para reproducir un mensaje predeterminado, llama al método playPresetMessage y pásale uno de los valores de cadena que se encuentran en la propiedad availablePhraseTypes.


import GoogleHomeSDK
import GoogleHomeTypes

func playDoorbellPresetMessage(device: HomeDevice, phraseTypeString: String) async {
    // 1. Retrieve the GoogleDoorbellDeviceType helper on the device
    guard let doorbellDeviceType = await device.types.get(GoogleDoorbellDeviceType.self) else {
        print("This device is not a Google Doorbell or is currently uninitialized.")
        return
    }

    // 2. Extract the Google.PresetMessageTrait
    guard let presetMessageTrait = doorbellDeviceType.traits[Google.PresetMessageTrait.self] else {
        print("PresetMessageTrait is not supported on this device.")
        return
    }

    // 3. (Optional) Check available phrase types supported by the device
    if let availablePhrases = presetMessageTrait.attributes.availablePhraseTypes {
        let phraseTypeNames = availablePhrases.map { $0.phraseType }
        print("Supported quick response phrases: \(phraseTypeNames)")
    }

    // 4. Send the playPresetMessage command asynchronously
    do {
        try await presetMessageTrait.playPresetMessage(phraseType: phraseTypeString)
        print("Preset message successfully requested.")
    } catch {
        print("SDK error occurred playing preset message: \(error)")
    }
}

Configura el idioma hablado

Configura el idioma hablado

Establece el idioma hablado activo de un dispositivo en una configuración regional específica (por ejemplo, "en_US") con el método setActiveLocale de la característica LocalizationConfiguration.

// Setting the active language
// Assuming localizationConfigurationTrait: Matter.LocalizationConfigurationTrait
let selectedLocale = "en_US"
try await localizationConfigurationTrait.update {
    $0.setActiveLocale(selectedLocale)
}

Obtén la hora más reciente de contacto del dispositivo con la nube

Para encontrar la hora más reciente en la que el dispositivo tuvo contacto con la nube, usa el atributo lastContactTimestamp del rasgo ExtendedGeneralDiagnostics:

if let lastContactTimeStamp = extendedGeneralDiagnosticsTrait.attributes.lastContactTimestamp {
  self.lastContactTime = Date(timeIntervalSince1970: Double(lastConnectedTimeStamp))
}

Configuración del tipo de soporte de la cámara

La característica Mount contiene la configuración del soporte de la cámara y la información de estado. Puedes leer atributos como el estado de activación, el tipo de detección y el nombre del tipo de activación. Además, puedes usar el atributo Mount para anular la configuración predeterminada del tipo de activación.

// 1. Get the Mount trait
guard let mountTrait = deviceType.traits[Google.MountTrait.self] else {
  print("Mount trait not supported or configured on this device.")
  return
}

// 2. Read the current mount state, detection type, and type name
let mountState         = mountTrait.attributes.mountState         // Type: Google.MountTrait.MountStateEnum?
let mountDetectionType = mountTrait.attributes.mountDetectionType // Type: Google.MountTrait.MountDetectionTypeEnum?
let mountTypeName      = mountTrait.attributes.mountTypeName      // Type: String?

// 3. Update the mount type override
try await mountTrait.update { mutableTrait in
  mutableTrait.setMountTypeOverride(.official)
}

Verifica la conectividad de un dispositivo

La conectividad de un dispositivo se verifica a nivel del tipo de dispositivo, ya que algunos dispositivos admiten varios tipos. El estado que se devuelve es una combinación de los estados de conectividad de todos los rasgos de ese dispositivo.

let doorbellConnectivity = doorbellDeviceType.metadata.sourceConnectivity.connectivityState

Se puede observar un estado de partiallyOnline en el caso de tipos de dispositivos mixtos cuando no hay conectividad a Internet. Es posible que los atributos estándar de Matter sigan en línea debido al enrutamiento local, pero los atributos basados en la nube estarán sin conexión.

Obtén la dirección IP del dispositivo

Para encontrar la dirección IP del dispositivo, usa el atributo networkInterfaces de GeneralDiagnosticsTrait. Las direcciones se devuelven como objetos Data, a los que puedes dar formato de strings IPv4 o IPv6 estándar con el framework Network:

func getIpAddresses(trait: Matter.GeneralDiagnosticsTrait) -> [String] {
  let interfaces = trait.attributes.networkInterfaces ?? []
  var ipAddresses: [String] = []

  for interface in interfaces {
    for data in interface.iPv4Addresses {
      if let ipv4 = IPv4Address(data) {
        ipAddresses.append(String(describing: ipv4))
      }
    }
    for data in interface.iPv6Addresses {
      if let ipv6 = IPv6Address(data) {
        ipAddresses.append(String(describing: ipv6))
      }
    }
  }

  return ipAddresses
}

Cómo iniciar una transmisión en vivo

   Implementado en la app de ejemplo para iOS   

Para iniciar una transmisión en vivo, envía la cadena del protocolo de descripción de sesión (SDP) al método startLiveView(offerSdp:) de la característica WebRtcLiveViewTrait, que devuelve tres valores:

  • Es el SDP de la sesión.
  • Es la duración de la sesión en segundos.
  • El ID de la sesión, que se puede usar para extender o finalizar la sesión.
public func sendOffer(offerSdp: String) async throws
-> (answerSdp: String, mediaSessionId: String, liveViewDuration: TimeInterval)
{
  do {
    // Sending StartLiveView command
    let response = try await liveViewTrait.startLiveView(
      offerSdp: offerSdp
    )
    // Received StartLiveView response
    return (
      answerSdp: response.answerSdp,
      mediaSessionId: response.mediaSessionId,
      liveViewDuration: TimeInterval(response.liveSessionDurationSeconds)
    )
  } catch {
    // Failed to send StartLiveView command
    throw error
  }
}

Cómo extender una transmisión en vivo

   Implementado en la app de ejemplo para iOS   

Las transmisiones en vivo tienen una duración predeterminada después de la cual vencen. Para extender la duración de una transmisión activa, emite una solicitud de extensión con el método extendLiveView(mediaSessionId:optionalArgsProvider:):

public func extendLiveView(mediaSessionId: String) async throws {
  do {
    // Extending live view
    let extendedDuration = try await liveViewTrait.extendLiveView(mediaSessionId: mediaSessionId)
  } catch {
    // Failed to extend live view
    throw error
  }
}

Cómo iniciar y detener TalkBack

   Implementado en la app de ejemplo para iOS   

Para iniciar la respuesta por voz, llama al método startTalkback(mediaSessionId:optionalArgsProvider:) del atributo WebRtcLiveViewTrait. Para detenerla, usa stopTalkback(mediaSessionId:).

public func toggleTwoWayTalk(isOn: Bool, mediaSessionId: String) async throws {
  do {
    if isOn {
      try await liveViewTrait.startTalkback(mediaSessionId: mediaSessionId)
    } else {
      try await liveViewTrait.stopTalkback(mediaSessionId: mediaSessionId)
    }
  } catch {
    throw HomeError.commandFailed("Failed to toggle twoWayTalk: \(error)")
  }
}

Administra la transmisión en vivo

   Implementado en la app de ejemplo para iOS   

Ajustar la calidad de la transmisión en vivo es útil para optimizar el uso del ancho de banda según el contexto de visualización del cliente (por ejemplo, cambiar a una resolución más baja cuando se muestra una tarjeta de vista previa más pequeña, una vista de cuadrícula o el modo de pantalla en pantalla).

Cambiar la calidad de forma dinámica con el atributo WebRtcLiveView administra específicamente la resolución de la sesión de transmisión en vivo activa en un cliente en particular. No es lo mismo que configurar un parámetro de uso de ancho de banda para todo el dispositivo directamente en él, lo que afectaría a todos los usuarios simultáneos y la calidad de las grabaciones de video históricas guardadas en la nube.

En el siguiente ejemplo, se muestra cómo recuperar y actualizar la calidad de la transmisión en vivo de un dispositivo:

  • Recuperar las opciones de calidad compatibles: Obtén las resoluciones de transmisión disponibles compatibles con el dispositivo. El código consulta la característica WebRtcLiveView para exponer las calidades de transmisión admitidas como una lista de valores QualityHint (como .sd, .hd, .fhd, .qhd o .uhd) con la propiedad supportedQualityHints.

  • Cambiar la calidad de la transmisión en vivo: Aplica un QualityHint seleccionado para cambiar la resolución de la transmisión en vivo activa (por ejemplo, cambiar de definición estándar a alta definición). La función updateQualityHint usa el método changeLiveViewQuality del atributo WebRtcLiveView para aplicar la configuración de QualityHint seleccionada a la sesión multimedia activa.

public var supportedQualityHints: [Google.WebRtcLiveViewTrait.QualityHint] {
  return liveViewTrait?.attributes.supportedQualityHints ?? []
}

public func updateQualityHint(
  liveViewTrait: Google.WebRtcLiveViewTrait,
  hint: Google.WebRtcLiveViewTrait.QualityHint,
  mediaSessionId: String
) async {
  do {
    _ = try await liveViewTrait.changeLiveViewQuality(
      mediaSessionId: mediaSessionId,
      qualityHint: hint
    )
  } catch {
    // error
  }
}

Habilita e inhabilita la capacidad de grabación

Para habilitar la capacidad de grabación de la cámara, pasa TransportStatusEnum.Active al método setTransportStatus(transportStatus:optionalArgsProvider:) de la característica PushAvStreamTransportTrait. Para inhabilitar la capacidad de grabación, pásale TransportStatusEnum.Inactive. En el siguiente ejemplo, unimos estas llamadas en una sola que usa un Boolean para activar o desactivar la capacidad de grabación:

public func toggleIsRecording(isOn: Bool) {
  self.uiState = .loading

  guard let pushAvStreamTransportTrait else {
    // PushAvStreamTransportTrait not found.
    return
  }
  Task {
    do {
      try await pushAvStreamTransportTrait.setTransportStatus(
        transportStatus: isOn ? .active : .inactive)
      if isOn {
        do {
          self.player = try self.createWebRtcPlayer()
        } catch {
          // Failed to initialize WebRtcPlayer
          self.uiState = .disconnected
          return
        }
        await self.player?.initialize()
        self.uiState = .live
      } else {
        self.player = nil
        self.uiState = .off
      }
    } catch {
      // Failed to toggle onOff
    }
  }
}

Habilitar o inhabilitar la capacidad de grabación de la cámara es lo mismo que activar o desactivar el video de la cámara. Cuando el video de una cámara está activado, está grabando (para fines de eventos y clips relacionados).

Cuando la capacidad de grabación está inhabilitada (el video de la cámara está desactivado):

Comprueba si la función de grabación está habilitada

Para determinar si la capacidad de grabación de una cámara está habilitada, verifica si hay conexiones activas. En el siguiente ejemplo, se definen dos funciones para hacerlo:

public func isDeviceRecording() -> Bool {
  guard let pushAvStreamTransportTrait else {
    // PushAvStreamTransportTrait not found.
    return false
  }
  guard
    let hasActiveConnection =
      pushAvStreamTransportTrait
      .attributes
      .currentConnections?
      .contains(where: { $0.transportStatus == .active })
  else {
    return false
  }
  return hasActiveConnection
}

Recupera instantáneas de la cámara

   Implementado en la app de ejemplo para iOS   

Usa el atributo CameraSnapshot para recuperar instantáneas de imágenes estáticas de una cámara. Estas instantáneas son útiles en diferentes situaciones:

  • Como un mosaico de marcador de posición en la IU mientras se carga una transmisión en vivo de WebRTC
  • Como una imagen de vista previa estática que se actualiza a una transmisión en vivo cuando el usuario interactúa con ella
  • Como lienzo de fondo para dibujar y configurar zonas de actividad personalizadas

El atributo CameraSnapshot ofrece dos formas de recuperar instantáneas, según si necesitas una instantánea almacenada en caché para una renderización rápida o una instantánea en vivo a pedido.

Obtén una instantánea de vista previa almacenada en caché

Una instantánea de vista previa es una imagen almacenada en caché que el SDK puede recuperar sin activar la cámara. Este es el método más rápido para recuperar una imagen estática y es ideal para las páginas de resumen de la cámara o las cuadrículas del panel.

Para obtener la instantánea de la vista previa, lee el atributo de rasgo preview_image_url:

guard let cameraSnapshotTrait = deviceType.traits[
  Google.CameraSnapshotTrait.self
] else {
  print("CameraSnapshot trait not supported on this device.")
  return
}

if let previewImageUrlString = cameraSnapshotTrait
  .attributes.preview_image_url,
   let url = URL(string: previewImageUrlString) {
  // Load the preview image from the URL
}

Según la configuración y la actividad de la cámara, la URL de vista previa apunta a una imagen almacenada en caché generada a partir de un video reciente (el primer fotograma I del fragmento de video más reciente) o una instantánea que la cámara sube periódicamente.

Para indicar la actualidad de la imagen, la nube de Google Home incorpora un encabezado X-Home-Image-Captured-At en la imagen JPEG o PNG que se entrega, con formato UTC. Puedes analizar este encabezado en el cliente para mostrar la antigüedad de la instantánea en una app.

Obtén una instantánea en vivo on demand

Para los recorridos del usuario que requieren la visualización en vivo actual de la cámara, como la configuración de las zonas de actividad, puedes solicitar una instantánea en vivo. Cuando se activa una instantánea en vivo, se le indica a la cámara que se active, capture un fotograma nuevo y lo suba a la nube.

Para solicitar una instantánea en vivo, usa el comando getLiveSnapshot():

do {
  let response = try await cameraSnapshotTrait.getLiveSnapshot()
  let snapshotUrlString = response.snapshot_url

  if let url = URL(string: snapshotUrlString) {
    // Load the live snapshot image from the URL
  }
} catch {
  print("Failed to get live snapshot: \(error)")
}

Autentica solicitudes de instantáneas

   Implementado en la app de ejemplo para iOS   

Todas las URLs de instantáneas apuntan a contenido multimedia seguro y requieren un token de OAuth 2.0 para el alcance de la versión 2 de las APIs de Home. Para descargar y renderizar las imágenes, el token de OAuth se debe insertar como un token del portador en el encabezado Authorization de tu solicitud HTTP.

En el siguiente ejemplo, se muestra cómo configurar una solicitud de red segura para descargar una instantánea:

import UIKit

func loadSnapshotImage(from url: URL, home: Home) async -> UIImage? {
  do {
    // 1. Retrieve the OAuth access token from the Home APIs SDK
    let (accessToken, _) = try await home.permissions.authorization()

    // 2. Configure the HTTP GET request with the Bearer token
    var request = URLRequest(url: url)
    request.setValue(
      "Bearer \(accessToken)",
      forHTTPHeaderField: "Authorization"
    )

    // 3. Fetch the image data
    let (data, _) = try await URLSession.shared.data(for: request)
    return UIImage(data: data)
  } catch {
    print("Error loading snapshot image: \(error)")
    return nil
  }
}

Configuración de la batería

Se pueden controlar varios parámetros de configuración de la batería a través de las APIs de Home.

Establece la preferencia de uso de batería

Configurar el balance energético te permite establecer el equilibrio entre la duración de la batería y el rendimiento de un dispositivo. Puedes crear diferentes perfiles de batería, como "Extendido", "Equilibrado" y "Rendimiento", y alternar entre ellos.

Esta función se implementa actualizando el atributo currentEnergyBalance de la característica EnergyPreference. El atributo acepta un índice de número entero que corresponde a un perfil específico definido en la lista energyBalances del dispositivo (por ejemplo, 0 para EXTENDED, 1 para BALANCED y 2 para PERFORMANCE).

Un valor null para currentEnergyBalance indica que el dispositivo usa un perfil personalizado. Este es un estado de solo lectura.

A continuación, se muestra un ejemplo de una estructura que usará el atributo currentEnergyBalance, seguido del fragmento de código real que usa el atributo.

// Example energyBalances list
{
  "energy_balances": [
    {
      "step": 0,
      "label": "EXTENDED"
    },
    {
      "step": 50,
      "label": "BALANCED"
    },
    {
      "step": 100,
      "label": "PERFORMANCE"
    }
  ]
}
private func setBatteryUsage(to option: UInt8) async throws {
  _ = try await energyPreferenceTrait.update {
    $0.setCurrentEnergyBalance(option)
  }
}

Cómo activar el ahorro de batería automático

   Implementado en la app de ejemplo para iOS   

Para configurar esta función, actualiza el atributo currentLowPowerModeSensitivity de la característica EnergyPreference. Este atributo usa un índice para seleccionar un nivel de sensibilidad, en el que 0 suele representar Disabled y 1 representa Enabled o Automatic.

private func setAutoBatterySaver(to value: Bool) async throws {
  _ = try await energyPreferenceTrait.update {
    $0.setCurrentLowPowerModeSensitivity(value ? 1 : 0)
  }
}

Cómo obtener el estado de carga de la batería

   Implementado en la app de ejemplo para iOS   

Para obtener el estado de carga actual del dispositivo (cargando, completamente cargado o sin cargar), usa el atributo batChargeState de la característica PowerSource.

self.chargingState = powerSourceTrait.attributes.batChargeState

var description: String
switch self.chargingState {
case .isCharging:
  description = "Charging"
case .isAtFullCharge:
  description = "Full"
case .isNotCharging:
  description = "Not Charging"
default:
  description = "Unknown"
}

Cómo obtener el nivel de batería

Para obtener el nivel de batería actual, usa el atributo batChargeLevel de la característica PowerSource. El nivel es OK, Warning (bajo) o Critical.

self.batteryLevel = powerSourceTrait.attributes.batChargeLevel

var description: String
switch self.batteryLevel {
case .ok:
  description = "OK"
case .warning:
  description = "Warning"
case .critical:
  description = "Critical"
default:
  description = "Unknown"
}

Obtén la fuente de alimentación

Para determinar la fuente de alimentación que usa el dispositivo, usa los atributos BatPresent y wiredPresent del atributo PowerSource.

if powerSourceTrait.attributes.wiredPresent ?? false {
  self.powerSourceType = .wired
} else if powerSourceTrait.attributes.batPresent ?? false {
  self.powerSourceType = .battery
} else {
  self.powerSourceType = nil
}

Comprueba si es necesario reemplazar la batería

El atributo batReplaceability indica si la batería no se puede reemplazar, si el usuario puede reemplazarla o si se puede reemplazar en fábrica.

Si la batería se puede reemplazar, el atributo batReplacementNeeded de la característica PowerSource te indica si es necesario reemplazarla.

// Get the battery replacement status
var batteryStatus = powerSourceTrait.attributes.batReplacementNeeded

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

Cuando el usuario puede reemplazar la batería, el elemento batReplacementDescription la describe, incluida información como el factor de forma, la química y el fabricante preferido.

Configuración de audio

Se pueden controlar varios parámetros de configuración de audio a través de las APIs de Home.

Activar o desactivar el micrófono

Para activar o desactivar el micrófono del dispositivo, actualiza el atributo microphoneMuted de la característica CameraAvStreamManagementTrait con la función integrada setMicrophoneMuted:

// Turn the device's microphone on or off
func setMicrophone(on: Bool) async {
  do {
    _ = try await self.cameraAvStreamManagementTrait?.update {
      $0.setMicrophoneMuted(!on)
    }
  } catch {
    // Error
  }
}

Cómo activar o desactivar la grabación de audio

Para activar o desactivar la grabación de audio en el dispositivo, actualiza el atributo recordingMicrophoneMuted del atributo CameraAvStreamManagementTrait con la función integrada setRecordingMicrophoneMuted:

// Turn audio recording on or off for the device
func setAudioRecording(on: Bool) async {
  do {
    _ = try await self.cameraAvStreamManagementTrait?.update {
      $0.setRecordingMicrophoneMuted(!on)
    }
  } catch {
    // Error
  }
}

Cómo ajustar el volumen de la bocina

Para ajustar el volumen de la bocina del dispositivo, actualiza el atributo speakerVolumeLevel de la característica CameraAvStreamManagementTrait con la función integrada setSpeakerVolumeLevel:

// Adjust the camera speaker volume
func setSpeakerVolume(to value: UInt8) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setSpeakerVolumeLevel(value)
    }
  } catch {
    // Error
  }
}

Configuración de las zonas de actividad

   Implementado en la app de ejemplo para iOS   

La característica ZoneManagement proporciona una interfaz para administrar regiones de interés personalizadas (zonas de actividad) en dispositivos de cámara y timbre. Estas zonas se usan para filtrar la detección de eventos (como el movimiento de personas o vehículos) en áreas específicas dentro del campo visual del dispositivo.

El usuario configura las zonas de actividad en una aplicación de socio, lo que le permite dibujar zonas sobre áreas específicas en el campo visual de la cámara. Luego, estas zonas definidas por el usuario se traducen en las estructuras que usa este atributo. Para obtener más información sobre cómo funcionan las zonas de actividad, consulta Cómo configurar y usar las zonas de actividad.

Las zonas de actividad suelen definirse con coordenadas cartesianas 2D. El atributo proporciona el TwoDCartesianVertexStruct para los vértices y el TwoDCartesianZoneStruct para la definición de la zona (nombre, vértices, color y uso).

Cómo verificar las zonas de actividad

Para mostrar las zonas de actividad, verifica el atributo zones de la característica ZoneManagement.

let zoneManagementTrait: Google.ZoneManagementTrait

self.zones = zoneManagementTrait.attributes.zones ?? []

Cómo agregar una zona de actividad

Para crear una zona nueva, usa el comando createTwoDCartesianZone. Este comando toma un TwoDCartesianZoneStruct, que define el nombre, los vértices, el color y el uso de la zona.

En el siguiente ejemplo, se muestra cómo crear una zona llamada "Front Porch" con cuatro vértices, de color salmón (#F439A0), que se usa para la detección de movimiento.

import GoogleHomeSDK
import GoogleHomeTypes

func createFrontPorchZone(trait: Google.ZoneManagementTrait) async {
  // 1. Define the vertices for the zone (2D Cartesian coordinates)
  // Values are UInt16, typically scaled to the device's twoDCartesianMax.
  let vertices = [
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 260, y = 422),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 1049, y = 0),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 2048, y = 0),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 2048, y = 950),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 1630, y = 1349),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 880, y = 2048),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 0, y = 2048),
    Google.ZoneManagementTrait.TwoDCartesianVertexStruct(x = 638, y = 1090)
  ]

  // 2. Define the zone structure using the given SDK struct
  let newZone = Google.ZoneManagementTrait.TwoDCartesianZoneStruct(
    name: "Front Porch",
    use: [.motion], // ZoneUseEnum.motion
    vertices: vertices,
    // Color is a hex string (for example, Salmon/Pink)
    color: "#F439A0"
  )

  do {
    // 3. Execute the raw command to add the zone to the device
    // This returns the created zone's ID (UInt16).
    var newZoneID = try await trait.createTwoDCartesianZone(zone: newZone)
  } catch {
    // Error
  }
}

Cómo actualizar una zona de actividad

Para actualizar una zona existente, usa el comando updateTwoDCartesianZone. Este comando requiere el zoneId y el TwoDCartesianZoneStruct actualizado.

let zoneManagementTrait: Google.ZoneManagementTrait
let zoneID: UInt16
let zone: Google.ZoneManagementTrait.TwoDCartesianZoneStruct

do {
  _ = try await zoneManagementTrait.updateTwoDCartesianZone(
        zoneID: zoneID, zone: zone)
} catch {
  // Error
}

Cómo borrar una zona de actividad

Para quitar una zona, usa el comando removeZone con el zoneId específico.

let zoneManagementTrait: Google.ZoneManagementTrait
let zoneID: UInt16

do {
  _ = try await zoneManagementTrait.removeZone(zoneID: zoneID)
} catch {
  // Error
}

Activadores de eventos de sonido

La característica AvStreamAnalysis proporciona una interfaz para administrar los activadores de detección de eventos en dispositivos de cámara y timbre. Si bien los activadores basados en la visión (como personas o vehículos) pueden ser específicos de una zona, los activadores relacionados con el sonido suelen ser parámetros de configuración a nivel del dispositivo.

Los siguientes tipos de activadores están disponibles para la detección de sonido con el EventTriggerTypeEnum:

Modo Valor enumerado Descripción
Sonido Sound Detección de sonido general.
Persona que habla PersonTalking Detecta voz.
Ladrido de perro DogBark Detecta vocalizaciones caninas.
Rotura de vidrio GlassBreak Detecta el sonido de rotura de vidrio.
Alarma de humo SmokeAlarm Detecta alarmas de humo, que suelen reconocerse por el patrón audible T3 (tres pitidos cortos seguidos de una pausa).
alarma de monóxido de carbono CoAlarm Detecta alarmas de monóxido de carbono (CO), que suelen reconocerse por el patrón audible T4 (cuatro pitidos cortos seguidos de una pausa).

Cómo verificar el estado de la detección de sonido

Para mostrarle al usuario el estado actual de la detección de sonido, debes verificar qué admite el dispositivo y qué habilita su hardware. Los dos atributos que se deben verificar son los siguientes:

En el desarrollo para iOS, por lo general, accederías al atributo AvStreamAnalysis desde el dispositivo para leer estos atributos.

// Example struct to store event triggers
public struct EventTrigger: Equatable {
  public var id: Google.AvStreamAnalysisTrait.EventTriggerTypeEnum
  public var enabled: Bool
}

let avStreamAnalysisTrait: Google.AvStreamAnalysisTrait

let possibleEventTriggers = avStreamAnalysisTrait.attributes.supportedEventTriggers ?? []
let enabledEventTriggers = avStreamAnalysisTrait.attributes.enabledEventTriggers ?? []

let eventTriggers [EventTrigger] = []
for trigger in possibleEventTriggers {
  self.eventTriggers.append(
    EventTrigger(
      id: trigger,
      enabled: enabledEventTriggers.contains(trigger)
    )
  )
}

Actualiza el conjunto de activadores habilitados

Para actualizar el conjunto de activadores habilitados, usa el comando SetOrUpdateEventDetectionTriggers, que toma una lista de estructuras EventTriggerEnablement.

// Example struct to store event triggers
public struct EventTrigger: Equatable {
  public var id: Google.AvStreamAnalysisTrait.EventTriggerTypeEnum
  public var enabled: Bool
}

let avStreamAnalysisTrait: Google.AvStreamAnalysisTrait
let eventTriggers: [EventTrigger]

let enabledEventTriggers = eventTriggers.map {
  Google.AvStreamAnalysisTrait.EventTriggerEnablement(
    eventTriggerType: $0.id,
    enablementStatus: $0.enabled ? .enabled : .disabled
  )
}

try await avStreamAnalysisTrait.setOrUpdateEventDetectionTriggers(
  eventTriggerEnablements: enabledEventTriggers
)

Modos de grabación

   Implementado en la app de ejemplo para iOS   

La característica RecordingMode proporciona una interfaz para administrar el comportamiento de grabación de video y de imágenes en dispositivos de cámara y timbre. Permite que los usuarios elijan entre la grabación continua, la grabación basada en eventos o la inhabilitación total de la grabación (solo visualización en vivo).

RecordingModeEnum define las estrategias de grabación disponibles:

Modo Valor enumerado Descripción
Inhabilitada Disabled La grabación está completamente inhabilitada. Se usa principalmente en dispositivos heredados.
CVR (grabación de video continua) Cvr El video se graba las 24 horas, todos los días. Requiere una suscripción (por ejemplo, Google Home Premium.
EBR (grabación basada en eventos) Ebr La grabación se activa por eventos (persona, movimiento). La duración del video depende de la duración del evento y de la suscripción.
ETR (grabación activada por eventos) Etr Grabación de vista previa corta (por ejemplo, 10 segundos) activada por eventos
Live View LiveView La grabación está inhabilitada, pero los usuarios aún pueden acceder a la transmisión en vivo.
Imágenes fijas Images Se graban instantáneas en lugar de videos cuando ocurren eventos.

Verifica los modos de grabación

Para mostrar la configuración de grabación actual, verifica los atributos del rasgo RecordingMode:

// Example struct to store recording modes.
public struct RecordingMode: Hashable {
  public let id: UInt8
  public let mode: Google.RecordingModeTrait.RecordingModeEnum
}

let recordingModeTrait: Google.RecordingModeTrait

if let availableRecordingModes = recordingModeTrait.attributes.availableRecordingModes,
   let supportedRecordingModes = recordingModeTrait.attributes.supportedRecordingModes,
   let selectedRecordingMode = recordingModeTrait.attributes.selectedRecordingMode {

  var recordingModes: [RecordingMode] = []

  for recordingModeId in availableRecordingModes {
    guard Int(recordingModeId) < supportedRecordingModes.count,
          Int(recordingModeId) >= 0 else {
      // Out of bounds error
    }

    recordingModes.append(
      RecordingMode(
        id: recordingModeId,
        mode: supportedRecordingModes[Int(recordingModeId)].recordingMode,
      )
    )
  }
}

Cómo cambiar el modo de grabación

Antes de actualizar, asegúrate de que el índice elegido del atributo supportedRecordingModes esté presente en el atributo availableRecordingModes.

Para actualizar el modo seleccionado, usa la función setSelectedRecordingMode y pasa el índice del modo elegido:

let recordingModeTrait: Google.RecordingModeTrait
let recordingModeID: UInt8

_ = try await recordingModeTrait.update {
  $0.setSelectedRecordingMode(recordingModeID)
}

Otros parámetros de configuración

Se pueden controlar varios parámetros de configuración más a través de las APIs de Home.

Cómo activar o desactivar la visión nocturna

Para activar o desactivar la visión nocturna de la cámara, usa TriStateAutoEnum para actualizar el atributo nightVision de la característica CameraAvStreamManagementTrait con la función integrada setNightVision:

// Turn night vision on or off
func setNightVision(
  to value: Google.CameraAvStreamManagementTrait.TriStateAutoEnum
) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setNightVision(value)
    }
  } catch {
    // Error
  }
}

Cómo cambiar el brillo de la luz de estado LED

Para cambiar el brillo del LED de estado, usa ThreeLevelAutoEnum para actualizar el atributo statusLightBrightness de la característica CameraAvStreamManagementTrait con la función integrada setStatusLightBrightness:

// Set the LED brightness
func setStatusLightBrightness(
  to value: Google.CameraAvStreamManagementTrait.ThreeLevelAutoEnum
) async {
  do {
    _ = try await cameraAvStreamManagementTrait.update {
      $0.setStatusLightBrightness(value)
    }
  } catch {
    // Error
  }
}

Cómo cambiar el viewport de la cámara

El viewport de la cámara es el mismo que el de la función Zoom and Crop que se describe en el artículo de ayuda Cómo acercar y mejorar el video de la cámara Nest.

El viewport se define en un ViewportStruct que contiene cuatro valores, que se usan como las coordenadas del viewport. Las coordenadas se definen de la siguiente manera:

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

La determinación de los valores de ViewportStruct depende de la IU de la app y de la implementación de la cámara. En un nivel muy básico, para establecer el viewport del video de la cámara, actualiza el atributo viewport de la característica CameraAvStreamManagementTrait con un ViewportStruct, usando la función integrada setViewport.

func setCrop(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16) {

  let viewport = Google.CameraAvStreamManagementTrait.ViewportStruct(
    x1: x1,
    y1: y1,
    x2: x2,
    y2: y2
  )

  Task {
    do {
      try await cameraAvStreamManagementTrait.update {
        $0.setViewport(viewport)
      }
    } catch {
      // Error
    }
  }

}

Genera un TransportOptionsStruct

Algunos parámetros de configuración requieren modificaciones en las propiedades de un TransportOptionsStruct, que luego se pasa a las opciones de transporte de una conexión de transmisión. En el caso de Swift, esta estructura se debe generar antes de actualizar cualquier propiedad.

Usa esta función auxiliar para generar el struct que se usará con los siguientes cambios de configuración:

func getTransportOptions(
  transportOptions: Google.PushAvStreamTransportTrait.TransportOptionsStruct,
  wakeUpSensitivity: UInt8?,
  maxEventLength: UInt32?
) async throws
  -> Google.PushAvStreamTransportTrait.TransportOptionsStruct
{

  var newMotionTimeControl:
    Google.PushAvStreamTransportTrait.TransportMotionTriggerTimeControlStruct? = nil
  if let maxEventLength {
    guard let motionTimeControl = transportOptions.triggerOptions.motionTimeControl else {
      throw HomeError.failedPrecondition(
        // Error - cannot update max event length without motion time control
    }
    newMotionTimeControl =
      Google.PushAvStreamTransportTrait.TransportMotionTriggerTimeControlStruct(
        initialDuration: motionTimeControl.initialDuration,
        augmentationDuration: motionTimeControl.augmentationDuration,
        maxDuration: maxEventLength,
        blindDuration: motionTimeControl.blindDuration
      )
  }

  return Google.PushAvStreamTransportTrait.TransportOptionsStruct(
    streamUsage: .recording,
    videoStreamID: nil,
    audioStreamID: nil,
    tlsEndpointID: transportOptions.tlsEndpointID,
    url: transportOptions.url,
    triggerOptions: Google.PushAvStreamTransportTrait.TransportTriggerOptionsStruct(
      triggerType: .motion,
      motionZones: nil,
      motionSensitivity: wakeUpSensitivity,
      motionTimeControl: newMotionTimeControl,
      maxPreRollLen: nil
    ),
    ingestMethod: .cmafIngest,
    containerOptions: Google.PushAvStreamTransportTrait.ContainerOptionsStruct(
      containerType: .cmaf,
      cmafContainerOptions: nil
    ),
    expiryTime: nil
  )
}

private func getRecordingConnection() async throws
  -> Google.PushAvStreamTransportTrait.TransportConfigurationStruct?
{
  guard let pushAvStreamTransportTrait else {
    // Error - PushAvStreamTransport trait not available
    return nil
  }

  let connections = try await pushAvStreamTransportTrait.findTransport().transportConfigurations

  for connection in connections {
    guard let transportOptions = connection.transportOptions,
      transportOptions.streamUsage == .recording
    else {
      continue
    }

    return connection
  }

  return nil
}

Habilita o inhabilita las estadísticas

Cada dispositivo puede aceptar de forma individual enviar datos de estadísticas detallados a la nube de Google Home (consulta Cloud Monitoring para las APIs de Home).

Para habilitar las estadísticas de un dispositivo, establece la propiedad analyticsEnabled) de ExtendedGeneralDiagnosticsTrait en true. Cuando configuras analyticsEnabled, otra propiedad, logUploadEnabled, se establece automáticamente en true, lo que permite que los archivos de registro de estadísticas se suban a la nube de Google Home.

// Enable analytics
_ = try await extendedGeneralDiagnosticsTrait.update {
  $0.setAnalyticsEnabled(true)
}

// Disable analytics
_ = try await extendedGeneralDiagnosticsTrait.update {
  $0.setAnalyticsEnabled(false)
}

Configuración de transporte y grabación

En esta sección, se abordan los parámetros de configuración relacionados con la calidad de la transmisión de la cámara y la activación de eventos. Estos parámetros de configuración se administran con el atributo PushAvStreamTransport.

Leer la configuración de transporte

En esta sección, se muestra cómo recuperar la configuración actual de un dispositivo de cámara o timbre. Recupera el atributo PushAvStreamTransport, busca la conexión específica que se usa para grabar y, luego, extrae los valores actuales de la calidad del ancho de banda, la sensibilidad de activación y la duración máxima del evento.

// Assuming access to
// var pushAvStreamTransportTrait: Google.PushAvStreamTransportTrait
let connections = try await pushAvStreamTransportTrait.findTransport().transportConfigurations

// Locate the connection designated for recording
let recordingConnection = connections.first { connection in
    guard let transportOptions = connection.transportOptions else { return false }
    return transportOptions.streamUsage == .recording
}

let options = recordingConnection?.transportOptions

// 1. Bandwidth Quality (Video Stream ID)
let videoStreamId = options?.videoStreamID

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

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

Actualiza la configuración de transporte

En esta sección, se muestra cómo cambiar la configuración de transporte. Crea un TransportOptionsStruct nuevo que contiene los valores nuevos y, luego, usa el comando modifyPushTransport para enviar estos parámetros de configuración actualizados al dispositivo y aplicarlos a la conexión de grabación que se encontró en el paso anterior.

Para modificar estos parámetros de configuración, usa el comando modifyPushTransport con un TransportOptionsStruct.

// Example: Updating to Max Quality and 30s duration
let currentOptions = recordingConnection!.transportOptions!
let newOptions = Google.PushAvStreamTransportTrait.TransportOptionsStruct(
    streamUsage: .recording,
    videoStreamID: 2, // Max Quality
    tlsEndpointID: currentOptions.tlsEndpointID,
    url: currentOptions.url,
    triggerOptions: Google.PushAvStreamTransportTrait.TransportTriggerOptionsStruct(
        triggerType: .motion,
        motionSensitivity: 5, // Medium
        motionTimeControl: Google.PushAvStreamTransportTrait.TransportMotionTriggerTimeControlStruct(
            initialDuration: currentOptions.triggerOptions.motionTimeControl?.initialDuration ?? 10,
            augmentationDuration: currentOptions.triggerOptions.motionTimeControl?.augmentationDuration ?? 5,
            maxDuration: 30,
            blindDuration: currentOptions.triggerOptions.motionTimeControl?.blindDuration ?? 0
        )
    ),
    ingestMethod: currentOptions.ingestMethod,
    containerOptions: currentOptions.containerOptions
)

try await pushAvStreamTransportTrait.modifyPushTransport(
    connectionID: recordingConnection!.connectionID,
    transportOptions: newOptions
)

Determina la calidad del ancho de banda

La propiedad videoStreamId de TransportOptionsStruct corresponde a una configuración de transmisión de video específica.

Para obtener las transmisiones de video compatibles, consulta el atributo allocatedVideoStreams, que es una lista de VideoStreamStructs. del atributo CameraAvStreamManagement del dispositivo.

Cómo ajustar la sensibilidad de activación del dispositivo

La propiedad motionSensitivity de TransportTriggerOptionsStruct corresponde a los siguientes valores:

Etiqueta Valor (UInt8)
Baja 1
Medio 5
Alto 10

Ajusta la duración máxima de los eventos

La propiedad maxDuration de TransportMotionTriggerTimeControlStruct corresponde a las siguientes duraciones de UInt32 (en segundos):

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

Configuración de la campanilla

Se pueden controlar varios parámetros de configuración de la campanilla del timbre a través de las APIs de Home.

Cómo cambiar el sonido de la campanilla

Para cambiar el sonido de la campanilla del timbre, primero obtén la lista de sonidos de campanilla que están instalados en el dispositivo con el atributo installedChimeSounds del atributo ChimeTrait:

doorbellChimeTrait.attributes.installedChimeSounds?.compactMap { chimeSound in
  return chimeSound.chimeID, chimeSound.name
}

Luego, actualiza el atributo selectedChime de la característica ChimeTrait con la función integrada setSelectedChime:

func setDoorbellChime(chimeID: UInt8) async {
  do {
    _ = try await doorbellChimeTrait.update {
      $0.setSelectedChime(chimeID)
    }
  } catch {
    // Error
  }
}

Usa una campanilla externa

   Implementado en la app de ejemplo para iOS   

El timbre se puede configurar para usar una campanilla externa, como una campana mecánica instalada dentro de la casa. Esto se debe configurar durante la instalación del timbre para evitar posibles daños en la campanilla externa.

Para indicar qué tipo de timbre externo está instalado, usa ExternalChimeType para actualizar el atributo externalChime del rasgo ChimeTrait con la función integrada setExternalChime:

// Indicate the external chime is mechanical
func setExternalChime(to value: Google.ChimeTrait.ExternalChimeType) async {
  do {
    _ = try await doorbellChimeTrait.update {
      $0.setExternalChime(value)
    }
  } catch {
    // Error
  }
}

Cómo cambiar la duración de la campanilla externa

La duración, en segundos, que suena una campanilla externa se puede configurar a través de las APIs de Home. Si la campanilla externa admite una duración de la campanilla, es posible que el usuario quiera configurarla.

El valor que se establece aquí depende de las especificaciones de la campanilla externa y de la duración recomendada de la campanilla.

Para cambiar la duración de la campanilla externa, actualiza el atributo externalChimeDurationSeconds de la característica ChimeTrait con la función integrada setExternalChimeDurationSeconds:

// Change the external chime duration
func setExternalChimeDuration(to value: UInt16) async {
  do {
    _ = try await doorbellChimeTrait.update {
      $0.setExternalChimeDuration(value)
    }
  } catch {
    // Error
  }
}

Cómo habilitar un tema de campanilla

   Implementado en la app de ejemplo para iOS   

Algunos timbres pueden tener campanadas que solo están disponibles para los usuarios durante un tiempo limitado. Por ejemplo, campanadas específicas para las festividades. Estos se denominan temas de campanilla.

Para ver qué temas de campanilla están disponibles para un usuario, crea un filtro de intervalo de tiempo y úsalo para filtrar los resultados del comando getAvailableThemes del atributo ChimeThemes. Esto devuelve una lista de los temas disponibles, incluidos sus nombres.

En el siguiente ejemplo, se muestra cómo filtrar la lista. Un tema se considera activo si la hora actual se encuentra dentro de sus horas de inicio y finalización (los valores startTimeSeconds y endTimeSeconds, respectivamente). Si no se establece una hora de inicio, se considera activa desde el principio de los tiempos. Si no se establece una hora de finalización, seguirá activa de forma indefinida. Si faltan ambos, el tema siempre estará activo.

let chimeThemes = try await chimeThemeTrait.getAvailableThemes().themes

if !chimeThemes.isEmpty {
  var chimeThemeSettings = []
  for chimeTheme in chimeThemes {
    let currentDateTime = UInt64(Date().timeIntervalSince1970)

    // Only show chime themes that are active.
    if chimeTheme.startTimeSeconds ?? 0 &lt;= currentDateTime
      &amp;&amp; chimeTheme.endTimeSeconds ?? UInt64.max &gt;= currentDateTime
    {
      self.chimeThemeSettings.append(chimeTheme.name)
    }
  }
}

Una vez que tengas el nombre del tema que deseas, como Christmas, puedes seleccionarlo con la función setSelectedTimeboxedThemeName() en el atributo ChimeThemes ChimeThemes.

private func setChimeTheme(to value: String) async throws {
  _ = try await chimeThemeTrait.update {
    $0.setSelectedTimeboxedThemeName(value)
  }
}

Configuración de anuncios de visitantes

Puedes consultar y administrar la configuración de los anuncios de visitantes para los timbres con el atributo VisitorAnnouncement de las APIs de Home. Este atributo controla si se anuncia la presencia de un visitante en las bocinas o pantallas inteligentes de Google cuando alguien toca el timbre.

En el siguiente ejemplo, se muestra cómo comprobar si los anuncios de visitantes están habilitados y cómo actualizar este parámetro de configuración:

let visitorAnnouncementsEnabled: Bool = visitorAnnouncementTrait.attributes.visitorAnnouncementsEnabled

let value: Bool
_ = try await self.visitorAnnouncementTrait?.update {
  $0.setVisitorAnnouncementsEnabled(value)
}

Cómo retirar un dispositivo

Para quitar un timbre de una estructura, llama a device.decommission(). La baja muestra al usuario una pantalla de confirmación administrada por Google en la que se advierte que se borrarán de forma permanente todos los datos del historial de video continuo, el historial de eventos, los clips y la biblioteca de rostros conocidos en toda la estructura.

Para obtener instrucciones detalladas y ejemplos de código, consulta Quita un dispositivo.