מדריך למצלמה במכשירי Android

סוג המכשיר "מצלמה" מיושם באמצעות שלוש תכונות: PushAvStreamTransport, שמטפלת בהעברת שידורי אודיו ווידאו באמצעות פרוטוקולים מבוססי-דחיפה, WebRtcLiveView, שמספקת את היכולת לשלוט בשידורים חיים וב-TalkBack, ו- CameraSnapshot, שמספקת תמונות ממוזערות של תצוגה מקדימה שנשמרו במטמון וצילומי תמונות בשידור חי.

לפני שמשתמשים בתכונות או מנסים לעדכן מאפיינים, תמיד צריך לבדוק אם המכשיר תומך במאפיינים ובפקודות. מידע נוסף זמין במאמר שליטה במכשירים ב-Android.

סוג המכשיר ב-Home APIs תכונות אפליקציה לדוגמה ב-Kotlin תרחיש לדוגמה

מצלמה

GoogleCameraDevice

home.matter.6006.types.0158

מכשיר שמצלם תמונות סטילס או סרטונים. המצלמות יכולות לכלול שידורים חיים נגישים, שיחה משני צידי הדלת או אירועי זיהוי.

תכונות נדרשות
     google PushAvStreamTransport
     google WebRtcLiveView

מצלמה

לקבל מידע בסיסי על מכשיר

   הטמעה באפליקציה לדוגמה ל-Android   

המאפיין BasicInformation כולל מידע כמו שם הספק, מזהה הספק, מזהה המוצר, שם המוצר (כולל מידע על הדגם) וגרסת התוכנה של המכשיר:

// Get device basic information. All general information traits are on the RootNodeDevice type.
    device.type(RootNodeDevice).first().standardTraits.basicInformation?.let { basicInformation ->
        println("vendorName ${basicInformation.vendorName}")
        println("vendorId ${basicInformation.vendorId}")
        println("productId ${basicInformation.productId}")
        println("productName ${basicInformation.productName}")
        println("softwareVersion ${basicInformation.softwareVersion}")
    }

קבלת מספר סידורי

כדי לקבל את המספר הסידורי של המכשיר, משתמשים בפקודה GetSerialNumber של המאפיין ExtendedBasicInformation. בדוגמה הבאה מוצגת שמירה של המספר הסידורי במשתנה בשם serialNumber:

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

קבלת הזמן האחרון שבו המכשיר יצר קשר עם הענן

כדי למצוא את הפעם האחרונה שבה המכשיר היה בקשר עם הענן, משתמשים במאפיין lastContactTimestamp של התכונה ExtendedGeneralDiagnostics:

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

הגדרות של סוג התושבת של המצלמה

התכונה Mount מכילה את הגדרות התושבת של המצלמה ומידע על הסטטוס. אפשר לקרוא מאפיינים כמו מצב ההרכבה, סוג הזיהוי ושם סוג ההרכבה. בנוסף, אפשר להשתמש במאפיין Mount כדי לשנות את הגדרת ברירת המחדל של סוג הטעינה.

// Get the Mount trait
val mountTrait: Mount = device.getTrait(Mount)

// Read the current mount state and detection type
val mountState = mountTrait.mountState
val mountDetectionType = mountTrait.mountDetectionType

// Read the current mount type name
val mountTypeName = mountTrait.mountTypeName

// Update the mount type override
mountTrait.update {
  setMountTypeOverride(MountTrait.MountTypeOverrideEnum.Official)
}

בדיקת החיבור של המכשיר

הקישוריות של מכשיר נבדקת בפועל ברמת סוג המכשיר, כי חלק מהמכשירים תומכים בכמה סוגי מכשירים. המצב שמוחזר הוא שילוב של מצבי הקישוריות של כל התכונות במכשיר הזה.

val cameraConnectivity = cameraDevice.metadata.sourceConnectivity.connectivityState

יכול להיות שיופיע הסטטוס PARTIALLY_ONLINE במקרה של סוגי מכשירים מעורבים כשאין חיבור לאינטרנט. יכול להיות שתכונות רגילות של Matter עדיין יהיו זמינות אונליין בגלל ניתוב מקומי, אבל תכונות מבוססות-ענן יהיו אופליין.

איך מוצאים את כתובת ה-IP של המכשיר

כדי למצוא את כתובת ה-IP של המכשיר, משתמשים במאפיין networkInterfaces של התכונה GeneralDiagnostics. הכתובות מוחזרות כמערכי בייטים, שאפשר להמיר למחרוזות IPv4 או IPv6 רגילות:

val ipAddresses =
  trait.networkInterfaces?.flatMap { networkInterface ->
    (networkInterface.ipv4Addresses + networkInterface.ipv6Addresses).mapNotNull { bytes ->
      try {
        java.net.InetAddress.getByAddress(bytes).hostAddress
      } catch (e: java.net.UnknownHostException) {
        null
      }
    }
  } ?: emptyList()

התחלת שידור חי

   הטמעה באפליקציה לדוגמה ל-Android   

כדי להתחיל שידור חי, שולחים את מחרוזת ה-Session Description Protocol‏ (SDP) ל-method‏ startLiveView() של המאפיין WebRtcLiveView. ה-method מחזיר WebRtcLiveViewTrait.StartLiveViewCommand.Response שמכיל שלושה ערכים:

  • ה-SDP של הסשן.
  • משך הסשן בשניות.
  • מזהה הסשן, שבו אפשר להשתמש כדי להאריך או לסיים את הסשן.
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)

הארכת שידור חי

   הטמעה באפליקציה לדוגמה ל-Android   

לשידורים חיים יש משך זמן מוגדר מראש, ואחרי שהוא מסתיים, השידור נגמר. כדי להאריך את משך הזמן של שידור פעיל, שולחים בקשת הארכה באמצעות השיטה WebRtcLiveView.extendLiveView():

// Assuming camera stream has just been started
suspend fun scheduleExtension(trait: WebRtcLiveView, mediaSessionId: String, liveSessionDurationSeconds: UShort ) {
  delay(liveSessionDurationSeconds - BUFFER_SECONDS * 1000)
  val response = trait.extendLiveView(mediaSessionId)
  // returns how long the session will be live for
  return response.liveSessionDurationSeconds
}

הפעלה והפסקה של TalkBack

   הטמעה באפליקציה לדוגמה ל-Android   

כדי להתחיל את הדיבור, מפעילים את ה-method‏ startTalkback() של המאפיין WebRtcLiveView. כדי להפסיק את ההפעלה, משתמשים בפקודה stopTalkback().

// Make sure camera stream is on
suspend fun setTalkback(isOn: Boolean, trait: WebRtcLiveView, mediaSessionId: String) {
  if(isOn) {
    trait.startTalkback(mediaSessionId)
  } else {
    trait.stopTalkback(mediaSessionId)
  }
}

ניהול השידור החי

כדאי לשנות את איכות השידור החי כדי לבצע אופטימיזציה של השימוש ברוחב הפס בהתאם להקשר הצפייה של הלקוח (לדוגמה, מעבר לרזולוציה נמוכה יותר כשמציגים משבצת קטנה יותר של תצוגה מקדימה, תצוגת משבצות או מצב תמונה בתוך תמונה).

שינוי האיכות באופן דינמי באמצעות המאפיין WebRtcLiveView מנהל באופן ספציפי את הרזולוציה של סשן השידור החי הפעיל בלקוח מסוים. היא לא זהה להגדרה של רוחב הפס במכשיר עצמו, שמשפיעה על כל הצופים בו-זמנית ועל האיכות של הקלטות הווידאו ההיסטוריות שנשמרו בענן.

בדוגמה הבאה אפשר לראות איך לאחזר ולעדכן את איכות השידור החי במכשיר:

  • אחזור אפשרויות האיכות הנתמכות: קבלת רזולוציות הסטרימינג הזמינות שנתמכות במכשיר. הקוד שולח שאילתה למאפיין supportedQualityHints של התכונה WebRtcLiveView בתהליך של סוג המכשיר, וחושף את האיכויות הנתמכות בתור StateFlow שמכיל רשימה של ערכי QualityHint (כמו QUALITY_HINT_SD, QUALITY_HINT_HD, QUALITY_HINT_FHD, QUALITY_HINT_QHD או QUALITY_HINT_UHD).

  • שינוי האיכות של השידור החי: אפשר להחיל QualityHint נבחר כדי לשנות את רזולוציית הסטרימינג של השידור החי הפעיל (לדוגמה, מעבר מאיכות רגילה לאיכות גבוהה). הפונקציה changeQuality פותרת את תכונת הסטרימינג בשידור חי של המכשיר וקוראת ל-changeLiveViewQuality עם mediaSessionId הפעיל וההגדרה שנבחרה של QualityHint.

// Assuming you have a HomeDevice instance 'device'
val availableQualityHints: StateFlow<List> =
    device.type(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
    }
}

הפעלה והשבתה של יכולת ההקלטה

כדי להפעיל את יכולת ההקלטה של המצלמה, מעבירים את הערך TransportStatusEnum.Active ל-method‏ setTransportStatus() של המאפיין PushAvStreamTransport. כדי להשבית את יכולת ההקלטה, מעבירים את הערך TransportStatusEnum.Inactive. בדוגמה הבאה, אנחנו עוטפים את הקריאות האלה בקריאה אחת שמשתמשת ב-Boolean כדי להחליף את מצב יכולת ההקלטה:

// Start or stop recording for all connections.
suspend fun setCameraRecording(trait: PushAvStreamTransport, isOn: Boolean) {
  if(isOn) {
    trait.setTransportStatus(TransportStatusEnum.Active)
  } else {
    trait.setTransportStatus(TransportStatusEnum.Inactive)
  }
}

הפעלה או השבתה של יכולת ההקלטה של המצלמה זהה להפעלה או לכיבוי של הווידאו במצלמה. כשהווידאו במצלמה מופעל, המצלמה מקליטה (למטרות של אירועים וקליפים קשורים).

כשיכולת ההקלטה מושבתת (הווידאו של המצלמה כבוי):

  • המצלמה עדיין יכולה להופיע כפעילה בהתאם לconnectivityState של סוג המכשיר.
  • אי אפשר לגשת לשידור החי, והמצלמה לא מזהה אירועים בענן.

איך בודקים אם אפשר להקליט את הפגישה

כדי לבדוק אם יכולת ההקלטה של המצלמה מופעלת, בודקים אם יש חיבורים פעילים. בדוגמה הבאה מוגדרות שתי פונקציות שמבצעות את הפעולה הזו:

// Get the on/off state
suspend fun onOffState(pushAvStreamTransport: PushAvStreamTransport) {
  return pushAvStreamTransport
    .currentConnections?.any { it.transportStatus == TransportStatusEnum.Active } ?: false
}

// Check if the camera's recording capability is enabled
fun PushAvStreamTransport.recordModeActive(): Boolean {
  return currentConnections?.any { it.transportStatus == TransportStatusEnum.Active } ?: false
}

דרך נוספת לבדוק היא להשתמש בפונקציה findTransport() עם פרדיקט:

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

אחזור תמונות מצב מהמצלמה

   הטמעה באפליקציה לדוגמה ל-Android   

אפשר להשתמש בתכונה CameraSnapshot כדי לאחזר תמונות סטטיות ממצלמה. תמונות המצב האלה שימושיות בכמה תרחישים:

  • כמשבצת זמנית בממשק המשתמש בזמן שהשידור החי ב-WebRTC נטען.
  • כתמונה סטטית של תצוגה מקדימה שמשתדרגת לשידור חי כשהמשתמש לוחץ עליה.
  • כקנבס רקע לציור ולהגדרה של אזורי פעילות בהתאמה אישית.

התכונה CameraSnapshot מציעה שתי דרכים לאחזור תמונות מצב, בהתאם לצורך שלכם בתמונת מצב שנשמרה במטמון לעיבוד מהיר או בתמונת מצב בזמן אמת על פי דרישה.

קבלת תמונת מצב של תצוגה מקדימה שנשמרה במטמון

תמונת מצב של תצוגה מקדימה היא תמונה שנשמרה במטמון, וערכת ה-SDK יכולה לאחזר אותה בלי להפעיל את המצלמה. זו השיטה המהירה ביותר לאחזר תמונה סטטית, והיא אידיאלית לדפי סקירה כללית של מצלמות או לרשתות של לוחות בקרה.

כדי לקבל את תמונת המצב של התצוגה המקדימה, קוראים את מאפיין התכונה preview_image_url:

// Get a flow of the CameraSnapshot trait
val cameraSnapshotFlow: Flow<CameraSnapshot?> = cameraDevice
  .type(GoogleCameraDevice)
  .trait(CameraSnapshot)

// Get the current trait state
val cameraSnapshot = cameraSnapshotFlow.firstOrNull()
if (cameraSnapshot != null) {
  val previewImageUrl = cameraSnapshot.previewImageUrl
  // Load the preview image from the URL
} else {
  // CameraSnapshot trait not supported on this device
}

בהתאם להגדרות המצלמה ולפעילות, כתובת ה-URL של התצוגה המקדימה מפנה לתמונה שנשמרה במטמון ונוצרה מקטע וידאו אחרון (ה-I-frame הראשון של קטע הווידאו האחרון) או לתמונת מצב שהמצלמה מעלה מדי פעם.

כדי לציין את עדכניות התמונה, הענן של Google Home מטמיע כותרת X-Home-Image-Captured-At בתמונת ה-JPEG או ה-PNG שמוצגת, בפורמט UTC. אפשר לנתח את הכותרת הזו בצד הלקוח כדי להציג את גיל קובץ ה-snapshot באפליקציה.

איך מקבלים תמונת מצב בזמן אמת על פי דרישה

בתהליכים שבהם המשתמשים צריכים את התצוגה החיה הנוכחית של המצלמה, כמו הגדרת אזורי פעילות, אתם יכולים לבקש תמונת מצב בזמן אמת. הפעלת תמונת מצב בשידור חי גורמת למצלמה להתעורר, לצלם פריים חדש ולהעלות אותו לענן.

כדי לבקש תמונת מצב בזמן אמת, משתמשים בפקודה getLiveSnapshot():

try {
  val response = cameraSnapshot.getLiveSnapshot()
  val snapshotUrl = response.snapshotUrl
  // Load the live snapshot image from the URL
} catch (e: Exception) {
  Log.e("CameraSnapshot", "Failed to get live snapshot: ${e.message}")
}

אימות בקשות של תמונת מצב

   הטמעה באפליקציה לדוגמה ל-Android   

כל כתובות ה-URL של תמונות המצב מפנות למדיה מאובטחת ודורשות אסימון OAuth 2.0 להיקף של Home APIs v2. כדי להוריד ולעבד את התמונות, צריך להוסיף את טוקן ה-OAuth כטוקן למוכ"ז בכותרת Authorization של בקשת ה-HTTP.

בדוגמה הבאה אפשר לראות איך מגדירים בקשה מאובטחת לאחזור מהרשת כדי להוריד קובץ Snapshot:

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

הגדרות הסוללה

אפשר לשלוט בהגדרות שונות של הסוללה באמצעות ממשקי ה-API של Home.

הגדרת ההעדפה לשימוש בסוללה

הגדרת מאזן האנרגיה מאפשרת לכם לקבוע את האיזון בין חיי הסוללה לבין הביצועים של המכשיר. אתם יכולים ליצור פרופילים שונים של סוללה, כמו "מורחב", "מאוזן" ו "ביצועים", ולעבור ביניהם.

כדי להטמיע את התכונה הזו, מעדכנים את המאפיין currentEnergyBalance של התכונה EnergyPreference. המאפיין מקבל אינדקס של מספר שלם שתואם לפרופיל ספציפי שמוגדר ברשימת energyBalances של המכשיר (לדוגמה, 0 עבור EXTENDED, 1 עבור BALANCED ו-2 עבור PERFORMANCE).

הערך null במאפיין currentEnergyBalance מציין שהמכשיר משתמש בפרופיל בהתאמה אישית. זהו מצב קריאה בלבד.

בדוגמה הבאה מוצג מבנה שבו המאפיין currentEnergyBalance ישתמש, ואחריו קטע הקוד בפועל שמשתמש במאפיין.

// Example energyBalances list
energy_balances: [
  {
    step: 0,
    label: "EXTENDED"
  },
  {
    step: 50,
    label: "BALANCED"
  },
  {
    step: 100,
    label: "PERFORMANCE"
  }
]
// The index parameter must be within the UByte range (0-255).
suspend fun setEnergyBalance(trait: EnergyPreference, index: Int) {
  trait.update { setCurrentEnergyBalance(index.toUByte()) }
}

// Setting the battery usage to more recording ie performance
setEnergyBalance(energyPreference, 2)

הפעלה אוטומטית של מצב החיסכון בסוללה

כדי להגדיר את התכונה הזו, מעדכנים את המאפיין currentLowPowerModeSensitivity של התכונה EnergyPreference. המאפיין הזה משתמש באינדקס כדי לבחור רמת רגישות, כאשר 0 בדרך כלל מייצג את Disabled ו-1 מייצג את Enabled או Automatic.

suspend fun setAutomaticBatterySaver(enable: Boolean, trait: EnergyPreference) {
  // 0 is Disabled, 1 is Enabled
  val value = if (enable) 1.toUByte() else 0.toUByte()
  trait.update { setCurrentLowPowerModeSensitivity(value) }
}

קבלת מצב הטעינה של הסוללה

כדי לקבל את מצב הטעינה הנוכחי של המכשיר (בטעינה, טעון במלואו או לא בטעינה), משתמשים במאפיין batChargeState של התכונה PowerSource.

// Get the battery charging state
val batteryChargeState = powerSource.batChargeState

when (batteryChargeState) {
    PowerSourceTrait.BatChargeStateEnum.IsCharging -> "Charging"
    PowerSourceTrait.BatChargeStateEnum.IsAtFullCharge -> "Full"
    PowerSourceTrait.BatChargeStateEnum.IsNotCharging -> "Not Charging"
    else -> "Unknown"
}

איך בודקים את רמת הטעינה

כדי לקבל את רמת הטעינה הנוכחית של הסוללה, משתמשים במאפיין batChargeLevel של התכונה PowerSource. הרמה היא OK, Warning (נמוכה) או Critical.

// Get the battery charge level
val batteryLevel = powerSourceTrait.batChargeLevel

when (batteryLevel) {
    PowerSourceTrait.BatChargeLevelEnum.OK -> "OK"
    PowerSourceTrait.BatChargeLevelEnum.Warning -> "Warning"
    PowerSourceTrait.BatChargeLevelEnum.Critical -> "Critical"
    else -> "Unknown"
}

משיגים את מקור המתח

כדי לקבוע באיזה מקור מתח המכשיר משתמש, צריך להשתמש במאפיינים BatPresent ו-wiredPresent של התכונה PowerSource.

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

בודקים אם צריך להחליף את הסוללה

המאפיין batReplaceability מציין אם הסוללה לא ניתנת להחלפה, ניתנת להחלפה על ידי המשתמש או ניתנת להחלפה במפעל.

המאפיין batReplacementNeeded של התכונה PowerSource מציין אם צריך להחליף את הסוללה.

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

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

כשהמשתמש יכול להחליף את הסוללה, batReplacementDescription מתאר את הסוללה, כולל מידע כמו גורם הצורה, הכימיה והיצרן המועדף.

הגדרות אודיו

אפשר לשלוט בהגדרות אודיו שונות באמצעות ממשקי ה-API של Home.

הפעלה או השבתה של המיקרופון

כדי להפעיל או להשבית את המיקרופון של המכשיר, מעדכנים את המאפיין microphoneMuted של התכונה CameraAvStreamManagement באמצעות הפונקציה המובנית setMicrophoneMuted ב-Kotlin:

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

איך מפעילים או משביתים את הקלטת האודיו

כדי להפעיל או להשבית את הקלטת האודיו במכשיר, מעדכנים את המאפיין recordingMicrophoneMuted של התכונה CameraAvStreamManagement באמצעות הפונקציה המובנית setRecordingMicrophoneMuted ב-Kotlin:

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

שינוי עוצמת הקול של הרמקול

כדי לשנות את עוצמת הקול של הרמקול במכשיר, מעדכנים את המאפיין speakerVolumeLevel של התכונה CameraAvStreamManagement באמצעות הפונקציה המובנית setSpeakerVolumeLevel ב-Kotlin:

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

הגדרות של אזורי פעילות

   הטמעה באפליקציה לדוגמה ל-Android   

התכונה ZoneManagement מספקת ממשק לניהול אזורי עניין מותאמים אישית (אזורי פעילות) במצלמות ובפעמוני דלת. האזורים האלה משמשים לסינון זיהוי האירועים (כמו תנועה של אדם או רכב) לאזורים ספציפיים בשדה הראייה של המכשיר.

המשתמש מגדיר את אזורי הפעילות באפליקציה של השותף, וכך הוא יכול לשרטט אזורים מעל חלקים ספציפיים בשדה הראייה של המצלמה. האזורים האלה שמוגדרים על ידי המשתמש מתורגמים למבנים שבהם התכונה הזו משתמשת. מידע נוסף על אופן הפעולה של אזורי הפעילות זמין במאמר הגדרה ושימוש באזורי פעילות.

בדרך כלל מגדירים אזורי פעילות באמצעות קואורדינטות קרטזיות דו-ממדיות. המאפיין מספק את TwoDCartesianVertexStruct לקודקודים ואת TwoDCartesianZoneStruct להגדרת האזור (שם, קודקודים, צבע ושימוש).

בדיקת אזורי הפעילות

כדי להציג אזורי פעילות, בודקים את המאפיין zones של התכונה ZoneManagement.

// 1. Obtain the trait flow from the device
private val zoneManagementFlow: Flow =
  device.type(CAMERA_TYPE).flatMapLatest { it.trait(ZoneManagement) }

// 2. Map the flow to the list of zone structures
val activityZones: Flow<List<ZoneManagementTrait.ZoneInformationStruct>> =
  zoneManagementFlow.map { trait ->
    trait.zones ?: emptyList()
  }

הוספת אזור פעילות

כדי ליצור אזור חדש, משתמשים בפקודה createTwoDCartesianZone. הפקודה הזו מקבלת TwoDCartesianZoneStruct, שמגדיר את השם, הקודקודים, הצבע והשימוש של האזור.

בדוגמה הבאה מוסבר איך ליצור אזור בשם "Front Porch" עם ארבעה קודקודים, בצבע ורוד סלמון (‎#F439A0), שמשמש לזיהוי תנועה.

import com.google.home.google.ZoneManagement
import com.google.home.google.ZoneManagementTrait
import com.google.home.matter.serialization.OptionalValue

/**
 * Creates a custom activity zone named "Front Porch" with a salmon color
 * configured for motion detection.
 */
suspend fun createFrontPorchZone(zoneManagement: ZoneManagement) {
  // 1. Define the vertices for the zone (2D Cartesian coordinates)
  // Values are typically scaled to a maximum defined by the device's twoDCartesianMax attribute.
  val vertices =
    listOf(
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 260u, y = 422u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 1049u, y = 0u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 2048u, y = 0u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 2048u, y = 950u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 1630u, y = 1349u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 880u, y = 2048u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 0u, y = 2048u),
      ZoneManagementTrait.TwoDCartesianVertexStruct(x = 638u, y = 1090u)
    )

  // 2. Define the zone structure
  val newZone =
    ZoneManagementTrait.TwoDCartesianZoneStruct(
      name = "Front Porch",
      vertices = vertices,
      // Usage defines what the zone filters (for example, Motion, Person, Vehicle)
      use = listOf(ZoneManagementTrait.ZoneUseEnum.Motion),
      // Color is typically a hex string (for example, Salmon/Pink)
      color = OptionalValue.present("#F439A0")
    )

  try {
    // 3. Execute the command to add the zone to the device
    zoneManagement.createTwoDCartesianZone(newZone)
    println("Successfully created activity zone.")
  } catch (e: Exception) {
    // Handle potential HomeException or Timeout
    println("Failed to create activity zone: ${e.message}")
  }
}

עדכון של אזור פעילות

כדי לעדכן אזור קיים, משתמשים בפקודה updateTwoDCartesianZone. כדי להריץ את הפקודה הזו, צריך את zoneId ואת TwoDCartesianZoneStruct המעודכן.

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

מחיקת אזור פעילות

כדי להסיר אזור, משתמשים בפקודה removeZone עם הערך הספציפי של zoneId.

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

טריגרים לאירועים של זיהוי צליל

המאפיין AvStreamAnalysis מספק ממשק לניהול טריגרים לזיהוי אירועים במצלמות ובפעמוני דלת. טריגרים שמבוססים על ראייה (כמו אנשים או כלי רכב) יכולים להיות ספציפיים לאזור, אבל טריגרים שקשורים לצלילים הם בדרך כלל הגדרות ברמת המכשיר.

אלה סוגי הטריגרים שזמינים לזיהוי צלילים באמצעות EventTriggerTypeEnum:

מצב הערך של הטיפוס בן המנייה (enum) תיאור
צליל Sound זיהוי צלילים כללי.
מישהו מדבר PersonTalking מזהה דיבור.
נביחות של כלב DogBark מזהה נביחות של כלבים.
זכוכית נשברת GlassBreak מזהה צליל של זכוכית נשברת.
גלאי עשן SmokeAlarm זיהוי של גלאי עשן, בדרך כלל לפי דפוס הצליל T3 (שלושה צפצופים קצרים ואחריהם הפסקה).
גלאי פחמן חד-חמצני CoAlarm זיהוי אזעקות של פחמן חד-חמצני (CO), שבדרך כלל מזוהות לפי דפוס הצליל T4 (ארבעה צפצופים קצרים ואחריהם הפסקה).

איך בודקים את סטטוס זיהוי הצלילים

כדי להציג למשתמש את המצב הנוכחי של זיהוי הצלילים, צריך לבדוק במה המכשיר תומך ומה מופעל על ידי החומרה של המכשיר. שני המאפיינים שצריך לבדוק הם:

בפיתוח ל-Android באמצעות Kotlin Flows, בדרך כלל צופים במאפיין AvStreamAnalysis מ-HomeDevice.

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

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

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

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

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

עדכון קבוצת הטריגרים המופעלים

כדי לעדכן את קבוצת הטריגרים המופעלים, משתמשים בפקודה SetOrUpdateEventDetectionTriggers, שמקבלת רשימה של מבני EventTriggerEnablement.

private suspend fun AvStreamAnalysis.updateEventTriggers(
  eventTriggers: List<EventTriggerAttribute>
) {
  val toUpdate = eventTriggers.map {
    EventTriggerEnablement(
      eventTriggerType = it.type,
      enablementStatus = if (it.enabled) {
        EnablementStatusEnum.Enabled
      } else {
        EnablementStatusEnum.Disabled
      },
    )
  }

  // Execute the command on the device
  setOrUpdateEventDetectionTriggers(toUpdate)
}

מצבי הקלטה

   הטמעה באפליקציה לדוגמה ל-Android   

התכונה RecordingMode מספקת ממשק לניהול ההתנהגות של הקלטת וידאו ותמונות במצלמות ובפעמוני דלת. המשתמשים יכולים לבחור בין הקלטה רציפה, הקלטה בזמן אירוע (EBR) או השבתה של ההקלטה לחלוטין (צפייה בזמן אמת בלבד).

השדה RecordingModeEnum מגדיר את אסטרטגיות ההקלטה הזמינות:

מצב הערך של הטיפוס בן המנייה (enum) תיאור
מושבת Disabled ההקלטה מושבתת לגמרי. משמש בעיקר מכשירים מדור קודם.
‫CVR (הקלטת וידאו רציפה) Cvr הווידאו מוקלט 24 שעות ביממה, 7 ימים בשבוע. נדרש מינוי (לדוגמה, Google Home Premium.
EBR (הקלטה בזמן אירוע) Ebr ההקלטה מופעלת על ידי אירועים (אדם, תנועה). אורך הסרטון תלוי במשך האירוע ובמינוי.
ETR (הקלטה שמופעלת על ידי אירוע) Etr הקלטה של קטע מקדים קצר (לדוגמה, 10 שניות) שמופעלת על ידי אירועים.
Live View LiveView ההקלטה מושבתת, אבל המשתמשים עדיין יכולים לגשת לשידור החי.
תמונות סטילס Images כשמתרחשים אירועים, המצלמה מצלמת תמונות במקום סרטונים.

בדיקת מצבי ההקלטה

כדי להציג את הגדרות ההקלטה הנוכחיות, בודקים את המאפיינים של התכונה RecordingMode:

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

// 2. Map the flow to recording mode options
data class RecordingModeOptions(
    val recordingMode: RecordingModeTrait.RecordingModeEnum,
    val index: Int,
    val available: Boolean,
    val readableString: String,
)

private val recordingModeOptions: Flow<List> =
    recordingModeTraitFlow.map { trait ->
        val supported = trait.supportedRecordingModes?.map { it.recordingMode } ?: emptyList()
        val available = trait.availableRecordingModes?.map { it.toInt() } ?: emptyList()

        supported.withIndex().map { (index, mode) ->
            RecordingModeOptions(
                recordingMode = mode,
                index = index,
                available = available.contains(index),
                readableString = mode.toReadableString(),
            )
        }
    }

שינוי מצב ההקלטה

לפני העדכון, צריך לוודא שהאינדקס שנבחר מהמאפיין supportedRecordingModes קיים במאפיין availableRecordingModes.

כדי לעדכן את המצב שנבחר, משתמשים בפונקציה setSelectedRecordingMode ומעבירים את האינדקס של המצב שנבחר:

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

הגדרות אחרות

אפשר לשלוט בהגדרות שונות אחרות באמצעות ממשקי ה-API של Home.

שינוי כיוון התמונה

אפשר לסובב את כיוון התמונה של המצלמה (וידאו). אפשר לסובב את הסרטון רק ב-180 מעלות.

כדי לשנות את כיוון התמונה של המצלמה, מעדכנים את המאפיין imageRotation של התכונה CameraAvStreamManagement באמצעות הפונקציה המובנית setImageRotation ב-Kotlin:

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

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

הפעלה או השבתה של ראיית לילה

כדי להפעיל או להשבית את ראיית הלילה במצלמה, משתמשים ב-TriStateAutoEnum כדי לעדכן את המאפיין nightVision של התכונה CameraAvStreamManagement באמצעות הפונקציה המובנית setNightVision ב-Kotlin:

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

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

שינוי הבהירות של נורית הסטטוס

כדי לשנות את הבהירות של נורית הסטטוס, משתמשים ב- ThreeLevelAutoEnum כדי לעדכן את המאפיין statusLightBrightness של התכונה CameraAvStreamManagement באמצעות הפונקציה המובנית 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)
}

שינוי אזור התצוגה של המצלמה

אזור התצוגה של המצלמה זהה לתכונה "הגדלה וחיתוך" שמתוארת במאמר התמיכה הגדלה ושיפור של סרטונים במצלמת Nest.

אזור התצוגה מוגדר ב-ViewportStruct שמכיל ארבעה ערכים, שמשמשים כקואורדינטות של אזור התצוגה. הקואורדינטות מוגדרות כך:

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

הערכים של ViewportStruct נקבעים לפי ממשק המשתמש של האפליקציה והטמעת המצלמה. ברמה הבסיסית ביותר, כדי להגדיר את אזור התצוגה של סרטון המצלמה, מעדכנים את המאפיין viewport של המאפיין CameraAvStreamManagement באמצעות ViewportStruct, בעזרת הפונקציה המובנית setViewport של 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(),
    )
) }

הפעלה או השבתה של ניתוח נתונים

כל מכשיר יכול להצטרף בנפרד לשליחת נתוני ניתוח מפורטים לענן של Google Home (ראו Cloud Monitoring לממשקי Home API).

כדי להפעיל ניתוח נתונים במכשיר, מגדירים את המאפיין analyticsEnabled של ExtendedGeneralDiagnosticsTrait לערך true. כשמגדירים את analyticsEnabled, מאפיין אחר, logUploadEnabled, מוגדר באופן אוטומטי ל-true, מה שמאפשר להעלות את קובצי היומן של ניתוח הנתונים לענן של Google Home.

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

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

הגדרות של העברה והקלטה

בקטע הזה מפורטות הגדרות שקשורות לאיכות הסטרימינג מהמצלמה ולהפעלת אירועים. ההגדרות האלה מנוהלות על ידי המאפיין PushAvStreamTransport.

קריאת הגדרות התחבורה

בקטע הזה מוסבר איך לאחזר את ההגדרות הנוכחיות ממצלמה או מפעמון דלת. הוא מאחזר את המאפיין PushAvStreamTransport, מוצא את החיבור הספציפי שמשמש להקלטה, ואז מחלץ את הערכים הנוכחיים של איכות רוחב הפס, הרגישות של יציאה ממצב שינה ואורך האירוע המקסימלי.

val trait: PushAvStreamTransport = device.getTrait(PushAvStreamTransport)
val connections = trait.findTransport().transportConfigurations

// Locate the connection designated for recording
val recordingConnection = connections.firstOrNull {
    it.transportOptions.getOrNull()?.streamUsage == StreamUsageEnum.Recording
}

val options = recordingConnection?.transportOptions?.getOrNull()

// 1. Bandwidth Quality (Video Stream ID)
val videoStreamId = options?.videoStreamId?.getOrNull()

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

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

עדכון הגדרות התחבורה

בקטע הזה מוסבר איך לשנות את הגדרות ההעברה. הוא יוצר TransportOptionsStruct חדש שמכיל את הערכים החדשים, ואז משתמש בפקודה modifyPushTransport כדי לשלוח את ההגדרות המעודכנות האלה בחזרה למכשיר, ולהחיל אותן על החיבור להקלטה שנמצא בשלב הקודם.

כדי לשנות את ההגדרות האלה, משתמשים בפקודה modifyPushTransport עם TransportOptionsStruct.

val toUpdate = TransportOptionsStruct(
    videoStreamId = OptionalValue.present(2u), // e.g., Max Quality
    triggerOptions = TransportTriggerOptionsStruct(
        motionSensitivity = OptionalValue.present(5u), // e.g., Medium
        motionTimeControl = OptionalValue.present(
            TransportMotionTriggerTimeControlStruct(maxDuration = 30u)
        )
    )
)

if (recordingConnection != null) {
    trait.modifyPushTransport(
        connectionId = recordingConnection.connectionId,
        transportOptions = toUpdate
    )
}

בדיקת איכות רוחב הפס

המאפיין videoStreamId של TransportOptionsStruct תואם להגדרות ספציפיות של שידור וידאו.

כדי לקבל את שידורי הווידאו הנתמכים, עיינו במאפיין allocatedVideoStreams, שהוא רשימה של VideoStreamStructs. מה-trait‏ CameraAvStreamManagement של המכשיר.

שינוי הרגישות של יציאה ממצב שינה במכשיר

המאפיין motionSensitivity של TransportTriggerOptionsStruct תואם לערכים הבאים:

תווית ערך (UByte)
נמוכה 1u
בינונית 5u
גבוהה 10u

שינוי משך הזמן המקסימלי של האירוע

המאפיין maxDuration של TransportMotionTriggerTimeControlStruct תואם למשכי הזמן הבאים (בשניות):

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

הוצאת מכשיר משימוש

כדי להסיר מצלמה ממבנה, צריך להפעיל את השיטה device.decommissionDevice(). כשמוציאים את המצלמה משימוש, מוצג למשתמש מסך אישור בניהול Google, שבו מופיעה אזהרה שכל הנתונים של היסטוריית הווידאו המלאה, היסטוריית האירועים, הקליפים וספריית הפנים המוכרות יימחקו באופן סופי בכל המבנה.

הוראות מפורטות ודוגמאות לקוד מופיעות במאמר הסרת מכשיר.