מדריך למכשיר פעמון לדלת ב-Android

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

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

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

פעמון דלת

GoogleDoorbellDevice

home.matter.6006.types.0113

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

Required Traits
     google PushAvStreamTransport
     google WebRtcLiveView

Doorbell

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

מאפיין 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}")
        println("serialNumber ${basicInformation.serialNumber}")
    }

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

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

val lightConnectivity = dimmableLightDevice.metadata.sourceConnectivity.connectivityState

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

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

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

  • נתוני ה-SDP של הסשן.
  • משך הסשן בשניות.
  • מזהה הסשן, שאפשר להשתמש בו כדי להאריך או לסיים את הסשן.
suspend fun getWebRtcLiveViewTrait(cameraDevice: HomeDevice) {
 return cameraDevice.type(GoogleDoorbellDevice).trait(WebRtcLiveView).first {
    it?.metadata?.sourceConnectivity?.connectivityState == ConnectivityState.ONLINE
  }

}

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

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

   ...

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

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

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

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

כדי להפעיל את 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)
  }
}

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

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

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

אפשר לשלוט בהגדרות שונות של הסוללה באמצעות ממשקי ה-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

הגדרות אודיו

אפשר לשלוט בהגדרות שונות של אודיו באמצעות ממשקי ה-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 באמצעות פונקציית Kotlin המובנית setSpeakerVolumeLevel:

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

הגדרות אחרות

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

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

כדי להפעיל או להשבית את ראיית הלילה במצלמה, משתמשים ב-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(),
    )
) }

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

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

בממשקי ה-API של Home, אפשר להגדיר את זה באמצעות המאפיין motionSensitivity של triggerOptions ב-transportOptions של המכשיר. האפשרויות האלה מוגדרות במאפיין PushAvStreamTransport של כל מכשיר.

אפשר להגדיר את רגישות ההפעלה רק לערכים הבאים:

  • ‫1 = נמוך
  • ‫5 = בינוני
  • ‫10 = גבוה

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

// Create a struct with the new wake-up sensitivity
val toUpdate =  TransportOptionsStruct(
  triggerOptions =
    TransportTriggerOptionsStruct(
      motionSensitivity =
        OptionalValue.present(wakeUpSensitivity.toUByte())
    )
  )

// Get the configurations for active connections
val connections  = pushAvStreamTransport.findTransport().transportConfigurations
  // Update all recording streams with the new transport options.
  for (connection in connections) {
    if (connection.transportOptions.getOrNull()?.streamUsage == StreamUsageEnum.Recording) {
      trait.modifyPushTransport(
        connectionId = connection.connectionId,
        transportOptions = toUpdate,
      )
    }
  }

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

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

  • ‫10 שניות
  • 15 שניות
  • ‫30 שניות
  • ‫60 שניות (דקה אחת)
  • ‫120 שניות (2 דקות)
  • ‫180 שניות (3 דקות)

בממשקי ה-API של Home, אפשר להגדיר את זה באמצעות המאפיין motionTimeControl של triggerOptions ב-transportOptions של המכשיר. האפשרויות האלה מוגדרות במאפיין PushAvStreamTransport של כל מכשיר.

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

// Create a struct with the new max event length
// where maxDuration is the length in seconds
val toUpdate =  TransportOptionsStruct(
  triggerOptions =
    TransportTriggerOptionsStruct(
      motionTimeControl =
        OptionalValue.present(
          TransportMotionTriggerTimeControlStruct(maxDuration = it.toUInt())
        )
    )
  )

// Get the configurations for active connections
val connections  = pushAvStreamTransport.findTransport().transportConfigurations
  // Update all recording streams with the new transport options.
  for (connection in connections) {
    if (connection.transportOptions.getOrNull()?.streamUsage == StreamUsageEnum.Recording) {
      trait.modifyPushTransport(
        connectionId = connection.connectionId,
        transportOptions = toUpdate,
      )
    }
  }

הגדרות של Chime

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

שינוי הצליל של מנגנון הצלצול

כדי לשנות את צליל הפעמון בדלת, קודם צריך לקבל את רשימת הצלילים של הפעמון שמותקנים במכשיר באמצעות מאפיין installedChimeSounds של תכונת Chime:

// Get a list of chimes and identify the currently selected one
private val doorbellChimeTraitFlow: Flow =
    device.traitFromType(Chime, GoogleDoorbellDevice)
val chimeSounds = doorbellChimeTraitFlow.first().installedChimeSounds ?: emptyList()

לאחר מכן, מעדכנים את מאפיין selectedChime של מאפיין Chime באמצעות הפונקציה המובנית setSelectedChime Kotlin:

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

שימוש בצליל חיצוני

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

כדי לציין איזה סוג של צלצול חיצוני מותקן, צריך להשתמש ב-ExternalChimeType כדי לעדכן את מאפיין externalChime של מאפיין התכונה Chime באמצעות פונקציית Kotlin המובנית:setExternalChime

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

שינוי משך הצלצול החיצוני

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

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

כדי לשנות את משך הצלצול החיצוני, מעדכנים את המאפיין externalChimeDurationSeconds של מאפיין Chime באמצעות הפונקציה המובנית setExternalChimeDurationSeconds Kotlin:

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

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

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

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

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

// Get themes from the ChimeThemes trait
fun List<ChimeThemesTrait.ThemeStruct>.filterTimeboxedThemes():
    List<ChimeThemesTrait.ThemeStruct> {
  val now = timeSource.instant().epochSecond.toULong()
  return filter { chimeStruct: ChimeThemesTrait.ThemeStruct ->
    val startTime: ULong = chimeStruct.startTimeSeconds.getOrNull() ?: 0UL
    val endTime: ULong = chimeStruct.endTimeSeconds.getOrNull() ?: MAX_VALUE
    startTime <= now && now <= endTime
  }
}

val availableThemes =
  doorbellChimeThemesTraitFlow
    .first()
    .getAvailableThemes()
    .themes
    .filterTimeboxedThemes()

אחרי שמקבלים את שם העיצוב הרצוי, כמו Christmas, אפשר לבחור אותו באמצעות הפונקציה setSelectedTimeboxedThemeName() במאפיין ChimeThemes:

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