Przewodnik po urządzeniach z Androidem z funkcją kamery

Typ urządzenia Kamera jest implementowany za pomocą 3 atrybutów: PushAvStreamTransport, który obsługuje transport strumieni audio i wideo za pomocą protokołów push, WebRtcLiveView, który umożliwia sterowanie transmisjami na żywo i funkcją TalkBack, oraz CameraSnapshot, który zapewnia podgląd zrzutów ekranu z pamięci podręcznej i zrzuty ekranu na żywo.

Przed użyciem jakichkolwiek funkcji lub próbą aktualizacji atrybutów zawsze sprawdź, czy urządzenie obsługuje dany atrybut i polecenie. Więcej informacji znajdziesz w artykule Sterowanie urządzeniami naAndroid.

Typ urządzenia w interfejsach API Google Home Cechy Przykładowa aplikacja w języku Kotlin Przypadek użycia

Aparat

GoogleCameraDevice

home.matter.6006.types.0158

Urządzenie, które rejestruje zdjęcia lub filmy. Kamery mogą oferować dostęp do transmisji na żywo, rozmowy dwukierunkowej lub wykrywania zdarzeń.

Wymagane cechy
     google PushAvStreamTransport
     google WebRtcLiveView

Aparat

Uzyskiwanie podstawowych informacji o urządzeniu

   Wdrożone w przykładowej aplikacji na Androida   

Atrybut BasicInformation zawiera informacje takie jak nazwa dostawcy, identyfikator dostawcy, identyfikator produktu, nazwa produktu (w tym 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. W przykładzie pokazano zapisywanie numeru seryjnego w zmiennej o nazwie serialNumber:

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

Pobieranie czasu ostatniego kontaktu urządzenia z chmurą

Aby znaleźć ostatni moment, 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

Atrybut Mount zawiera ustawienia uchwytu kamery i informacje o jego stanie. Możesz odczytywać atrybuty, takie jak stan montowania, typ wykrywania i nazwa typu montowania. Możesz też użyć cechy Mount, aby zastąpić domyślną konfigurację typu podłączenia.

// 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 połączenia wszystkich cech na tym urządzeniu.

val cameraConnectivity = cameraDevice.metadata.sourceConnectivity.connectivityState

Stan PARTIALLY_ONLINE może być widoczny w przypadku różnych typów urządzeń, gdy nie ma połączenia z internetem. Standardowe cechy Matter mogą nadal być dostępne online dzięki lokalnemu routingowi, ale cechy działające w 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żna 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żone w przykładowej aplikacji na Androida   

Aby rozpocząć transmisję na żywo, wyślij ciąg Session Description Protocol (SDP) do metody startLiveView() cechy WebRtcLiveView, która zwraca 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(GoogleCameraDevice).trait(WebRtcLiveView).first {
    it?.metadata?.sourceConnectivity?.connectivityState == ConnectivityState.ONLINE
  }

}

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

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

   ...

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

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

Przedłużanie transmisji na żywo

   Wdrożone w przykładowej aplikacji na Androida   

Transmisje na żywo mają wstępnie ustawiony czas trwania, po którym wygasają. Aby wydłużyć czas trwania aktywnej transmisji, wyślij prośbę o rozszerzenie 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żone w przykładowej aplikacji na Androida   

Aby rozpocząć rozmowę, 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)
  }
}

Zarządzanie transmisją na żywo

Dostosowanie jakości transmisji na żywo jest przydatne do optymalizacji wykorzystania przepustowości na podstawie kontekstu wyświetlania klienta (np. przełączenie na niższą rozdzielczość podczas wyświetlania mniejszego kafelka podglądu, widoku siatki lub trybu obrazu w obrazie).

Dynamiczna zmiana jakości za pomocą cechy WebRtcLiveView pozwala zarządzać rozdzielczością aktywnej sesji transmisji na żywo na konkretnym kliencie. Nie jest to to samo co skonfigurowanie ustawienia wykorzystania przepustowości na całym urządzeniu bezpośrednio na urządzeniu, co wpłynęłoby na wszystkich jednoczesnych widzów 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 o atrybut supportedQualityHints cechy WebRtcLiveView w przepływie typu urządzenia i udostępnia obsługiwane jakości jako StateFlow zawierający listę wartości QualityHint (takich jak QUALITY_HINT_SD, QUALITY_HINT_HD, QUALITY_HINT_FHD, QUALITY_HINT_QHD lub QUALITY_HINT_UHD).

  • Zmień jakość transmisji na żywo: zastosuj wybraną wartość QualityHint, aby zmienić rozdzielczość aktywnej transmisji na żywo (np. przełączyć ze standardowej rozdzielczości na wysoką rozdzielczość). Funkcja changeQuality rozpoznaje cechę transmisji na żywo urządzenia i wywołuje changeLiveViewQuality z aktywnym mediaSessionId i wybraną konfiguracją QualityHint.

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

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

Włączanie i wyłączanie funkcji nagrywania

Aby włączyć funkcję nagrywania, przekaż TransportStatusEnum.Active do metody setTransportStatus() cechy PushAvStreamTransport. Aby wyłączyć możliwość nagrywania, przekaż wartość TransportStatusEnum.Inactive. W przykładzie poniżej opakowujemy te wywołania w jedno wywołanie, które używa Boolean do przełączania funkcji 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 przez kamerę jest równoznaczne z włączeniem lub wyłączeniem nagrywania przez nią filmów. Gdy nagrywanie przez kamerę jest włączone, kamera nagrywa (na potrzeby zdarzeń i powiązanych klipów).

Gdy funkcja nagrywania jest wyłączona (obraz z kamery jest wyłączony):

  • Kamera może nadal wyświetlać się 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 jest włączona, sprawdź, czy są aktywne jakieś połączenia. W poniższym przykładzie zdefiniowano 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

   Wdrożone w przykładowej aplikacji na Androida   

Użyj cechy CameraSnapshot, aby pobierać statyczne migawki z kamery. Te zrzuty są przydatne w kilku sytuacjach:

  • 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 miejsc aktywności;

Cecha CameraSnapshot umożliwia pobieranie zdjęć na 2 sposoby, w zależności od tego, czy potrzebujesz zdjęcia z pamięci podręcznej do szybkiego renderowania, czy zdjęcia na żywo na żądanie.

Pobieranie podglądu z pamięci podręcznej

Zrzut podglądu to obraz z pamięci podręcznej, który pakiet SDK może pobrać bez wybudzania kamery. Jest to najszybsza metoda pobierania obrazu statycznego, która idealnie nadaje się do stron przeglądu kamer lub siatek panelu.

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 i aktywności kamery adres URL podglądu wskazuje obraz z pamięci podręcznej wygenerowany na podstawie ostatniego nagrania wideo (pierwsza klatka kluczowa ostatniego fragmentu filmu) 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ć wiek migawki w aplikacji.

Pobieranie zrzutu na żywo na żądanie

W przypadku ścieżek użytkownika, które wymagają bieżącego podglądu na żywo z kamery (np. konfigurowania miejsc aktywności), możesz poprosić o migawkę na żywo. Uruchomienie podglądu na żywo powoduje wybudzenie kamery, przechwycenie nowej klatki i przesłanie jej 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

   Wdrożone w przykładowej aplikacji na Androida   

Wszystkie adresy URL zdjęć wskazują bezpieczne multimedia i wymagają tokena OAuth 2.0 dla zakresu interfejsów Home API w wersji 2. Aby pobrać i wyrenderować obrazy, token OAuth musi zostać wstawiony jako token okaziciela w nagłówku Authorization żądania HTTP.

Poniższy przykład pokazuje, jak skonfigurować bezpieczne żądanie sieciowe, aby pobrać zrzut:

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

Ustawienia baterii

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

Ustawianie preferencji dotyczących wykorzystania baterii

Ustawienie balansu energii pozwala skonfigurować kompromis między czasem pracy na baterii a wydajnością urządzenia. Możesz utworzyć różne profile baterii, takie jak „Wydłużony”, „Zrównoważony” i „Wydajność”, i przełączać się między nimi.

Ta funkcja jest implementowana przez aktualizację atrybutu currentEnergyBalance cechy EnergyPreference. Atrybut akceptuje indeks liczby całkowitej, który odpowiada konkretnemu profilowi zdefiniowanemu na liście energyBalances urządzenia (np. 0 dla EXTENDED, 1 dla BALANCED i 2 dla PERFORMANCE).

Wartość null dla currentEnergyBalance oznacza, że urządzenie używa profilu niestandardowego. To jest stan tylko do odczytu.

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

// 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 czułości, gdzie 0 zwykle reprezentuje Disabled, a 1 reprezentuje 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 sprawdzić bieżący stan ładowania urządzenia (ładowanie, pełne naładowanie lub brak ładowania), 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 sprawdzić bieżący poziom baterii, użyj atrybutu batChargeLevel cechy PowerSource. Poziom może być 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"
}

Pobieranie źródła zasilania

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

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

Sprawdzanie, czy bateria wymaga wymiany

Atrybut batReplaceability wskazuje, czy bateria jest niewymienna, wymienna przez użytkownika czy wymienna przez producenta.

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 może być wymieniana przez użytkownika, batReplacementDescription opisuje ją, podając informacje takie jak format, skład chemiczny i preferowany producent.

Ustawienia dźwięku

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

Włączanie i wyłączanie mikrofonu

Aby włączyć lub wyłączyć mikrofon urządzenia, zaktualizuj atrybut microphoneMuted cechy CameraAvStreamManagement, używając 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 recordingMicrophoneMuted cechy CameraAvStreamManagement 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) }
}

Regulowanie głośności głośnika

Aby dostosować głośność głośnika urządzenia, zaktualizuj atrybut speakerVolumeLevel cechy CameraAvStreamManagement, używając 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 strefy aktywności

   Wdrożone w przykładowej aplikacji na Androida   

Atrybut ZoneManagement udostępnia interfejs do zarządzania niestandardowymi obszarami zainteresowania (miejscami aktywności) na kamerach i dzwonkach. Te strefy służą do filtrowania wykrywania zdarzeń (takich jak ruch osoby lub pojazdu) w określonych obszarach w polu widzenia urządzenia.

Miejsca aktywności są konfigurowane przez użytkownika w aplikacji partnera, co pozwala mu na rysowanie stref nad określonymi obszarami w polu widzenia kamery. Te zdefiniowane przez użytkownika strefy są następnie przekształcane w struktury używane przez tę cechę. Więcej informacji o tym, jak działają miejsca aktywności, znajdziesz w artykule Konfigurowanie i używanie miejsc aktywności.

Miejsca aktywności są zwykle definiowane za pomocą współrzędnych kartezjańskich 2D. Atrybut udostępnia TwoDCartesianVertexStruct dla wierzchołków i TwoDCartesianZoneStruct dla definicji strefy (nazwa, wierzchołki, kolor i użycie).

Sprawdzanie miejsc aktywności

Aby wyświetlić miejsca 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 parametr TwoDCartesianZoneStruct, który określa nazwę, wierzchołki, kolor i zastosowanie strefy.

Poniższy przykład pokazuje, jak utworzyć strefę o nazwie „Ganek” 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 miejsc aktywności

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

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

Aktywatory wykrycia dźwięku

Cecha AvStreamAnalysis udostępnia interfejs do zarządzania aktywatorami wykrywania zdarzeń na kamerach i wideodomofonach. Wyzwalacze oparte na obrazie (np. osoby lub pojazdy) mogą być przypisane do konkretnej strefy, natomiast wyzwalacze związane z dźwiękiem są zwykle konfigurowane na poziomie urządzenia.

W przypadku wykrywania dźwięku za pomocą EventTriggerTypeEnum dostępne są te typy aktywatorów:

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 odgłosy psów.
Odgłos tłuczonego szkła GlassBreak Wykrywa odgłos tłuczonego szkła.
Alarm z czujnika dymu SmokeAlarm Wykrywa alarmy z czujnika dymu, często rozpoznawane po wzorcu 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 tlenku węgla (CO), zwykle rozpoznawane po wzorcu 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. Te 2 atrybuty to:

Podczas tworzenia aplikacji na Androida za pomocą Kotlin Flows zwykle obserwujesz cechę AvStreamAnalysis z HomeDevice.

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

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

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

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

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

Zaktualizuj zestaw włączonych reguł.

Aby zaktualizować zestaw włączonych aktywatorów, 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żone w przykładowej aplikacji na Androida   

Atrybut RecordingMode udostępnia interfejs do zarządzania nagrywaniem filmów i robieniem zdjęć na kamerach i dzwonkach. Umożliwia użytkownikom wybór między nagrywaniem ciągłym, nagrywaniem zdarzeń lub całkowitym wyłączeniem nagrywania (tylko podgląd na żywo).

RecordingModeEnum definiuje 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 (nagrywanie ciągłe) Cvr Filmy są nagrywane przez całą dobę. Wymaga subskrypcji (np. Google Home Premium.
Nagrywanie zdarzeń Ebr Nagrywanie jest aktywowane przez zdarzenia (osoba, ruch). Długość wideo zależy od czasu trwania zdarzenia i subskrypcji.
ETR (Event Triggered Recording) Etr Krótkie nagranie podglądu (np. 10 sekund) wywoływane przez zdarzenia.
Live View LiveView Nagrywanie jest wyłączone, ale użytkownicy nadal mogą uzyskać dostęp do transmisji na żywo.
Grafika Images W przypadku wystąpienia zdarzeń zamiast filmów są rejestrowane zdjęcia.

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 indeks wybrany z atrybutu supportedRecordingModes jest obecny 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 kontrolować różne inne ustawienia.

Zmienianie orientacji obrazu

Orientację obrazu z kamery (filmu) można obrócić. Film można obrócić tylko o 180 stopni.

Aby zmienić orientację obrazu z kamery, zaktualizuj atrybut imageRotation cechy CameraAvStreamManagement, używając wbudowanej funkcji setImageRotation w języku Kotlin:

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

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

Włączanie i wyłączanie trybu nocnego

Aby włączyć lub wyłączyć tryb nocny 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)
}

Zmienianie jasności diody LED stanu

Aby zmienić jasność diody LED stanu, użyj ThreeLevelAutoEnum i zaktualizuj atrybut statusLightBrightness cechy CameraAvStreamManagement za pomocą wbudowanej funkcji setStatusLightBrightness w języku 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 widoku z kamery

Okno kamery jest takie samo jak w przypadku funkcji powiększania i przycinania opisanej w artykule pomocy Powiększanie i ulepszanie nagrań z kamery Nest.

Okno jest zdefiniowane w elemencie ViewportStruct, który zawiera 4 wartości używane jako współrzędne okna. Współrzędne są zdefiniowane w ten sposób:

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

Określenie wartości ViewportStruct zależy od interfejsu aplikacji i implementacji aparatu. Aby ustawić widoczny obszar obrazu z kamery, zaktualizuj atrybut viewport cechy CameraAvStreamManagement za pomocą ViewportStruct, używając wbudowanej funkcji setViewport w języku Kotlin:

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 Cloud Monitoring dla interfejsów API Home).

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

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

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

Konfiguracje transportu i nagrywania

Ta sekcja zawiera ustawienia związane z jakością strumienia z kamery i wyzwalaniem zdarzeń. Tymi ustawieniami zarządza cecha PushAvStreamTransport.

Odczytywanie ustawień transportu

W tej sekcji dowiesz się, jak pobrać bieżącą konfigurację z kamery lub dzwonka. Pobiera cechę PushAvStreamTransport, znajduje konkretne połączenie używane do nagrywania, a następnie wyodrębnia bieżące wartości jakości przepustowości, 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 obiekt TransportOptionsStruct zawierający nowe wartości, a następnie używa polecenia modifyPushTransport, aby odesłać te zaktualizowane ustawienia do urządzenia i zastosować je do połączenia nagrywania znalezionego w poprzednim kroku.

Aby zmodyfikować te ustawienia, użyj polecenia modifyPushTransport z 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, zapoznaj się z atrybutem allocatedVideoStreams, który jest listą VideoStreamStructs. z cechy CameraAvStreamManagement urządzenia.

Dostosowywanie czułości wybudzania urządzenia

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

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

Dostosowywanie maksymalnej długości wydarzenia

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

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

Wycofywanie urządzenia

Aby usunąć kamerę z budynku, wywołaj metodę device.decommissionDevice(). Wycofanie powoduje wyświetlenie użytkownikowi zarządzanego przez Google ekranu potwierdzenia z ostrzeżeniem, że cała historia całodobowych nagrań wideo, historia zdarzeń, klipy i dane z biblioteki znajomych twarzy zostaną trwale usunięte z całej struktury.

Szczegółowe instrukcje i przykłady kodu znajdziesz w artykule Usuwanie urządzenia.