Przewodnik po dzwonku do drzwi na Androida

Typ urządzenia Dzwonek jest implementowany za pomocą 2 cech:PushAvStreamTransport, która obsługuje transport strumieni audio i wideo za pomocą protokołów opartych na push, orazWebRtcLiveView, która umożliwia sterowanie transmisjami na żywo i funkcją talkback.

Przed użyciem jakichkolwiek funkcji lub próbą zaktualizowania atrybutów zawsze sprawdzaj, czy urządzenie obsługuje atrybuty i polecenia. Więcej informacji znajdziesz w artykule Sterowanie urządzeniami na Android.

Typ urządzenia interfejsów Home API Cechy Przykładowa aplikacja w Kotlinie Przypadek użycia

Dzwonek

GoogleDoorbellDevice

home.matter.6006.types.0113

Urządzenie uruchamiane przyciskiem na zewnątrz drzwi, które emituje sygnał dźwiękowy lub wizualny, używane do zwrócenia uwagi osoby znajdującej się po drugiej stronie drzwi. Dzwonki do drzwi mogą mieć funkcje transmisji na żywo z ułatwieniami dostępu, dwukierunkowego połączenia głosowego lub wykrywania zdarzeń.

Wymagane cechy
     google PushAvStreamTransport
     google WebRtcLiveView

Dzwonek

Uzyskiwanie podstawowych informacji o urządzeniu

   Wdrożono w przykładowej aplikacji na Androida   

Cechy BasicInformation zawierają informacje takie jak nazwa dostawcy, identyfikator dostawcy, identyfikator produktu, nazwa produktu (zawiera informacje o modelu) i wersja oprogramowania urządzenia:

// 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}")
    }

Uzyskiwanie numeru seryjnego

Aby uzyskać numer seryjny urządzenia, użyj polecenia GetSerialNumber cechy ExtendedBasicInformation. Przykład pokazuje zapisanie numeru seryjnego w zmiennej o nazwie serialNumber:

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

szybkie odpowiedzi.

Funkcja szybkich odpowiedzi umożliwia użytkownikowi wysłanie wstępnie zdefiniowanej wiadomości do dzwonka.

Ta funkcja jest dostępna tylko na dzwonkach. Lista predefiniowanych wiadomości jest dostępna dla aplikacji partnera, która może wyświetlać je użytkownikowi do wyboru. Wstępnie zdefiniowanych wiadomości nie można edytować.

Szybkie odpowiedzi są implementowane za pomocą cechy PresetMessage.

Odtwarzanie gotowej wiadomości

Aby odtworzyć gotową wiadomość, wywołaj metodę playPresetMessage i przekaż jej jedną z wartości ciągu znaków znalezionych we właściwości 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}")
    }
}

Ustawianie języka wypowiedzi

Ustaw aktywny język mówiony urządzenia na określony region (np. „en_US”) za pomocą metody setActiveLocale cechy 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)
}

Pobieranie najnowszej godziny kontaktu urządzenia z chmurą

Aby znaleźć ostatni czas, w którym urządzenie miało kontakt z chmurą, użyj atrybutu lastContactTimestamp cechy ExtendedGeneralDiagnostics:

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

Ustawienia typu mocowania kamery

Cechy Mount zawierają ustawienia mocowania kamery i informacje o stanie. Możesz odczytywać atrybuty, takie jak stan montażu, typ wykrywania i nazwa typu montażu. Dodatkowo możesz użyć cechy Mount, aby zastąpić domyślną konfigurację typu mocowania.

// 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)
}

Sprawdzanie połączenia urządzenia

Połączenie urządzenia jest sprawdzane na poziomie typu urządzenia, ponieważ niektóre urządzenia obsługują wiele typów. Zwracany stan jest kombinacją stanów łączności wszystkich cech na tym urządzeniu.

   Wdrożono w przykładowej aplikacji na Androida   
val lightConnectivity = dimmableLightDevice.metadata.sourceConnectivity.connectivityState

Stan PARTIALLY_ONLINE może wystąpić w przypadku różnych typów urządzeń, gdy nie ma połączenia z internetem. Matter standardowe cechy mogą być nadal online ze względu na lokalne przekierowywanie, ale cechy oparte na chmurze będą offline.

Uzyskiwanie adresu IP urządzenia

Aby znaleźć adres IP urządzenia, użyj atrybutu networkInterfaces cechy GeneralDiagnostics. Adresy są zwracane jako tablice bajtów, które możesz sformatować jako standardowe ciągi IPv4 lub 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()

Rozpoczynanie transmisji na żywo

   Wdrożono w przykładowej aplikacji na Androida   

Aby rozpocząć transmisję na żywo, wyślij ciąg znaków protokołu SDP do metody startLiveView() cechy WebRtcLiveView, która zwraca obiekt WebRtcLiveViewTrait.StartLiveViewCommand.Response zawierający 3 wartości:

  • SDP sesji.
  • Czas trwania sesji w sekundach.
  • Identyfikator sesji, który może służyć do przedłużenia lub zakończenia sesji.
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)

Przedłużanie transmisji na żywo

   Wdrożono w przykładowej aplikacji na Androida   

Transmisje na żywo mają z góry określony czas trwania, po którym wygasają. Aby wydłużyć czas trwania aktywnego strumienia, wyślij prośbę o przedłużenie za pomocą metody 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
}

Włączanie i wyłączanie TalkBack

   Wdrożono w przykładowej aplikacji na Androida   

Aby uruchomić TalkBack, wywołaj metodę startTalkback() cechy WebRtcLiveView. Aby zatrzymać, użyj 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)
  }
}

Włączanie i wyłączanie możliwości nagrywania

Aby włączyć funkcję nagrywania kamery, przekaż wartość TransportStatusEnum.Active do metody PushAvStreamTransport cechy setTransportStatus(). Aby wyłączyć możliwość nagrywania, przesuń suwak TransportStatusEnum.Inactive. W poniższym przykładzie umieszczamy te wywołania w jednym wywołaniu, które używa Boolean, aby włączać i wyłączać możliwość nagrywania:

// 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)
  }
}

Włączenie lub wyłączenie funkcji nagrywania kamery jest równoznaczne z włączeniem lub wyłączeniem wideo z kamery. Gdy wideo z kamery jest włączone, kamera nagrywa (na potrzeby zdarzeń i powiązanych klipów).

Gdy funkcja nagrywania jest wyłączona (wideo z kamery jest wyłączone):

  • Kamera może nadal być widoczna jako online zgodnie z connectivityState typu urządzenia.
  • Nie można uzyskać dostępu do transmisji na żywo, a kamera nie wykrywa żadnych zdarzeń w chmurze.

Sprawdzanie, czy funkcja nagrywania jest włączona

Aby sprawdzić, czy funkcja nagrywania kamery jest włączona, sprawdź, czy są aktywne jakiekolwiek połączenia. Poniższy przykład definiuje 2 funkcje, które to robią:

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

Innym sposobem sprawdzenia jest użycie funkcji findTransport() z predykatem:

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

Pobieranie zrzutów z kamery

Użyj cechy CameraSnapshot, aby pobrać statyczne migawki z kamery. Zrzuty te są przydatne w kilku scenariuszach:

  • jako kafelek zastępczy w interfejsie podczas wczytywania transmisji na żywo WebRTC,
  • jako statyczny obraz podglądu, który po interakcji użytkownika zmienia się w transmisję na żywo;
  • jako tło do rysowania i konfigurowania niestandardowych stref aktywności;

Cechę CameraSnapshot można wykorzystać na 2 sposoby w zależności od tego, czy potrzebujesz zrzutu z pamięci podręcznej do szybkiego renderowania, czy zrzutu na żywo na żądanie.

Wyświetlanie podglądu z pamięci podręcznej

Podgląd to obraz w pamięci podręcznej, który SDK może pobrać bez włączania aparatu. Jest to najszybsza metoda pobierania obrazu statycznego, która idealnie sprawdza się w przypadku stron z podsumowaniem informacji o kamerze lub siatek paneli.

Aby uzyskać podgląd, odczytaj atrybut cechy 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
}

W zależności od konfiguracji kamery i aktywności adres URL podglądu wskazuje obraz w pamięci podręcznej wygenerowany na podstawie ostatnich nagrań wideo (pierwsza klatka I najnowszego fragmentu wideo) lub zrzut okresowo przesyłany przez kamerę.

Aby wskazać aktualność obrazu, chmura Google Home umieszcza w serwowanym obrazie JPEG/PNG nagłówek X-Home-Image-Captured-At w formacie UTC. Możesz przeanalizować ten nagłówek po stronie klienta, aby wyświetlić w aplikacji wiek migawki.

Tworzenie zrzutu na żywo na żądanie

W przypadku ścieżek użytkownika, które wymagają bieżącego podglądu na żywo z kamery, np. podczas konfigurowania stref aktywności, możesz poprosić o wykonanie zdjęcia na żywo. Wywołanie migawki na żywo powoduje wybudzenie kamery, zrobienie nowego zdjęcia i przesłanie go do chmury.

Aby poprosić o zrzut na żywo, użyj polecenia 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}")
}

Uwierzytelnianie żądań zrzutów

Wszystkie adresy URL migawek wskazują bezpieczne multimedia i wymagają tokena OAuth 2.0 w zakresie Home API w wersji 2. Aby pobrać i wyrenderować obrazy, musisz wstrzyknąć token OAuth jako token Bearer w nagłówku Authorization żądania HTTP.

Przykład poniżej pokazuje, jak skonfigurować bezpieczne żądanie sieciowe pobrania migawki:

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()
    }
  }

Zarządzanie transmisją na żywo

Dostosowywanie jakości transmisji na żywo jest przydatne do optymalizacji wykorzystania przepustowości w zależności od kontekstu oglądania przez klienta (np. przełączanie na niższą rozdzielczość podczas wyświetlania mniejszego kafelka podglądu, widoku siatki lub trybu obrazu w obrazie).

Dynamiczna zmiana jakości za pomocą atrybutu WebRtcLiveView umożliwia zarządzanie rozdzielczością aktywnej sesji transmisji na żywo na konkretnym kliencie. Nie jest to to samo co skonfigurowanie ustawienia wykorzystania przepustowości na poziomie urządzenia bezpośrednio na urządzeniu, które miałoby wpływ na wszystkich jednocześnie oglądających i jakość historycznych nagrań wideo zapisanych w chmurze.

Poniższy przykład pokazuje, jak pobrać i zaktualizować jakość transmisji na żywo na urządzeniu:

  • Pobieranie obsługiwanych opcji jakości: pobierz dostępne rozdzielczości strumieniowania obsługiwane przez urządzenie. Kod wysyła zapytanie do atrybutu WebRtcLiveViewcechysupportedQualityHints w przepływie typu urządzenia i udostępnia obsługiwane jakości jako StateFlow zawierający listę QualityHintwartości (np. QUALITY_HINT_SD, QUALITY_HINT_HD, QUALITY_HINT_FHD, QUALITY_HINT_QHD lub QUALITY_HINT_UHD).

  • Zmień jakość transmisji na żywo: zastosuj wybraną QualityHint, aby zmienić rozdzielczość aktywnej transmisji na żywo (np. przejść z rozdzielczości standardowej na wysoką). Funkcja changeQuality rozwiązuje cechę transmisji na żywo urządzenia i wywołuje funkcję changeLiveViewQuality z aktywną konfiguracją mediaSessionId i wybraną konfiguracją 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
    }
}

Ustawienia baterii

Różnymi ustawieniami baterii można sterować za pomocą interfejsów Home API.

Ustawianie preferencji wykorzystania baterii

Ustawienie bilansu energetycznego umożliwia skonfigurowanie kompromisu między czasem pracy na baterii a wydajnością urządzenia. Możesz utworzyć różne profile baterii, np. „Wydłużony”, „Zrównoważony” i „Wydajność”, i przełączać się między nimi.

Ta funkcja jest wdrażana przez aktualizację atrybutu currentEnergyBalance cechy EnergyPreference. Atrybut przyjmuje liczbę całkowitą odpowiadającą konkretnemu profilowi zdefiniowanemu na liście energyBalances urządzenia (np. 0 dla EXTENDED, 1 dla BALANCED i 2 dla PERFORMANCE).

Wartość null w przypadku currentEnergyBalance oznacza, że urządzenie używa profilu niestandardowego. Jest to stan tylko do odczytu.

Poniżej znajdziesz przykład struktury, której będzie używał atrybut currentEnergyBalance, a następnie fragment kodu, który go wykorzystuje.

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

Włączanie automatycznego oszczędzania baterii

Aby skonfigurować tę funkcję, zaktualizuj atrybut currentLowPowerModeSensitivity cechy EnergyPreference. Ten atrybut używa indeksu do wyboru poziomu wrażliwości, gdzie 0 zwykle oznacza Disabled, a 1 oznacza Enabled lub 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) }
}

Sprawdzanie stanu ładowania baterii

Aby uzyskać bieżący stan ładowania urządzenia (ładowanie, naładowane w pełni lub nieładowane), użyj atrybutu batChargeState cechy 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"
}

Sprawdzanie poziomu baterii

Aby uzyskać aktualny poziom baterii, użyj atrybutu batChargeLevel cechy PowerSource. Poziom to OK, Warning (niski) lub 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"
}

Uzyskiwanie źródła zasilania

Aby określić źródło zasilania urządzenia, użyj atrybutów BatPresent i wiredPresent cechy PowerSource.

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

Sprawdź, czy bateria wymaga wymiany.

Atrybut batReplaceability określa, czy baterii nie można wymienić, czy może to zrobić użytkownik lub producent.

Atrybut batReplacementNeeded cechy PowerSource informuje, czy bateria wymaga wymiany.

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

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

Jeśli bateria jest wymieniana przez użytkownika, pole batReplacementDescription zawiera opis baterii, w tym informacje takie jak kształt, skład chemiczny i preferowany producent.

Ustawienia dźwięku

Różnymi ustawieniami dźwięku można sterować za pomocą interfejsów API Home.

Włączanie i wyłączanie mikrofonu

Aby włączyć lub wyłączyć mikrofon urządzenia, zaktualizuj atrybut microphoneMuted cechy CameraAvStreamManagement za pomocą wbudowanej funkcji setMicrophoneMuted w języku Kotlin:

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

Włączanie i wyłączanie nagrywania dźwięku

Aby włączyć lub wyłączyć nagrywanie dźwięku na urządzeniu, zaktualizuj atrybut recordingMicrophoneMutedCameraAvStreamManagement za pomocą wbudowanej funkcji setRecordingMicrophoneMuted w języku Kotlin:

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

Dostosowywanie głośności głośnika

Aby dostosować głośność głośnika urządzenia, zaktualizuj atrybut speakerVolumeLevel cechy CameraAvStreamManagement za pomocą wbudowanej funkcji setSpeakerVolumeLevel w języku Kotlin:

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

Ustawienia miejsc aktywności

   Wdrożono w przykładowej aplikacji na Androida   

Cechą ZoneManagement jest interfejs do zarządzania niestandardowymi obszarami zainteresowania (miejscami aktywności) na kamerach i dzwonkach do drzwi. Strefy te służą do filtrowania wykrywania zdarzeń (np. ruchu osoby lub pojazdu) w określonych obszarach w polu widzenia urządzenia.

Miejsca aktywności są konfigurowane przez użytkownika w aplikacji partnera. Umożliwia to wyznaczanie stref w określonych obszarach pola widzenia kamery. Zdefiniowane przez użytkownika strefy są następnie przekształcane w struktury używane przez ten atrybut. Więcej informacji o tym, jak działają strefy aktywności, znajdziesz w artykule Konfigurowanie i używanie stref aktywności.

Strefy aktywności są zwykle definiowane za pomocą 2-wymiarowych współrzędnych kartezjańskich. Cechą jest TwoDCartesianVertexStruct dla wierzchołków i TwoDCartesianZoneStruct dla definicji strefy (nazwa, wierzchołki, kolor i użycie).

Sprawdzanie miejsc aktywności

Aby wyświetlić strefy aktywności, sprawdź atrybut zones cechy 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()
  }

Dodawanie strefy aktywności

Aby utworzyć nową strefę, użyj polecenia createTwoDCartesianZone. To polecenie przyjmuje TwoDCartesianZoneStruct, który określa nazwę, wierzchołki, kolor i użycie strefy.

Poniższy przykład pokazuje, jak utworzyć strefę o nazwie „Front Porch” (Weranda) z 4 wierzchołkami w kolorze łososiowym (#F439A0), która będzie używana do wykrywania ruchu.

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}")
  }
}

Aktualizowanie strefy aktywności

Aby zaktualizować istniejącą strefę, użyj polecenia updateTwoDCartesianZone. To polecenie wymaga zoneId i zaktualizowanego TwoDCartesianZoneStruct.

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

Usuwanie strefy aktywności

Aby usunąć strefę, użyj polecenia removeZone z określonym argumentem zoneId.

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

Aktywatory zdarzeń dźwiękowych

Cecha AvStreamAnalysis udostępnia interfejs do zarządzania wyzwalaczami wykrywania zdarzeń na kamerach i dzwonkach do drzwi. Wywoływanie na podstawie obrazu (np. wykrywanie osób lub pojazdów) może być ograniczone do określonej strefy, ale wywoływanie na podstawie dźwięku jest zwykle konfigurowane na poziomie urządzenia.

W przypadku wykrywania dźwięku za pomocą EventTriggerTypeEnum dostępne są te typy reguł:

Tryb Wartość typu wyliczeniowego Opis
Dźwięk Sound Ogólne wykrywanie dźwięku.
Ktoś coś mówi PersonTalking wykrywa mowę;
Szczekanie psa DogBark Wykrywa wokalizacje psów.
Odgłos tłuczonego szkła GlassBreak Wykrywa odgłos tłuczonego szkła.
Alarm z czujnika dymu SmokeAlarm Wykrywa alarmy dymowe, które często są rozpoznawane po sygnale dźwiękowym T3 (3 krótkie sygnały dźwiękowe, po których następuje przerwa).
alarm z czujnika tlenku węgla CoAlarm Wykrywa alarmy z czujników tlenku węgla, które zwykle są rozpoznawane po sygnale dźwiękowym T4 (4 krótkie sygnały dźwiękowe, po których następuje przerwa).

Sprawdzanie stanu wykrywania dźwięku

Aby wyświetlić użytkownikowi bieżący stan wykrywania dźwięku, musisz sprawdzić, co obsługuje urządzenie i co jest włączone przez sprzęt urządzenia. Sprawdź te 2 atrybuty:

W przypadku tworzenia aplikacji na Androida z użyciem Kotlin Flows zwykle obserwujesz cechę AvStreamAnalysisHomeDevice.

// 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)
        )
      }
  }

Aktualizowanie zestawu włączonych aktywatorów

Aby zaktualizować zestaw włączonych wyzwalaczy, użyj polecenia SetOrUpdateEventDetectionTriggers które przyjmuje listę struktur 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)
}

Tryby nagrywania

   Wdrożono w przykładowej aplikacji na Androida   

Cechą RecordingMode jest interfejs do zarządzania nagrywaniem wideo i obrazów na kamerach i dzwonkach do drzwi. Umożliwia użytkownikom wybór między nagrywaniem ciągłym, nagrywaniem opartym na zdarzeniach lub całkowitym wyłączeniem nagrywania (tylko podgląd na żywo).

RecordingModeEnum określa dostępne strategie nagrywania:

Tryb Wartość typu wyliczeniowego Opis
Wyłączono Disabled Nagrywanie jest całkowicie wyłączone. Używany głównie przez starsze urządzenia.
CVR (Continuous Video Recording) Cvr Filmy są nagrywane przez całą dobę. Wymaga subskrypcji (np. Google Home Premium).
EBR (nagrywanie zdarzeń) Ebr Nagrywanie jest aktywowane przez zdarzenia (osoba, ruch). Długość filmu zależy od czasu trwania wydarzenia i subskrypcji.
ETR (nagrywanie wywoływane przez zdarzenie) Etr krótkie nagranie podglądu (np. 10 sekund) wywoływane przez zdarzenia.
Widok Live View LiveView Nagrywanie jest wyłączone, ale użytkownicy nadal mogą oglądać transmisję na żywo.
Obrazy statyczne Images Zamiast filmów rejestrowane są migawki, gdy wystąpią zdarzenia.

Sprawdzanie trybów nagrywania

Aby wyświetlić bieżącą konfigurację nagrywania, sprawdź atrybuty cechy 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(),
            )
        }
    }

Zmiana trybu nagrywania

Przed aktualizacją upewnij się, że wybrany indeks z atrybutu supportedRecordingModes występuje w atrybucie availableRecordingModes.

Aby zaktualizować wybrany tryb, użyj funkcji setSelectedRecordingMode, przekazując indeks wybranego trybu:

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

Inne ustawienia

Za pomocą interfejsów Home API można sterować różnymi innymi ustawieniami.

Włączanie i wyłączanie widzenia w nocy

Aby włączyć lub wyłączyć widzenie w nocy w przypadku kamery, użyj TriStateAutoEnum, aby zaktualizować atrybut nightVision cechy CameraAvStreamManagement za pomocą wbudowanej funkcji setNightVision w języku Kotlin:

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

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

Zmiana jasności diody LED stanu

Aby zmienić jasność diody LED stanu, użyj ThreeLevelAutoEnum do zaktualizowania atrybutu statusLightBrightness cechy CameraAvStreamManagement za pomocą wbudowanej funkcji setStatusLightBrightness Kotlin:

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

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

Zmiana widocznego obszaru kamery

Widok z kamery jest taki sam jak w przypadku funkcji powiększania i kadrowania opisanej w artykule pomocy Powiększanie i ulepszanie obrazu z kamery Nest.

Obszar wyświetlania jest zdefiniowany w ViewportStruct, który zawiera 4 wartości używane jako współrzędne obszaru wyświetlania. Współrzędne są zdefiniowane w ten sposób:

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

Określanie wartości parametru ViewportStruct zależy od interfejsu aplikacji i implementacji kamery. Aby ustawić obszar widoku filmu z kamery na bardzo podstawowym poziomie, zaktualizuj atrybut viewport cechy CameraAvStreamManagement za pomocą ViewportStruct, używając wbudowanej funkcji setViewport w Kotlinie:

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(),
    )
) }

Włączanie i wyłączanie statystyk

Każde urządzenie może indywidualnie wyrazić zgodę na wysyłanie szczegółowych danych analitycznych do chmury Google Home (patrz interfejsy Cloud Monitoring for Home API).

Aby włączyć analizy na urządzeniu, ustaw wartość właściwości analyticsEnabled elementu ExtendedGeneralDiagnosticsTrait na true. Gdy ustawisz analyticsEnabled, inna właściwość, logUploadEnabled, zostanie automatycznie ustawiona na true, co umożliwi przesyłanie plików dziennika analitycznego do chmury Google Home.

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

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

Konfiguracje transportu i nagrywania

W tej sekcji znajdziesz ustawienia związane z jakością transmisji z kamery i wywoływaniem zdarzeń. Tymi ustawieniami zarządza cecha PushAvStreamTransport.

Odczytywanie ustawień transportu

W tej sekcji pokazujemy, jak pobrać bieżącą konfigurację z kamery lub dzwonka. Pobiera cechę PushAvStreamTransport, znajduje konkretne połączenie użyte do nagrywania, a następnie wyodrębnia bieżące wartości jakości pasma, czułości wybudzania i maksymalnej długości zdarzenia.

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

Aktualizowanie ustawień transportu

W tej sekcji dowiesz się, jak zmienić ustawienia transportu. Tworzy nowy TransportOptionsStruct zawierający nowe wartości, a następnie używa polecenia modifyPushTransport, aby wysłać te zaktualizowane ustawienia z powrotem na urządzenie i zastosować je do połączenia nagrywania znalezionego w poprzednim kroku.

Aby zmodyfikować te ustawienia, użyj modifyPushTransportpolecenia z parametrem 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
    )
}

Określanie jakości przepustowości

Właściwość videoStreamId obiektu TransportOptionsStruct odpowiada konkretnej konfiguracji strumienia wideo.

Aby uzyskać obsługiwane strumienie wideo, skorzystaj z atrybutu allocatedVideoStreams, który jest listą VideoStreamStructs. z atrybutu CameraAvStreamManagement urządzenia.

Dostosowywanie czułości wybudzania urządzenia

Właściwość motionSensitivity atrybutu TransportTriggerOptionsStruct odpowiada tym wartościom:

Etykieta Wartość (UByte)
Niski 1u
Średni 5u
Wysoki 10u

Dostosowywanie maksymalnego czasu trwania zdarzenia

Właściwość maxDuration w obiekcie TransportMotionTriggerTimeControlStruct odpowiada tym czasom trwania (w sekundach):

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

Ustawienia dzwonka

Różnymi ustawieniami dzwonka można sterować za pomocą interfejsów Home API.

Zmienianie dźwięku gongu

Aby zmienić dźwięk dzwonka, najpierw pobierz listę dźwięków dzwonka zainstalowanych na urządzeniu, korzystając z atrybutu installedChimeSounds cechy 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()

Następnie zaktualizuj atrybut selectedChime cechy Chime za pomocą wbudowanej funkcji setSelectedChime Kotlin:

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

Używanie zewnętrznego dzwonka

   Wdrożono w przykładowej aplikacji na Androida   

Dzwonek można skonfigurować tak, aby korzystał z zewnętrznego dzwonka, np. mechanicznego dzwonka zainstalowanego w domu. Należy to skonfigurować podczas instalacji dzwonka, aby uniknąć potencjalnych uszkodzeń zewnętrznego dzwonka.

Aby wskazać typ zainstalowanego dzwonka zewnętrznego, użyj elementu ExternalChimeType, aby zaktualizować atrybut externalChime cechy Chime za pomocą wbudowanej funkcji setExternalChime w języku Kotlin:

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

Zmiana czasu trwania zewnętrznego gongu

Czas dzwonienia zewnętrznego dzwonka w sekundach można skonfigurować za pomocą interfejsów Home API. Jeśli dzwonek zewnętrzny obsługuje czas trwania dzwonka, użytkownik może chcieć go skonfigurować.

Wartość ustawiona w tym miejscu zależy od specyfikacji zewnętrznego gongu i zalecanej długości dzwonka.

Aby zmienić czas trwania zewnętrznego dzwonka, zaktualizuj atrybut externalChimeDurationSeconds cechy Chime za pomocą wbudowanej funkcji setExternalChimeDurationSeconds w języku Kotlin:

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

Włączanie motywu dzwonka

   Wdrożono w przykładowej aplikacji na Androida   

Niektóre dzwonki mogą mieć dzwonki, które są dostępne dla użytkowników tylko przez ograniczony czas. Na przykład dzwonki związane ze świętami. Są one nazywane motywami dzwonka.

Aby sprawdzić, które motywy dzwonka są dostępne dla użytkownika, utwórz filtr przedziału czasowego i użyj go do filtrowania wyników polecenia getAvailableThemes() z cechy ChimeThemes. Zwróci to listę dostępnych motywów, w tym ich nazwy.

Poniższy przykład pokazuje, jak filtrować listę. Motyw jest uznawany za aktywny, jeśli bieżąca godzina mieści się w zakresie czasu jego rozpoczęcia i zakończenia (odpowiednio wartości startTimeSecondsendTimeSeconds). Jeśli nie ustawisz godziny rozpoczęcia, będzie ona uznawana za aktywną od początku. Jeśli nie ustawisz czasu zakończenia, będzie ona aktywna bezterminowo. Jeśli brakuje obu tych elementów, motyw jest zawsze aktywny.

// 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()

Gdy poznasz nazwę motywu, np. Christmas, możesz go wybrać za pomocą funkcji setSelectedTimeboxedThemeName()ChimeThemes cechy:

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

Ustawienia komunikatów o gościach

Ustawieniami komunikatów o gościach w przypadku dzwonków możesz zarządzać za pomocą interfejsów Home API, korzystając z VisitorAnnouncement cechy. Ta cecha określa, czy obecność gościa jest ogłaszana na głośnikach lub wyświetlaczach Google, gdy ktoś użyje dzwonka.

Poniższy przykład pokazuje, jak sprawdzić, czy komunikaty dla gości są włączone, i jak zaktualizować to ustawienie:

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

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