نظرة عامة على Wi-Fi Aware

تتيح إمكانات Wi-Fi Aware للأجهزة التي تعمل بالإصدار 8.0 من نظام التشغيل Android (المستوى 26 من واجهة برمجة التطبيقات) والإصدارات الأحدث رصد الأجهزة الأخرى والاتصال بها مباشرةً بدون أي نوع آخر من الاتصال بينها. يُعرف Wi-Fi Aware أيضًا باسم اتصال مباشر بمحطات لاسلكية مجاورة (NAN).

تعمل شبكة Wi-Fi Aware من خلال إنشاء مجموعات مع الأجهزة المجاورة، أو من خلال إنشاء مجموعة جديدة إذا كان الجهاز هو الأول في منطقة معيّنة. ينطبق سلوك التجميع هذا على الجهاز بأكمله، وتديره خدمة نظام Wi-Fi Aware، ولا يمكن للتطبيقات التحكّم في سلوك التجميع. تستخدم التطبيقات واجهات برمجة تطبيقات Wi-Fi Aware للتواصل مع خدمة نظام Wi-Fi Aware التي تدير أجهزة Wi-Fi Aware على الجهاز.

تتيح واجهات برمجة التطبيقات Wi-Fi Aware للتطبيقات تنفيذ العمليات التالية:

  • اكتشاف الأجهزة الأخرى: تتضمّن واجهة برمجة التطبيقات آلية للعثور على الأجهزة الأخرى القريبة. تبدأ العملية عندما ينشر جهاز واحد خدمة واحدة أو أكثر يمكن اكتشافها. بعد ذلك، عندما يشترك جهاز في خدمة واحدة أو أكثر ويدخل ضمن نطاق شبكة Wi-Fi الخاصة بالناشر، يتلقّى المشترك إشعارًا بأنّه تم العثور على ناشر مطابق. بعد أن يعثر المشترك على الناشر، يمكنه إرسال رسالة قصيرة أو إنشاء اتصال شبكة مع الجهاز الذي تم العثور عليه. يمكن أن تكون الأجهزة ناشرة ومشتركة في الوقت نفسه.

  • إنشاء اتصال شبكة: بعد أن يعثر جهازان على بعضهما البعض، يمكنهما إنشاء اتصال شبكة ثنائي الاتجاه باستخدام Wi-Fi Aware بدون نقطة وصول.

تتيح اتصالات شبكة Wi-Fi Aware معدّلات نقل بيانات أعلى على مسافات أطول مقارنةً باتصالات البلوتوث. تكون أنواع الاتصالات هذه مفيدة للتطبيقات التي تشارك كميات كبيرة من البيانات بين المستخدمين، مثل تطبيقات مشاركة الصور.

تحسينات في الإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات)

على الأجهزة التي تعمل بالإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات) والإصدارات الأحدث التي تتوافق مع وضع الاتصال الفوري، يمكن للتطبيقات استخدام الطريقتَين PublishConfig.Builder.setInstantCommunicationModeEnabled() وSubscribeConfig.Builder.setInstantCommunicationModeEnabled() لتفعيل وضع الاتصال الفوري أو إيقافه لجلسة البحث عن الناشر أو المشترك. يؤدي وضع الاتصال الفوري إلى تسريع تبادل الرسائل واكتشاف الخدمات وإعداد أي مسار بيانات كجزء من جلسة اكتشاف الناشر أو المشترك. لتحديد ما إذا كان الجهاز يتيح وضع التواصل الفوري، استخدِم طريقة isInstantCommunicationModeSupported().

تحسينات في الإصدار 12 من نظام التشغيل Android (المستوى 31 لواجهة برمجة التطبيقات)

يضيف نظام التشغيل Android 12 (المستوى 31 لواجهة برمجة التطبيقات) بعض التحسينات على Wi-Fi Aware:

  • على الأجهزة التي تعمل بنظام التشغيل Android 12 (المستوى 31 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، يمكنك استخدام onServiceLost() للحصول على تنبيه عندما يفقد تطبيقك خدمة تم اكتشافها بسبب توقّف الخدمة أو خروجها عن النطاق.
  • تم تبسيط عملية إعداد مسارات بيانات Wi-Fi Aware. استخدمت الإصدارات السابقة ميزة المراسلة من الطبقة 2 لتوفير عنوان MAC الخاص بالمرسِل، ما أدى إلى حدوث تأخير. على الأجهزة التي تعمل بالإصدار 12 من نظام التشغيل Android والإصدارات الأحدث، يمكن ضبط الجهاز المستجيب (الخادم) لقبول أي جهاز آخر، أي أنّه لا يحتاج إلى معرفة عنوان MAC الخاص بالجهاز المبدئي مسبقًا. يؤدي ذلك إلى تسريع عملية إعداد مسار البيانات ويتيح إنشاء روابط متعددة بين نقطتين باستخدام طلب شبكة واحد فقط.
  • يمكن للتطبيقات التي تعمل على الإصدار 12 من نظام التشغيل Android أو الإصدارات الأحدث استخدام طريقة WifiAwareManager.getAvailableAwareResources() للحصول على عدد مسارات البيانات المتاحة حاليًا ونشر الجلسات والاشتراك في الجلسات. ويمكن أن يساعد ذلك التطبيق في تحديد ما إذا كانت هناك موارد متاحة كافية لتنفيذ الوظائف المطلوبة.

الإعداد الأولي

لإعداد تطبيقك لاستخدام ميزة "الرصد والربط بشبكة" في Wi-Fi Aware، اتّبِع الخطوات التالية:

  1. يجب طلب الأذونات التالية في ملف بيان تطبيقك:

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
  2. تحقَّق مما إذا كان الجهاز يتيح استخدام Wi-Fi Aware من خلال واجهة برمجة التطبيقات PackageManager كما هو موضّح أدناه:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. تحقَّق ممّا إذا كانت ميزة Wi-Fi Aware متاحة حاليًا. قد تكون ميزة Wi-Fi Aware متوفّرة على الجهاز، ولكن قد لا تكون متاحة حاليًا لأنّ المستخدم أوقف Wi-Fi أو خدمات الموقع الجغرافي. قد لا تتوافق بعض الأجهزة مع Wi-Fi Aware إذا كانت تستخدم Wi-Fi Direct أو SoftAP أو الربط، وذلك حسب إمكانيات الأجهزة والبرامج الثابتة. للتحقّق مما إذا كانت ميزة Wi-Fi Aware متاحة حاليًا، اتّصِل بالرقم isAvailable().

    قد تتغيّر إمكانية توفّر Wi-Fi Aware في أي وقت. يجب أن يسجّل تطبيقك BroadcastReceiver لتلقّي ACTION_WIFI_AWARE_STATE_CHANGED، والذي يتم إرساله كلما تغيّر مدى التوفّر. عندما يتلقّى تطبيقك الغرض من البث، عليه تجاهل جميع الجلسات الحالية (بافتراض أنّ خدمة Wi-Fi Aware قد تعذّر الوصول إليها)، ثم التحقّق من حالة التوفّر الحالية وتعديل سلوكه وفقًا لذلك. مثلاً:

    Kotlin

    val wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager?
    val filter = IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED)
    val myReceiver = object : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            // discard current sessions
            if (wifiAwareManager?.isAvailable) {
                ...
            } else {
                ...
            }
        }
    }
    context.registerReceiver(myReceiver, filter)

    Java

    WifiAwareManager wifiAwareManager = 
            (WifiAwareManager)context.getSystemService(Context.WIFI_AWARE_SERVICE)
    IntentFilter filter =
            new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
    BroadcastReceiver myReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // discard current sessions
            if (wifiAwareManager.isAvailable()) {
                ...
            } else {
                ...
            }
        }
    };
    context.registerReceiver(myReceiver, filter);

لمزيد من المعلومات، يُرجى الاطّلاع على البث المباشر.

الحصول على جلسة

لبدء استخدام Wi-Fi Aware، يجب أن يحصل تطبيقك على WifiAwareSession من خلال استدعاء attach(). تنفّذ هذه الطريقة ما يلي:

  • يتم تفعيل أجهزة Wi-Fi Aware.
  • الانضمام إلى مجموعة Wi-Fi Aware أو إنشاؤها
  • تنشئ هذه الطريقة جلسة Wi-Fi Aware مع مساحة اسم فريدة تعمل كحاوية لجميع جلسات البحث التي تم إنشاؤها بداخلها.

في حال نجح التطبيق في إرفاق نفسه، ينفّذ النظام دالة الاستدعاء onAttached(). يوفّر هذا الإجراء ردّ الاتصال عنصر WifiAwareSession يجب أن يستخدمه تطبيقك في جميع عمليات الجلسة اللاحقة. يمكن للتطبيق استخدام الجلسة من أجل نشر خدمة أو الاشتراك في خدمة.

يجب أن يطلب تطبيقك attach() مرة واحدة فقط. إذا كان تطبيقك يستدعي attach() عدة مرات، سيتلقّى التطبيق جلسة مختلفة لكل عملية استدعاء، ولكل جلسة مساحة اسم خاصة بها. قد يكون ذلك مفيدًا في السيناريوهات المعقّدة، ولكن يجب تجنُّبه بشكل عام.

نشر خدمة

لإتاحة إمكانية اكتشاف خدمة، عليك استدعاء الطريقة publish() التي تتطلّب المَعلمات التالية:

  • تحدّد PublishConfig اسم الخدمة وخصائص الإعداد الأخرى، مثل فلتر المطابقة.
  • تحدّد DiscoverySessionCallback الإجراءات التي سيتم تنفيذها عند وقوع أحداث، مثل عندما يتلقّى المشترك رسالة.

وفي ما يلي مثال لذلك:

Kotlin

val config: PublishConfig = PublishConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.publish(config, object : DiscoverySessionCallback() {

    override fun onPublishStarted(session: PublishDiscoverySession) {
        ...
    }

    override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
        ...
    }
})

Java

PublishConfig config = new PublishConfig.Builder()
    .setServiceName(Aware_File_Share_Service_Name)
    .build();

awareSession.publish(config, new DiscoverySessionCallback() {
    @Override
    public void onPublishStarted(PublishDiscoverySession session) {
        ...
    }
    @Override
    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
        ...
    }
}, null);

في حال نجاح عملية النشر، يتم استدعاء طريقة onPublishStarted() ردّ الاتصال.

بعد النشر، عندما تنتقل الأجهزة التي تعمل بتطبيقات المشتركين المطابقة إلى نطاق Wi-Fi الخاص بالجهاز الناشر، يكتشف المشتركون الخدمة. عندما يعثر أحد المشتركين على ناشر، لا يتلقّى الناشر إشعارًا، ولكن إذا أرسل المشترك رسالة إلى الناشر، سيتلقّى الناشر إشعارًا. وعند حدوث ذلك، يتم استدعاء طريقة onMessageReceived() لإجراء معاودة الاتصال. يمكنك استخدام الوسيطة PeerHandle من هذه الطريقة لإرسال رسالة إلى المشترك أو إنشاء اتصال به.

لإيقاف نشر الخدمة، يُرجى الاتصال على DiscoverySession.close(). ترتبط جلسات الاستكشاف بـ WifiAwareSession الرئيسي. إذا تم إغلاق الجلسة الرئيسية، سيتم أيضًا إغلاق جلسات الاستكشاف المرتبطة بها. مع أنّ الكائنات التي تم تجاهلها يتم إغلاقها أيضًا، لا يضمن النظام وقت إغلاق الجلسات التي لم يعُد نطاقها ساريًا، لذا ننصحك باستدعاء طرق close() بشكل صريح.

الاشتراك في خدمة

للاشتراك في خدمة، استدعِ طريقة subscribe() التي تتطلّب المَعلمات التالية:

  • تحدّد SubscribeConfig اسم الخدمة التي تريد الاشتراك فيها وخصائص الإعداد الأخرى، مثل فلتر المطابقة.
  • تحدّد DiscoverySessionCallback الإجراءات التي سيتم تنفيذها عند وقوع أحداث، مثل اكتشاف ناشر.

وفي ما يلي مثال لذلك:

Kotlin

val config: SubscribeConfig = SubscribeConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.subscribe(config, object : DiscoverySessionCallback() {

    override fun onSubscribeStarted(session: SubscribeDiscoverySession) {
        ...
    }

    override fun onServiceDiscovered(
            peerHandle: PeerHandle,
            serviceSpecificInfo: ByteArray,
            matchFilter: List<ByteArray>
    ) {
        ...
    }
}, null)

Java

SubscribeConfig config = new SubscribeConfig.Builder()
    .setServiceName("Aware_File_Share_Service_Name")
    .build();

awareSession.subscribe(config, new DiscoverySessionCallback() {
    @Override
    public void onSubscribeStarted(SubscribeDiscoverySession session) {
        ...
    }

    @Override
    public void onServiceDiscovered(PeerHandle peerHandle,
            byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
        ...
    }
}, null);

في حال نجاح عملية الاشتراك، يستدعي النظام onSubscribeStarted() رد الاتصال في تطبيقك. وبما أنّه يمكنك استخدام الوسيطة SubscribeDiscoverySession في رد الاتصال للتواصل مع الناشر بعد أن يكتشف تطبيقك أحد الناشرين، عليك حفظ هذا المرجع. يمكنك تعديل جلسة الاشتراك في أي وقت من خلال استدعاء updateSubscribe() في جلسة البحث.

في هذه المرحلة، ينتظر اشتراكك أن يتوفّر ناشرون متوافقون ضمن نطاق شبكة Wi-Fi. عند حدوث ذلك، ينفّذ النظام طريقة onServiceDiscovered() الاستدعاء. يمكنك استخدام وسيطة PeerHandle من معاودة الاتصال هذه من أجل إرسال رسالة أو إنشاء اتصال بهذا الناشر.

لإيقاف الاشتراك في خدمة، اتّصِل بالرقم DiscoverySession.close(). ترتبط جلسات الاستكشاف بـ WifiAwareSession الرئيسي. إذا تم إغلاق الجلسة الرئيسية، سيتم أيضًا إغلاق جلسات الاستكشاف المرتبطة بها. مع أنّ الكائنات التي تم تجاهلها يتم إغلاقها أيضًا، لا يضمن النظام وقت إغلاق الجلسات التي لم يعُد نطاقها ساريًا، لذا ننصحك باستدعاء طرق close() بشكل صريح.

إرسال رسالة

لإرسال رسالة إلى جهاز آخر، تحتاج إلى العناصر التالية:

لإرسال رسالة، اتّصِل بالرقم sendMessage(). قد تحدث عمليات معاودة الاتصال التالية:

  • عندما يتلقّى الجهاز الآخر الرسالة بنجاح، يستدعي النظام دالة onMessageSendSucceeded() رد الاتصال في تطبيق الإرسال.
  • عندما يتلقّى الجهاز الآخر رسالة، يستدعي النظام دالة onMessageReceived() الرجوع في تطبيق الاستلام.

على الرغم من أنّ PeerHandle مطلوب للتواصل مع الأجهزة النظيرة، يجب عدم الاعتماد عليه كمعرّف دائم للأجهزة النظيرة. يمكن للتطبيق استخدام معرّفات ذات مستوى أعلى، سواء كانت مضمّنة في خدمة البحث نفسها أو في الرسائل اللاحقة. يمكنك تضمين معرّف في خدمة الاكتشاف باستخدام الطريقتَين setMatchFilter() أو setServiceSpecificInfo() في PublishConfig أو SubscribeConfig. تؤثّر الطريقة setMatchFilter() في عملية العثور على المحتوى، بينما لا تؤثّر الطريقة setServiceSpecificInfo() في هذه العملية.

يتضمّن تضمين معرّف في رسالة تعديل مصفوفة بايت الرسالة لتضمين معرّف (على سبيل المثال، كأول وحدتَي بايت).

إنشاء اتصال

تتيح تقنية Wi-Fi Aware إنشاء شبكة بين جهازَين يستخدمان هذه التقنية.

لإعداد الاتصال بين العميل والخادم، اتّبِع الخطوات التالية:

  1. استخدِم ميزة "الاستكشاف عبر Wi-Fi Aware" من أجل نشر خدمة (على الخادم) والاشتراك في خدمة (على العميل).

  2. بعد أن يعثر المشترك على الناشر، عليه إرسال رسالة من المشترك إلى الناشر.

  3. ابدأ ServerSocket على جهاز الناشر، ثم اضبط المنفذ أو احصل عليه:

    Kotlin

    val ss = ServerSocket(0)
    val port = ss.localPort

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. استخدِم ConnectivityManager لطلب شبكة Wi-Fi Aware من الناشر باستخدام WifiAwareNetworkSpecifier، مع تحديد جلسة البحث وPeerHandle للمشترك، والذي حصلت عليه من الرسالة التي أرسلها المشترك:

    Kotlin

    val networkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build()
    val myNetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build()
    val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            ...
        }
    
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            ...
        }
    
        override fun onLost(network: Network) {
            ...
        }
    }
    
    connMgr.requestNetwork(myNetworkRequest, callback);

    Java

    NetworkSpecifier networkSpecifier = new WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build();
    NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build();
    ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            ...
        }
    
        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
            ...
        }
    
        @Override
        public void onLost(Network network) {
            ...
        }
    };
    
    ConnectivityManager connMgr.requestNetwork(myNetworkRequest, callback);
  5. بعد أن يطلب الناشر شبكة، عليه إرسال رسالة إلى المشترك.

  6. بعد أن يتلقّى المشترك الرسالة من الناشر، اطلب شبكة Wi-Fi Aware من المشترك باستخدام الطريقة نفسها التي استخدمها الناشر. لا تحدّد منفذًا عند إنشاء NetworkSpecifier. يتم استدعاء طرق معاودة الاتصال المناسبة عندما يكون اتصال الشبكة متاحًا أو تم تغييره أو انقطع.

  7. بعد استدعاء الطريقة onAvailable() على المشترك، يصبح الكائن Network متاحًا ويمكنك استخدامه لفتح Socket للتواصل مع ServerSocket على الناشر، ولكن يجب معرفة عنوان IPv6 ومنفذ ServerSocket. يمكنك الحصول على هذه القيم من عنصر NetworkCapabilities المقدَّم في دالة معاودة الاتصال onCapabilitiesChanged():

    Kotlin

    val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo
    val peerIpv6 = peerAwareInfo.peerIpv6Addr
    val peerPort = peerAwareInfo.port
    ...
    val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)

    Java

    WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
    Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
    int peerPort = peerAwareInfo.getPort();
    ...
    Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
  8. عند الانتهاء من الاتصال بالشبكة، انقر على رمز المكالمة unregisterNetworkCallback().

تحديد النطاق بين الأجهزة واكتشاف الأجهزة القريبة

يمكن لجهاز يتضمّن إمكانات تحديد الموقع الجغرافي باستخدام Wi-Fi RTT قياس المسافة مباشرةً إلى الأجهزة المجاورة واستخدام هذه المعلومات لتقييد عملية البحث عن الخدمات باستخدام Wi-Fi Aware.

تتيح واجهة برمجة التطبيقات Wi-Fi RTT تحديد المسافة مباشرةً إلى جهاز Wi-Fi Aware باستخدام عنوان MAC أو PeerHandle.

يمكن حصر عملية البحث عن الأجهزة القريبة باستخدام Wi-Fi Aware على اكتشاف الخدمات ضمن نطاق جغرافي معيّن فقط. على سبيل المثال، يمكنك إعداد سياج جغرافي يسمح باكتشاف جهاز ينشر خدمة "Aware_File_Share_Service_Name" على مسافة لا تقل عن 3 أمتار (يتم تحديدها على أنّها 3,000 ملم) ولا تزيد عن 10 أمتار (يتم تحديدها على أنّها 10,000 ملم).

لتفعيل ميزة "السياج الجغرافي"، يجب أن يتّخذ كل من الناشر والمشترك الإجراءات التالية:

  • على الناشر تفعيل تحديد المدى في الخدمة المنشورة باستخدام setRangingEnabled(true).

    إذا لم يفعّل الناشر تحديد المدى، سيتم تجاهل أي قيود على السياج الجغرافي يحدّدها المشترك، وسيتم إجراء عملية البحث العادية بدون مراعاة المسافة.

  • على المشترك تحديد سياج جغرافي باستخدام مجموعة من setMinDistanceMm و setMaxDistanceMm.

    بالنسبة إلى أيّ من القيمتين، تشير المسافة غير المحدّدة إلى عدم وجود حدّ. يؤدي تحديد الحد الأقصى للمسافة فقط إلى ضِمنية الحد الأدنى للمسافة وهو 0. ويعني تحديد الحد الأدنى للمسافة فقط أنّه لا يوجد حد أقصى.

عندما يتم رصد خدمة جهاز قريب ضمن نطاق سياج جغرافي، يتم تشغيل معاودة الاتصال onServiceDiscoveredWithinRange، ما يوفّر المسافة المقاسة إلى الجهاز القريب. يمكن بعد ذلك طلب بيانات من واجهة برمجة التطبيقات المباشرة لخدمة Wi-Fi RTT حسب الحاجة لقياس المسافة في أوقات لاحقة.