Guía del dispositivo de timbre para iOS

El tipo de dispositivo Doorbell se implementa con dos rasgos: PushAvStreamTransportTrait, que controla el transporte de transmisiones de audio y video con protocolos basados en la transmisión, y WebRtcLiveViewTrait, que proporciona la capacidad de controlar transmisiones en vivo y la función de intercomunicador.

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

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

Timbre

GoogleDoorbellDeviceType

home.matter.6006.types.0113

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

Required Traits
     google PushAvStreamTransportTrait
     google WebRtcLiveViewTrait

Timbre

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

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

// [START get_device_information]
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!
let serialNumber = basicInfoTrait.attributes.serialNumber!
// [END get_device_information]

Cómo verificar la conectividad de un dispositivo

En realidad, 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 todas las características del dispositivo.

let lightConnectivity =
  dimmableLightDeviceType.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 rasgos estándar de Matter sigan en línea debido al enrutamiento local, pero los rasgos basados en la nube estarán sin conexión.

Cómo iniciar una transmisión en vivo

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:) del rasgo WebRtcLiveViewTrait, que devuelve tres valores:

  • Es el SDP de la sesión.
  • Es la duración de la sesión en segundos.
  • Es el ID de 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

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

Para iniciar TalkBack, llama al método startTalkback(mediaSessionId:optionalArgsProvider:) del rasgo 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)")
  }
}

Cómo habilitar e inhabilitar 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:) del rasgo PushAvStreamTransportTrait. Para inhabilitar la capacidad de grabación, pasa TransportStatusEnum.Inactive. En el siguiente ejemplo, incluimos estas llamadas en una sola llamada 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 encender o apagar el video de la cámara. Cuando el video de una cámara está encendido, se 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 capacidad 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 realizar esta acción:

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
}

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.

Cómo establecer la preferencia de uso de la 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 cambiar entre ellos.

Esta función se implementa actualizando el atributo currentEnergyBalance del rasgo EnergyPreference. El atributo acepta un índice 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 de 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

Para configurar esta función, actualiza el atributo currentLowPowerModeSensitivity del rasgo 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)
  }
}

Obtén el estado de carga de la batería

Para obtener el estado de carga actual del dispositivo (cargando, completamente cargado o sin cargar), usa el atributo batChargeState del rasgo 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 del rasgo 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 rasgo PowerSource.

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

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 del rasgo CameraAvStreamManagementTrait con la función setMicrophoneMuted integrada:

// 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 rasgo CameraAvStreamManagementTrait con la función setRecordingMicrophoneMuted integrada:

// 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 del altavoz del dispositivo, actualiza el atributo speakerVolumeLevel del rasgo CameraAvStreamManagementTrait con la función setSpeakerVolumeLevel integrada:

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

Otros parámetros de configuración

También se pueden controlar otros parámetros de configuración 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 del rasgo CameraAvStreamManagementTrait con la función setNightVision integrada:

// 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 LED de estado

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

// 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 visor de la cámara es el mismo que la función Zoom and Crop que se describe en el artículo de asistencia técnica sobre cómo hacer zoom en el video de la cámara Nest y mejorar la detección.

La ventana gráfica se define en un ViewportStruct que contiene cuatro valores, los cuales se usan como las coordenadas de la ventana gráfica. 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 y la implementación de la cámara de una app. En un nivel muy básico, para configurar el viewport del video de la cámara, actualiza el atributo viewport del rasgo 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 debe generarse 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
}

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

La sensibilidad de activación del dispositivo se usa para conservar la batería, ya que disminuye el rango en el que el dispositivo puede detectar actividad y aumenta el tiempo de activación después de detectar esa actividad.

En las APIs de Home, esto se puede configurar con la propiedad motionSensitivity del objeto triggerOptions en el objeto transportOptions del dispositivo. Estas opciones se definen dentro del rasgo PushAvStreamTransportTrait para cada dispositivo.

La sensibilidad de activación solo se puede establecer en los siguientes valores:

  • 1 = Baja
  • 5 = Mediana
  • 10 = Alto

El proceso de actualización consiste en encontrar la configuración de transporte para los flujos de grabación activos con el comando findTransport y, luego, modificar la configuración con el nuevo valor de sensibilidad con el comando modifyPushTransport.

El comando modifyPushTransport requiere que se pase el TransportOptionsStruct completo, por lo que primero debes copiar los valores existentes de la configuración actual. Consulta Genera un TransportOptionsStruct para una función de ayuda que haga esto.

func setWakeUpSensitivity(to value: UInt8) async {
  do {
    let connection = try await getRecordingConnection()
    guard let connection,
      let transportOptions = connection.transportOptions
    else {
      // Error - Transport options not available
      return
    }

    guard transportOptions.triggerOptions.motionSensitivity != nil else {
      // Error - Motion sensitivity not available to be updated for this device
      return
    }

    try await pushAvStreamTransportTrait.modifyPushTransport(
      connectionID: connection.connectionID,
      transportOptions: self.getTransportOptions(
        transportOptions: transportOptions,
        wakeUpSensitivity: value,
        maxEventLength: nil
      )
    )

  } catch {
    // Error
  }
}

Cómo ajustar la duración máxima de eventos

La duración máxima del evento es el período durante el cual la cámara grabará un clip para un evento. A través de las APIs de Home, esto se puede configurar, por dispositivo, con las mismas longitudes que a través de Google Home app (GHA), en intervalos de segundos:

  • 10 segundos
  • 15 segundos
  • 30 segundos
  • 60 segundos (1 minuto)
  • 120 segundos (2 minutos)
  • 180 segundos (3 minutos)

En las APIs de Home, esto se puede configurar con la propiedad motionTimeControl del objeto triggerOptions en el objeto transportOptions del dispositivo. Estas opciones se definen dentro del rasgo PushAvStreamTransportTrait para cada dispositivo.

El proceso de actualización consiste en encontrar la configuración de transporte para los flujos de grabación activos con el comando findTransport y, luego, modificar la configuración con el nuevo valor de duración del evento con el comando modifyPushTransport.

El comando modifyPushTransport requiere que se pase el TransportOptionsStruct completo, por lo que primero debes copiar los valores existentes de la configuración actual. Consulta Genera un TransportOptionsStruct para una función de ayuda que haga esto.

func setMaxEventLength(to value: UInt32) async {
  do {
    let connection = try await getRecordingConnection()
    guard let connection,
      let transportOptions = connection.transportOptions
    else {
      // Error - Transport options not available
      return
    }

    guard transportOptions.triggerOptions.motionTimeControl != nil else {
      // Error - Motion time control not available to be updated for this device
      return
    }

    try await pushAvStreamTransportTrait.modifyPushTransport(
      connectionID: connection.connectionID,
      transportOptions: self.getTransportOptions(
        transportOptions: transportOptions,
        wakeUpSensitivity: nil,
        maxEventLength: value
      )
    )

  } catch {
    // Error
  }
}

Configuración de la campanilla

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

Cómo cambiar el sonido de la campanilla

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

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

Luego, actualiza el atributo selectedChime del rasgo ChimeTrait con la función setSelectedChime integrada:

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

Cómo usar un timbre externo

El timbre se puede configurar para que use un timbre externo, 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 el timbre externo.

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

// 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, durante la que suena un timbre externo se puede configurar a través de las APIs de Home. Si el timbre externo admite una duración, es posible que el usuario quiera configurarla.

El valor establecido aquí depende de las especificaciones del timbre externo y de la duración recomendada del timbre.

Para cambiar la duración del timbre externo, actualiza el atributo externalChimeDurationSeconds del rasgo ChimeTrait con la función setExternalChimeDurationSeconds integrada:

// 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 timbre

Es posible que algunos timbres tengan sonidos que solo estén disponibles para los usuarios por un tiempo limitado. Por ejemplo, sonidos específicos para festividades. Se llaman temas de timbre.

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

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 que está activo desde el inicio de los tiempos. Si no se establece una hora de finalización, seguirá activa de forma indefinida. Si faltan ambos, el tema siempre está 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 <= currentDateTime
      && chimeTheme.endTimeSeconds ?? UInt64.max >= 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 rasgo ChimeThemes ChimeThemes.

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