دليل أجهزة الكاميرا على 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   

لبدء بث مباشر، أرسِل سلسلة بروتوكول وصف الجلسة (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)

تمديد بث مباشر

   التنفيذ في نموذج التطبيق لنظام التشغيل 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   

لبدء ميزة 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
    }
}

استرداد لقطات الكاميرا

   التنفيذ في نموذج التطبيق لنظام التشغيل 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 من أحدث جزء من الفيديو) أو لقطة يتم تحميلها بشكل دوري بواسطة الكاميرا.

للإشارة إلى حداثة الصورة، تدمج سحابة 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}")
}

مصادقة طلبات اللقطات

   التنفيذ في نموذج التطبيق لنظام التشغيل Android   

تشير جميع عناوين URL للّقطات إلى وسائط آمنة وتتطلّب رمز OAuth 2.0 المميّز لنطاق الإصدار 2 من Home APIs. لتنزيل الصور وعرضها، يجب إدخال رمز OAuth المميّز كرمز حامل مميّز في العنوان 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()
    }
  }

إعدادات البطارية

يمكن التحكّم في إعدادات البطارية المختلفة من خلال Home APIs.

ضبط الإعدادات المفضّلة لاستخدام البطارية

يتيح لك ضبط توازن الطاقة إعداد المفاضلة بين عمر البطارية والأداء للجهاز. يمكنك إنشاء ملفات تعريف مختلفة للبطارية، مثل "ممتد" و"متوازن" و "الأداء"، والتبديل بينها.

يتم تنفيذ هذه الميزة من خلال تعديل السمة 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 البطارية، بما في ذلك معلومات مثل شكلها وتركيبتها الكيميائية والشركة المصنّعة المفضّلة.

إعدادات الصوت

يمكن التحكّم في إعدادات الصوت المختلفة من خلال Home APIs.

تفعيل الميكروفون أو إيقافه

لتشغيل ميكروفون الجهاز أو إيقافه، عدِّل السمة 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 باستخدام دالة Kotlin المضمّنة setRecordingMicrophoneMuted:

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

إعدادات "منطقة النشاط"

   التنفيذ في نموذج التطبيق لنظام التشغيل 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، الذي يحدّد اسم المنطقة ورؤوسها ولونها واستخدامها.

يوضّح المثال التالي كيفية إنشاء منطقة باسم "الشرفة الأمامية" بأربعة رؤوس، بلون السلمون (#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:

الوضع قيمة التعداد الوصف
الصوت 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 واجهة لإدارة سلوك تسجيل الفيديو والصور على أجهزة الكاميرا وجرس الباب. تتيح هذه الميزة للمستخدمين الاختيار بين التسجيل المتواصل أو التسجيل فقط عند رصد حدث أو إيقاف التسجيل بالكامل (المشاهدة المباشرة فقط).

تحدّد السمة RecordingModeEnum استراتيجيات التسجيل المتاحة:

الوضع قيمة التعداد الوصف
غير مفعَّلة Disabled تم إيقاف التسجيل بالكامل. يُستخدم بشكل أساسي في الأجهزة القديمة.
التسجيل المتواصل Cvr يتم تسجيل الفيديو على مدار الساعة طيلة أيام الأسبوع. يتطلّب اشتراكًا (على سبيل المثال، Google Home Premium.
EBR (التسجيل فقط عند رصد حدث) 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())
}

إعدادات أخرى

يمكن التحكّم في إعدادات أخرى مختلفة من خلال Home APIs.

تغيير اتجاه الصورة

يمكن تدوير اتجاه صورة الكاميرا (الفيديو). يمكن تدوير الفيديو بمقدار 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)
}

تغيير سطوع مؤشر LED الخاص بالحالة

لتغيير مستوى سطوع مؤشر LED الخاص بالحالة، استخدِم 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 for Home APIs).

لتفعيل الإحصاءات لجهاز معيّن، اضبط السمة analyticsEnabled في ExtendedGeneralDiagnosticsTrait على true. عند ضبط analyticsEnabled، يتم ضبط سمة أخرى، وهي logUploadEnabled، تلقائيًا على true، ما يسمح بتحميل ملفات سجلّ الإحصاءات إلى Google Home Cloud.

// 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. من السمة CameraAvStreamManagement للجهاز.

ضبط حساسية أجهزة الاستشعار لتشغيل الكاميرا

تتوافق السمة motionSensitivity في TransportTriggerOptionsStruct مع القيم التالية:

التصنيف القيمة (UByte)
منخفض 1u
متوسط 5u
مرتفع 10u

تعديل الحد الأقصى لمدة الحدث

تتوافق السمة maxDuration في TransportMotionTriggerTimeControlStruct مع المدد التالية (بالثواني):

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

إيقاف تشغيل جهاز نهائيًا

لإزالة كاميرا من مبنى، يمكنك الاتصال بالرقم device.decommissionDevice(). يؤدي إيقاف التشغيل إلى ظهور شاشة تأكيد تديرها Google تحذّر المستخدم من أنّه سيتم حذف جميع بيانات سجلّ الفيديوهات على مدار الساعة وسجلّ الأحداث والمقاطع ومكتبة الوجوه المألوفة نهائيًا في جميع أنحاء البنية.

للحصول على تعليمات مفصّلة وأمثلة على الرموز، يُرجى الاطّلاع على إزالة جهاز.