إنشاء جهاز توجيه حدودي على Android

عرض المصدر على أداة "بحث الرموز البرمجية لنظام التشغيل Android"

إذا لم تكن من مورّدي شرائح Thread أو أجهزة Android، يمكنك التوقف عن القراءة الآن.

يرشدك هذا المستند خلال خطوات إنشاء جهاز Thread Border Router جديد يستند إلى Android باستخدام أحدث رمز مصدر AOSP. من خلال اتّباع هذا المستند، ستتعرّف على ما يلي:

  1. البنية العامة وحالة إتاحة Thread في Android
  2. كيفية إنشاء خدمة Thread HAL
  3. كيفية جعل جهازك متوافقًا مع Google Home
  4. كيفية اختبار جهاز توجيه الحدود Thread

إذا كنت بحاجة إلى دعم، يُرجى الإبلاغ عن مشكلة في GitHub أو فتح مناقشة إذا كان لديك أي أسئلة.

نظرة عامة

تستند حِزمة Android Thread إلى OpenThread وot-br-posix، وهي برامج مفتوحة المصدر تقدّمها Google على GitHub. بالطريقة نفسها التي يتم بها تطوير OpenThread في مستودع GitHub مفتوح، يتم تطوير حِزمة Android Thread في قاعدة بيانات AOSP المفتوحة. يتم إرسال جميع الميزات وإصلاحات الأخطاء أولاً في AOSP. يتيح ذلك للمورّدين بدء استخدام أحدث إصدارات Thread بدون انتظار إصدارات Android العادية.

الهندسة المعمارية

تتألف حزمة Android Thread بالكامل من مكوّنَين رئيسيَّين: حزمة Thread الأساسية في قسم نظام عام وخدمة Thread HAL في قسم مخصّص للمورّد. لا يحتاج مورّدو الأجهزة عادةً إلا إلى إنشاء خدمة HAL والاعتناء بها.

android-thread-arch

في ما يلي ملخّص موجز لطريقة عمل حِزمة Android Thread: - هناك خدمة نظام Java Thread في خادم النظام تدير الحِزمة بأكملها، وهي توفّر واجهة برمجة التطبيقات لنظام Thread، وتُنشئ thread-wpan واجهة النفق، وتُسجِّل شبكة Thread في خدمة الاتصال وتنفِّذ وظيفتَي "توجيه الحدود" و"الخادم الوكيل للإعلانات". ‫- يتم استضافة حِزمة Thread / OpenThread الأساسية في عملية مستقلة أصلية غير مفوَّضة باسم ot-daemon. تتم إدارة ot-daemon مباشرةً من خلال خدمة نظام Java عبر واجهات برمجة تطبيقات AIDL الخاصة، كما يمكنها الوصول إلى بث الوسائط المرئي والصوتي من خلال واجهة برمجة التطبيقات Thread HAL API. - يجب أن توفّر خدمة Thread HAL التي يوفّرها المورّد واجهة برمجة التطبيقات Thread HAL API. ويعمل عادةً كمنصة RCP وينفِّذ بروتوكول spinel.

أين الرمز؟

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

يمكن لمورّدي أجهزة Android الذين سبق لهم إنشاء بيئة تطوير لنظام Android على الجهاز تخطّي هذا القسم.

إذا كنت مبتدئًا في منظومة Android المتكاملة أو كنت مورّدًا لشرائح السيليكون وتريد جعل شريحة Thread متوافقة مع Android وتقديم الدعم لمورّدي الأجهزة، يُرجى مواصلة القراءة.

اتّباع الدرس التطبيقي حول الترميز المخصّص لمطوّري تطبيقات Android

لإعداد بيئة تطوير Android للمرة الأولى، استخدِم الدرس التطبيقي التالي: https://source.android.com/docs/setup/start. في نهاية هذا الدليل التعليمي حول الرموز البرمجية، ستتمكّن من إنشاء جهاز Cuttlefish محاكي وتشغيله من الرمز المصدر.

إنشاء خدمة Thread HAL

تجربة ميزة "سلسلة محادثات" في Cuttlefish

Cuttlefish هو جهاز Android افتراضي. قبل بدء إنشاء خدمة HAL الخاصة بك، من الأفضل تجربة Thread في Cuttlefish لفهم آلية عمل HAL.

تتوفر خدمة Thread HAL تلقائيًا في Cuttlefish ويتم تنفيذها باستخدام بروتوكول RCP المحاكي الذي ينقل ويستقبل الحِزم عبر مقبس UDP من وإلى برمجية محاكاة Thread (802.15.4) اللاسلكية.

في مثيل Cuttlefish، يكون تطبيق ThreadNetworkDemoApp مثبّتًا مسبقًا. افتح هذا التطبيق للانضمام بجهاز Cuttlefish إلى شبكة Thread التلقائية.

demoapp-screenshot

تتوفّر أيضًا أداتا سطر الأوامر ot-ctl وot-cli-ftd لضبط شبكة Thread بغرض اختبارها. تتوافق هذه الأدوات مع جميع أوامر OpenThread CLI التي قد تكون على دراية بها.

يمكنك استخدام grep للبحث عن سجلات خدمة Cuttlefish Thread HAL من خلال:

adb logcat | egrep -i threadnetwork-service

07-21 10:43:05.048     0     0 I init    : Parsing file /apex/com.android.hardware.threadnetwork/etc/threadnetwork-service.rc...
07-21 10:59:27.233   580   580 W android.hardware.threadnetwork-service: ThreadChip binder is unlinked
07-21 10:59:27.233   580   580 I android.hardware.threadnetwork-service: Close IThreadChip successfully
07-21 10:59:27.385   580   580 I android.hardware.threadnetwork-service: Open IThreadChip successfully

أو يمكنك استخدام grep للبحث عن سجلّات ot-daemon من خلال:

adb logcat | egrep -i ot-daemon
07-21 10:43:48.741     0     0 I init    : starting service 'ot-daemon'...
07-21 10:43:48.742     0     0 I init    : Created socket '/dev/socket/ot-daemon/thread-wpan.sock', mode 660, user 1084, group 1084
07-21 10:43:48.762     0     0 I init    : ... started service 'ot-daemon' has pid 2473
07-21 10:46:26.320  2473  2473 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-21 10:46:30.290  2473  2473 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer
07-21 10:48:07.264  2473  2473 I ot-daemon: [INFO]-BINDER--: Start joining...
07-21 10:48:07.267  2473  2473 I ot-daemon: [I] Settings------: Saved ActiveDataset
07-21 10:48:07.267  2473  2473 I ot-daemon: [I] DatasetManager: Active dataset set
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] DnssdServer---: Started
07-21 10:48:07.273  2473  2473 I ot-daemon: [N] Mle-----------: Role disabled -> detached
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] Mle-----------: AttachState Idle -> Start
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) [Ip6+ Role LLAddr MLAddr KeySeqCntr Ip6Mult+ Channel PanId NetName ExtPanId ...
07-21 10:48:07.273  2473  2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) ... NetworkKey PSKc SecPolicy NetifState ActDset]

تستخدم خدمة Cuttlefish Thread HAL خدمة Thread HAL التلقائية بالإضافة إلى ملف RCP الثنائي المحاكي لـ OpenThread. اطّلِع على القسم التالي لمعرفة كيفية عملها.

خدمة HAL التلقائية

يتم تضمين خدمة HAL التلقائية مع Thread HAL API. تتوافق خدمة HAL التلقائية مع كل من أجهزة RCP المحاكية والحقيقية. يتلقّى هذا الإجراء عنوان URL اختياريًا لجهاز RCP، وإذا لم يتم تقديم عنوان URL، يتم ضبطه تلقائيًا على جهاز RCP المحاكي.

في الملف hardware/interfaces/threadnetwork/aidl/default/threadnetwork-service.rc:

service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service
    class hal
    user thread_network

ويعادل ذلك ما يلي:

service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+forkpty:///apex/com.android.hardware.threadnetwork/bin/ot-rcp?forkpty-arg=1
    class hal
    user thread_network

بالنسبة إلى أجهزة RCP الحقيقية، تتيح واجهات SPI وUART، ويمكنك تحديد الجهاز باستخدام المخطّط spinel+spi:// وspinel+hdlc+uart:// و spinel+socket:// على التوالي.

فهم APEX الخاص بالمورّد

على غرار حِزمة Thread في وحدة Tethering الرئيسية، يتم أيضًا تجميع خدمة Thread HAL التلقائية في Cuttlefish في وحدة APEX. ولكن هذه ملفًا لوحدة APEX الخاصة بالمورّد وسيتم تثبيتها على /vendor/apex/ (سيتم فك ضغط العناصر في الوحدة إلى /apex/com.android.hardware.threadnetwork/).

apex {
    name: "com.android.hardware.threadnetwork",
    manifest: "manifest.json",
    file_contexts: "file_contexts",
    key: "com.android.hardware.key",
    certificate: ":com.android.hardware.certificate",
    updatable: false,
    vendor: true,

    binaries: [
        "android.hardware.threadnetwork-service",
        "ot-rcp",
    ],

    prebuilts: [
        "threadnetwork-default.xml", // vintf_fragment
        "threadnetwork-service.rc", // init_rc
        "android.hardware.thread_network.prebuilt.xml", // permission
    ],
}

هناك بعض الإعدادات المهمة التي يجب الانتباه إليها أو إجراء تغييرات عليها عند إنشاء وحدة HAL APEX الخاصة بك:

  • file_contexts: يصف هذا الحقل ملفات البيانات أو الملفات الثنائية التي يتم تسليمها في ملف وحدة APEX هذا أو الملفات التي تحتاج خدمة HAL إلى الوصول إليها (مثل جهاز RCP ). يتيح لك ذلك تحديد قواعد سياسة أمان معيّنة لخدمة HAL للوصول إلى جهاز RCP للأجهزة.

  • binaries: الملف الثنائي الذي تم إرساله في وحدة APEX هذه

  • threadnetwork-service.rc: كيفية بدء خدمة HAL عليك تحديد مسار جهاز RCP هنا.

  • android.hardware.thread_network.prebuilt.xml: لتحديد ميزة الجهاز android.hardware.thread_network هذه الخطوة مطلوبة لكي يعرف نظام Android أنّ جهازك متوافق مع الأجهزة التي تستخدم بروتوكول Thread. بخلاف ذلك، لن يتم تفعيل حزمة Android Thread.

إنشاء خدمة HAL

سواءً كنت مطوّر أجهزة Android أو مورّد سيليكون، يجب أن تكون على دراية بإنشاء البرامج الثابتة لبروتوكول RCP في شبكة الطاقة (OT RCP) لشريحة Thread. تفترض التعليمات التالية أنّ شريحة الجهاز متصلة بشكلٍ صحيح وقد تم التحقّق منها.

إنّ أبسط طريقة لإنشاء HAL APEX هي إنشاء APEX جديد باستخدام الثنائيات والإصدارات المُسبقة من HAL APEX التلقائي. على سبيل المثال، إذا كانت شركتك هي Banana وكان جهاز RCP على جهازك هو /dev/ttyACM0، سيظهر Thread HAL APEX على النحو التالي:

  • Android.bp:
  prebuilt_etc {
    name: "banana-threadnetwork-service.rc",
    src: "banana-threadnetwork-service.rc",
    installable: false,
  }

  apex {
    name: "com.banana.android.hardware.threadnetwork",
    manifest: "manifest.json",
    file_contexts: "file_contexts",
    key: "com.android.hardware.key",
    certificate: ":com.android.hardware.certificate",
    updatable: false,
    vendor: true,

    binaries: [
        "android.hardware.threadnetwork-service",
    ],

    prebuilts: [
        "banana-threadnetwork-service.rc",
        "threadnetwork-default.xml",
        "android.hardware.thread_network.prebuilt.xml",
    ],
  }
  • file_contexts:
  (/.*)?                                                      u:object_r:vendor_file:s0
  /etc(/.*)?                                                  u:object_r:vendor_configs_file:s0
  /bin/hw/android\.hardware\.threadnetwork-service            u:object_r:hal_threadnetwork_default_exec:s0
  /dev/ttyACM0                                                u:object_r:threadnetwork_rcp_device:s0

ترتبط مسارات الملفات في العمود الأول بـ /apex/com.android.hardware.threadnetwork/.

  • threadnetwork-service.rc:
  service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=115200
    class hal
    user root
  • manifest.json:
  {
    "name": "com.android.hardware.threadnetwork",
    "version": 1
  }

لنفترض أنّك تنشئ جهازًا جديدًا باسم Orange، سيكون دليل الإعدادات الخاص بالجهاز على النحو التالي:

device/banana/orange/threadnetwork/
    sepolicy/
    Android.bp
    file_contexts
    manifest.json
    threadnetwork-default.xml
    threadnetwork-service.rc

اطّلِع على القسم التالي لمعرفة قواعد sepolicy التي يجب إضافتها في الدليل الفرعي sepolicy/.

قواعد سياسة الأمان لجهاز RCP

لا تملك خدمة Thread HAL إذن الوصول إلى جهاز RCP تلقائيًا (مثل /dev/ttyACM0)، ويجب إضافة قواعد سياسة الأمان المخصّصة إلى دليل sepolicy/.

أنشئ ملف sepolicy/threadnetwork_hal.te جديدًا يتضمّن المحتوى التالي:

type threadnetwork_rcp_device, dev_type;

# Allows the Thread HAL service to read / write the Thread RCP device
allow hal_threadnetwork_default threadnetwork_rcp_device:chr_file rw_file_perms;

تجميع

لقد أكملت الآن جميع متطلبات الرمز البرمجي تقريبًا لإضافة Thread، والخطوة الأخيرة هي إضافة Thread HAL APEX وقواعد sepolicy إلى صورة جهازك.

يمكنك إجراء ذلك عن طريق إضافة الرمز أدناه إلى Makefile على جهازك (على سبيل المثال، device.mk):

PRODUCT_PACKAGES += com.banana.hardware.threadnetwork
BOARD_SEPOLICY_DIRS += device/banana/orange/threadnetwork/sepolicy

إذا كان كل شيء يعمل على ما يرام، ستتمكّن الآن من الاطّلاع على سجلّ خدمة Thread HAL على النحو التالي:

adb logcat | egrep -i threadnetwork-service
08-13 13:26:41.751   477   477 I android.hardware.threadnetwork-service: ServiceName: android.hardware.threadnetwork.IThreadChip/chip0, Url: spinel+spi
08-13 13:26:41.751   477   477 I android.hardware.threadnetwork-service: Thread Network HAL is running
08-13 13:26:55.165   477   477 I android.hardware.threadnetwork-service: Open IThreadChip successfully

سيكون سجلّ ot-daemon على النحو التالي:

adb logcat -s ot-daemon
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Running OTBR_AGENT/Unknown
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Thread version: 1.3.0
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Thread interface: thread-wpan
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Backbone interface is not specified
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-AGENT---: Radio URL: threadnetwork_hal://binder?none
08-13 13:26:55.157  1019  1019 I ot-daemon: [NOTE]-ILS-----: Infra link selected:
08-13 13:26:55.160  1019  1019 I ot-daemon: [I] Platform------: [HAL] Wait for getting the service android.hardware.threadnetwork.IThreadChip/chip0 ...
08-13 13:26:55.165  1019  1019 I ot-daemon: [I] Platform------: [HAL] Successfully got the service android.hardware.threadnetwork.IThreadChip/chip0
08-13 13:26:55.275  1019  1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_UNKNOWN
08-13 13:26:55.276  1019  1019 I ot-daemon: [I] P-RadioSpinel-: Software reset RCP successfully
08-13 13:26:55.277  1019  1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_POWER_ON
08-13 13:26:55.322  1019  1019 I ot-daemon: [I] ChildSupervsn-: Timeout: 0 -> 190
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] RoutingManager: Initializing - InfraIfIndex:0
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] InfraIf-------: Init infra netif 0
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] Settings------: Read BrUlaPrefix fd7b:cc45:ff06::/48
08-13 13:26:55.324  1019  1019 I ot-daemon: [N] RoutingManager: BR ULA prefix: fd7b:cc45:ff06::/48 (loaded)
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] RoutingManager: Generated local OMR prefix: fd7b:cc45:ff06:1::/64
08-13 13:26:55.324  1019  1019 I ot-daemon: [N] RoutingManager: Local on-link prefix: fdde:ad00:beef:cafe::/64
08-13 13:26:55.324  1019  1019 I ot-daemon: [I] RoutingManager: Enabling

التخصيص

توفّر وحدة Thread الرئيسية (التي تشكّل في الواقع جزءًا من وحدة "ربط الأجهزة") بضع إعدادات قابلة للتراكب يمكن للمورّدين تحديدها لتخصيص سلوك الحزمة. اطّلِع علىملف config_thread.xml للحصول على القائمة الكاملة.

عادةً ما يجب ضبط config_thread_border_router_default_enabled على true لتفعيل جهازك كجهاز توجيه حدودي في Thread، وتغيير config_thread_vendor_name وconfig_thread_vendor_oui و config_thread_model_name إلى قيم المورّد أو المنتج. سيتم تضمين هذه القيم في خدمة _meshcop._udp mDNS التي يعلن عنها دائمًا جهاز توجيه حدود Thread.

لإضافة التراكب، عليك إنشاء هدف ConnectivityOverlayOrange runtime_resource_overlay جديد لجهاز Orange. أنشئ دليلاً جديدًا باسم ConnectivityOverlay/ ضمن device/banana/orange/rro_overlays وأنشئ المحتوى التالي فيه:

device/banana/orange/rro_overlays/ConnectivityOverlay/
  res
    values
      config_thread.xml
  Android.bp
  AndroidManifest.xml
  • Android.bp:
  package {
      default_applicable_licenses: ["Android-Apache-2.0"],
  }

  runtime_resource_overlay {
      name: "ConnectivityOverlayOrange",
      manifest: "AndroidManifest.xml",
      resource_dirs: ["res"],
      certificate: "platform",
      product_specific: true,
      sdk_version: "current",
  }
  • AndroidManifest.xml:
  <!-- Orange overlays for the Connectivity module -->
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.banana.android.connectivity.resources.orange"
      android:versionCode="1"
      android:versionName="1.0">
      <application android:hasCode="false" />

      <!-- If your device uses google-signed mainline modules, the targetPackage
      needs to be "com.google.android.connectivity.resources", otherise, it
      should be "com.android.connectivity.resources"
      -->
      <overlay
          android:targetPackage="com.google.android.connectivity.resources"
          android:targetName="ServiceConnectivityResourcesConfig"
          android:isStatic="true"
          android:priority="1"/>
  </manifest>
  
  • config_thread.xml:
  <bool name="config_thread_border_router_default_enabled">true</bool>
  <string translatable="false" name="config_thread_vendor_name">Banana Inc.</string>
  <string translatable="false" name="config_thread_vendor_oui">AC:DE:48</string>
  <string translatable="false" name="config_thread_model_name">Orange</string>
  

كما هو الحال مع HAL APEX، عليك إضافة التطبيق المتراكب إلى ملف device.mk:

PRODUCT_PACKAGES += \
    ConnectivityOverlayOrange</code>

إذا كان كل شيء يعمل على ما يرام، ستلاحظ أنّ ot-daemon يسجّل اسم المورّد والطراز في بداية السجلّ:

adb logcat -s ot-daemon
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] Cli-----------: Input: state
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] Cli-----------: Output: disabled
07-22 15:31:37.693  1472  1472 I ot-daemon: [I] Cli-----------: Output: Done
07-22 15:31:37.693  1472  1472 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer
07-22 15:31:50.091  1472  1472 I ot-daemon: [I] P-Daemon------: Session socket is ready
07-22 15:31:50.091  1472  1472 I ot-daemon: [I] Cli-----------: Input: factoryreset
07-22 15:31:50.092  1472  1472 I ot-daemon: [I] Settings------: Wiped all info
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-ADPROXY-: Stopped
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-DPROXY--: Stopped
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-BA------: Stop Thread Border Agent
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-BA------: Unpublish meshcop service Banana Inc. Orange #4833._meshcop._udp.local
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-MDNS----: Removing service Banana Inc. Orange #4833._meshcop._udp
07-22 15:31:50.092  1472  1472 I ot-daemon: [INFO]-MDNS----: Unpublishing service Banana Inc. Orange #4833._meshcop._udp listener ID = 0

أن تكون متوافقة مع Google Home

بالإضافة إلى ذلك، إذا كنت تريد أن يستخدم نظام Google Home المتكامل جهاز التوجيه الحدودي، يمكنك تحديد هذا الإعداد في config_thread.xml:

<string-array name="config_thread_mdns_vendor_specific_txts">
  <item>vgh=1</item>
</string-array>

الاختبار

من المفترض أن يكون جهازك متوافقًا مع مواصفات جهاز توجيه الحدود Thread 1.3 والإصدارات الأحدث الآن. قبل إرسال التطبيق إلى برنامج اعتماد Thread، يجب إجراء بعض اختبارات xTS لنظام التشغيل Android لضمان التوافق.

  • يضمن اختبار VTS عمل خدمة Thread HAL على النحو المتوقّع على جهازك. يمكنك إجراء الاختبارات باستخدام الأمر

    atest VtsHalThreadNetworkTargetTest

  • يضمن اختبار CTS عمل واجهات برمجة التطبيقات Thread على النحو المتوقّع على جهازك. يمكنك إجراء الاختبارات باستخدام الأمر

    atest CtsThreadNetworkTestCases

  • يقدّم اختبار الدمج ضمانًا إضافيًا للجودة بشأن طريقة عمل رمز Thread mainline على جهازك. يمكنك إجراء الاختبارات باستخدام الأمر

    atest ThreadNetworkIntegrationTests

يمكنك أيضًا العثور على مزيد من التعليمات حول كيفية إجراء اختبارات VTS/CTS/MTS باستخدام مجموعات الاختبار التي تم إصدارها:

الاختبار باستخدام تطبيق Thread التجريبي

على غرار جهاز Cuttlefish، يمكنك إضافة تطبيق Thread التجريبي إلى صورة النظام:

# ThreadNetworkDemoApp for testing
PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp

يُرجى العلم أنّه يجب إضافته إلى الصيغة المخصّصة لتصحيح الأخطاء أو الإصدار المخصّص للمطوّرين فقط (على سبيل المثال، PRODUCT_PACKAGES_DEBUG) لأنّه من المفترض ألا يتم تضمينه في الإصدار المخصّص للمستخدمين النهائيين.