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

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

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

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

מצלמה

GoogleCameraDevice

home.matter.6006.types.0158

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

מאפיינים נדרשים
     google PushAvStreamTransport
     google WebRtcLiveView

מצלמה

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

   Implemented in Sample App for 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 lightConnectivity = dimmableLightDevice.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()

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

   Implemented in Sample App for Android   

כדי להתחיל שידור חי, שולחים את המחרוזת Session Description Protocol (SDP) אל השיטה startLiveView() של המאפיין WebRtcLiveView, שמחזירה את 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)

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

   Implemented in Sample App for 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

   Implemented in Sample App for Android   

כדי להפעיל את TalkBack, מתקשרים אל השיטה 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 לשיטה 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
    }
}

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

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

  • כאריח placeholder בממשק המשתמש בזמן הטעינה של שידור חי ב-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 של קטע הווידאו האחרון) או לתמונת מצב שמועלית על ידי המצלמה באופן תקופתי.

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

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

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

כדי לבקש תמונה עדכנית, משתמשים בפקודה 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}")
}
.

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

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

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

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 באמצעות פונקציית Kotlin המובנית setMicrophoneMuted:

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

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

   Implemented in Sample App for 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)
}

מצבי הקלטה

   Implemented in Sample App for Android   

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

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

מצב ערך enum תיאור
מושבת Disabled ההקלטה מושבתת לחלוטין. משמש בעיקר מכשירים מדור קודם.
CVR (הקלטת וידאו רציפה) Cvr הסרטון מוקלט מסביב לשעון. נדרש מינוי (לדוגמה, Google Home Premium.
EBR (Event Based Recording) Ebr ההקלטה מופעלת על ידי אירועים (אדם, תנועה). אורך הסרטון תלוי במשך האירוע ובמינוי.
ETR (הקלטה שמופעלת על ידי אירוע) Etr הקלטה של תצוגה מקדימה קצרה (לדוגמה, 10 שניות) מופעלת על ידי אירועים.
תצוגה חיה 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 באמצעות פונקציית Kotlin המובנית:setStatusLightBrightness

// 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 for Home APIs).

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

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

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

הגדרות של שינוע והקלטה

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

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

בקטע הזה נסביר איך לאחזר את ההגדרה הנוכחית ממצלמה או ממכשיר Nest Doorbell. הוא מאחזר את מאפיין 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. מהמאפיין CameraAvStreamManagement של המכשיר.

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

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

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

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

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

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