bluetooth --- Bluetooth منخفض المستوى

توفر هذه الوحدة واجهة للتحكم في وحدة Bluetooth المدمجة على اللوحة. وهي تدعم Bluetooth Low Energy (BLE) في أدوار Central وPeripheral وBroadcaster وObserver، إضافة إلى GATT Server وClient وقنوات L2CAP الموجهة بالاتصال. ويمكن للجهاز أن يعمل في عدة أدوار في آن واحد. كما أن الإقران (Pairing) والربط (Bonding) مدعومان أيضاً.

صُممت هذه الواجهة (API) لتطابق بروتوكول Bluetooth منخفض المستوى ولتوفر لبنات بناء لتجريدات أعلى مستوى مثل أنواع أجهزة محددة.

نصيحة

بالنسبة لمعظم التطبيقات، يُفضّل استخدام مكتبة aioble الأعلى مستوى، والتي توفر غلافاً قائماً على asyncio حول هذه الوحدة. راجع aioble --- BLE غير المتزامن.

class BLE

class bluetooth.BLE

تُرجع كائن BLE الوحيد (singleton).

التهيئة

active(active: bool | None = None, /) bool

تغيّر اختيارياً حالة تنشيط راديو BLE، وتُرجع الحالة الحالية.

يجب تنشيط الراديو قبل استخدام أي من الدوال الأخرى في هذا الصنف.

config(param: str, /) Any
config(*, **kwargs: Any) None

للحصول على قيم تهيئة واجهة BLE أو ضبطها. للحصول على قيمة، يجب وضع اسم المعامل بين علامتي اقتباس كسلسلة نصية، ويُستعلم عن معامل واحد فقط في كل مرة. ولضبط القيم استخدم صيغة الكلمات المفتاحية، إذ يمكن ضبط معامل واحد أو أكثر في المرة الواحدة.

القيم المدعومة حالياً هي:

  • 'mac': العنوان المستخدم حالياً، اعتماداً على وضع العنونة الحالي. يُرجع هذا صفّاً (tuple) بالشكل (addr_type, addr).

    راجع gap_scan للاطلاع على تفاصيل نوع العنوان.

    لا يمكن الاستعلام عن هذا إلا عندما تكون الواجهة نشطة حالياً.

  • 'addr_mode': يضبط وضع العنونة. القيم هي:

    القيمة

    الاسم

    السلوك

    0x00

    PUBLIC

    استخدام العنوان العام (public) للمتحكم.

    0x01

    RANDOM

    استخدام عنوان ثابت مُولَّد.

    0x02

    RPA

    استخدام عناوين خاصة قابلة للحل (resolvable).

    0x03

    NRPA

    استخدام عناوين خاصة غير قابلة للحل (non-resolvable).

    بشكل افتراضي، ستستخدم الواجهة عنواناً من نوع PUBLIC إن كان متاحاً، وإلا فستستخدم عنواناً من نوع RANDOM.

  • 'gap_name': للحصول على اسم جهاز GAP المستخدم من قِبل خدمة Generic Access (المعرّف UUID 0x1800)، خاصية Device Name (المعرّف UUID 0x2a00) أو ضبطه. يمكن ضبط هذا في أي وقت وتغييره عدة مرات.

  • 'rxbuf': للحصول على حجم المخزن المؤقت الداخلي المستخدم لتخزين الأحداث الواردة بالبايت أو ضبطه. هذا المخزن المؤقت عام لكامل مُشغِّل BLE، ومن ثَمَّ يتعامل مع البيانات الواردة لجميع الأحداث، بما في ذلك جميع الخصائص. زيادته تتيح تعاملاً أفضل مع البيانات الواردة المتدفقة دفعةً واحدة (مثل نتائج المسح) والقدرة على استقبال قيم خصائص أكبر.

  • 'mtu': للحصول على قيمة MTU التي ستُستخدم أثناء تبادل ATT MTU أو ضبطها. ستكون قيمة MTU الناتجة هي الأصغر بين هذه القيمة وقيمة MTU للجهاز البعيد. لن يحدث تبادل ATT MTU تلقائياً (ما لم يبادر الجهاز البعيد بذلك)، ويجب بدؤه يدوياً باستخدام gattc_exchange_mtu. استخدم الحدث _IRQ_MTU_EXCHANGED لاكتشاف قيمة MTU لاتصال معيّن.

  • 'bond': يضبط ما إذا كان الربط (bonding) سيُفعَّل أثناء الإقران. عند تفعيله، ستضبط طلبات الإقران علامة "bond" وسيخزّن كلا الجهازين المفاتيح.

  • 'mitm': يضبط ما إذا كانت الحماية من هجمات MITM مطلوبة للإقران.

  • 'io': يضبط قدرات الإدخال/الإخراج (I/O) لهذا الجهاز.

    الخيارات المتاحة هي:

    الثابت

    القيمة

    القدرة

    _IO_CAPABILITY_DISPLAY_ONLY

    0

    العرض فقط

    _IO_CAPABILITY_DISPLAY_YESNO

    1

    العرض مع إدخال نعم/لا

    _IO_CAPABILITY_KEYBOARD_ONLY

    2

    لوحة المفاتيح فقط

    _IO_CAPABILITY_NO_INPUT_OUTPUT

    3

    لا إدخال ولا إخراج

    _IO_CAPABILITY_KEYBOARD_DISPLAY

    4

    لوحة مفاتيح وشاشة عرض

  • 'le_secure': يضبط ما إذا كان الإقران من نوع "LE Secure" مطلوباً. القيمة الافتراضية هي false (أي السماح بـ "Legacy Pairing").

معالجة الأحداث

irq(handler: Callable[[int, Tuple], Any | None], /) None

تُسجّل دالة رد نداء للأحداث الصادرة من مكدس BLE. تأخذ دالة handler وسيطين، هما event (وستكون أحد الرموز أدناه) وdata (وهي صفّ (tuple) من القيم خاص بالحدث).

ملاحظة: كتحسين لمنع التخصيصات غير الضرورية للذاكرة، فإن المدخلات addr وadv_data وchar_data وnotify_data وuuid في الصفوف (tuples) هي نسخ memoryview للقراءة فقط تشير إلى المخزن الحلقي (ringbuffer) الداخلي لوحدة bluetooth، وهي صالحة فقط أثناء استدعاء دالة معالج مقاطعة IRQ. وإذا احتاج برنامجك إلى حفظ إحدى هذه القيم للوصول إليها بعد عودة معالج IRQ (مثلاً بحفظها في نسخة صنف أو في متغير عام)، فعليه أن يأخذ نسخة من البيانات، إما باستخدام bytes() أو bluetooth.UUID()، على هذا النحو:

connected_addr = bytes(addr)  # equivalently: adv_data, char_data, or notify_data
matched_uuid = bluetooth.UUID(uuid)

على سبيل المثال، قد يفحص معالج IRQ الخاص بنتيجة مسح القيمة adv_data ليقرر ما إذا كان الجهاز هو الجهاز الصحيح، وعندها فقط ينسخ بيانات العنوان لاستخدامها في مكان آخر من البرنامج. ولطباعة البيانات من داخل معالج IRQ، ستكون print(bytes(addr)) مطلوبة.

عادةً ما يوزّع المعالج التنفيذ بناءً على رمز الحدث ويفكّ تغليف صفّ الحمولة الخاص بالحدث:

def bt_irq(event, data):
    if event == _IRQ_CENTRAL_CONNECT:
        conn_handle, addr_type, addr = data
        ...
    elif event == _IRQ_SCAN_RESULT:
        addr_type, addr, adv_type, rssi, adv_data = data
        ...

فيما يلي قائمة بكل رمز حدث وما يقدّمه من حمولة ووصف مختصر له. بالنسبة للأحداث التي يُذكر فيها الحقل status، تكون قيمة status هي 0 عند النجاح وقيمة غير صفرية خاصة بالتنفيذ عند الفشل.

الثابت

القيمة

الحدث

صفّ الحمولة (tuple)

_IRQ_CENTRAL_CONNECT

1

اتصل جهاز central بهذا الجهاز الطرفي (peripheral).

(conn_handle, addr_type, addr)

_IRQ_CENTRAL_DISCONNECT

2

انقطع اتصال جهاز central عن هذا الجهاز الطرفي (peripheral).

(conn_handle, addr_type, addr)

_IRQ_GATTS_WRITE

3

كتب عميل متصل إلى خاصية أو واصف محلي. استخدم gatts_read لجلب القيمة الجديدة.

(conn_handle, attr_handle)

_IRQ_GATTS_READ_REQUEST

4

أصدر عميل متصل طلب قراءة. أرجِع رمز خطأ غير صفري من الجدول أدناه لرفض القراءة، أو 0 / None لقبولها.

(conn_handle, attr_handle)

_IRQ_SCAN_RESULT

5

تم استقبال حزمة إعلان (advertising) واحدة أثناء مسح نشط.

(addr_type, addr, adv_type, rssi, adv_data)

_IRQ_SCAN_DONE

6

انتهى المسح الحالي، إما لأن المدة المُهيّأة انقضت أو لأنه تم استدعاء gap_scan(None).

()

_IRQ_PERIPHERAL_CONNECT

7

نجح استدعاء سابق لـ gap_connect.

(conn_handle, addr_type, addr)

_IRQ_PERIPHERAL_DISCONNECT

8

انقطع اتصال جهاز طرفي (peripheral) كان متصلاً.

(conn_handle, addr_type, addr)

_IRQ_GATTC_SERVICE_RESULT

9

تم العثور على خدمة واحدة بواسطة gattc_discover_services.

(conn_handle, start_handle, end_handle, uuid)

_IRQ_GATTC_SERVICE_DONE

10

انتهى اكتشاف الخدمات.

(conn_handle, status)

_IRQ_GATTC_CHARACTERISTIC_RESULT

11

تم العثور على خاصية واحدة بواسطة gattc_discover_characteristics.

(conn_handle, end_handle, value_handle, properties, uuid)

_IRQ_GATTC_CHARACTERISTIC_DONE

12

انتهى اكتشاف الخصائص.

(conn_handle, status)

_IRQ_GATTC_DESCRIPTOR_RESULT

13

تم العثور على واصف واحد بواسطة gattc_discover_descriptors.

(conn_handle, dsc_handle, uuid)

_IRQ_GATTC_DESCRIPTOR_DONE

14

انتهى اكتشاف الواصفات.

(conn_handle, status)

_IRQ_GATTC_READ_RESULT

15

أعاد استدعاء سابق لـ gattc_read بيانات.

(conn_handle, value_handle, char_data)

_IRQ_GATTC_READ_DONE

16

انتهى استدعاء سابق لـ gattc_read.

(conn_handle, value_handle, status)

_IRQ_GATTC_WRITE_DONE

17

تم الإقرار باستلام استدعاء سابق لـ gattc_write.

(conn_handle, value_handle, status)

_IRQ_GATTC_NOTIFY

18

أرسل خادم بعيد إشعاراً (غير مُقَر باستلامه).

(conn_handle, value_handle, notify_data)

_IRQ_GATTC_INDICATE

19

أرسل خادم بعيد دلالة (indication) (مُقَر باستلامها).

(conn_handle, value_handle, notify_data)

_IRQ_GATTS_INDICATE_DONE

20

تم الإقرار باستلام دلالة (indication) أُرسلت سابقاً من قِبل العميل (أو انتهت مهلتها).

(conn_handle, value_handle, status)

_IRQ_MTU_EXCHANGED

21

اكتمل تبادل ATT MTU (بمبادرة من أي من الطرفين).

(conn_handle, mtu)

_IRQ_L2CAP_ACCEPT

22

طلب جهاز بعيد إنشاء اتصال L2CAP على معرّف PSM يستمع عليه هذا الجهاز. أرجِع عدداً صحيحاً غير صفري للرفض، أو 0 / None للقبول.

(conn_handle, cid, psm, our_mtu, peer_mtu)

_IRQ_L2CAP_CONNECT

23

تم إنشاء قناة L2CAP الآن، إما بقبول طلب وارد أو بإكمال استدعاء صادر لـ l2cap_connect.

(conn_handle, cid, psm, our_mtu, peer_mtu)

_IRQ_L2CAP_DISCONNECT

24

انقطع اتصال قناة L2CAP. تكون قيمة status هي 0 لانقطاع نظيف، أو غير صفرية إذا فشلت محاولة اتصال صادرة.

(conn_handle, cid, psm, status)

_IRQ_L2CAP_RECV

25

وصلت بيانات على قناة L2CAP. استدعِ l2cap_recvinto لقراءتها.

(conn_handle, cid)

_IRQ_L2CAP_SEND_READY

26

تم تفريغ استدعاء سابق لـ l2cap_send كان قد أعاد False، والقناة جاهزة من جديد. قيمة status غير الصفرية تعني أن مخزن الإرسال المؤقت قد فاض ويجب على التطبيق إعادة إرسال البيانات.

(conn_handle, cid, status)

_IRQ_CONNECTION_UPDATE

27

حدّث الجهاز البعيد معاملات الاتصال (الفاصل الزمني، وزمن الاستجابة، ومهلة الإشراف).

(conn_handle, conn_interval, conn_latency, supervision_timeout, status)

_IRQ_ENCRYPTION_UPDATE

28

تغيّرت حالة التشفير لاتصال ما، عادةً بعد اكتمال الإقران أو الربط.

(conn_handle, encrypted, authenticated, bonded, key_size)

_IRQ_GET_SECRET

29

يطلب المكدس سرّ ربط (bonding secret) مخزّن. إذا كانت قيمة key هي None، فأرجِع القيمة المخزّنة رقم index من النوع sec_type؛ وإلا فأرجِع القيمة المرتبطة بـ (sec_type, key) المعطاة. أرجِع None إذا لم يكن هناك شيء مخزّن.

(sec_type, index, key)

_IRQ_SET_SECRET

30

يطلب المكدس من التطبيق حفظ سرّ ربط (bonding secret) بشكل دائم. أرجِع True بمجرد تخزينه.

(sec_type, key, value)

_IRQ_PASSKEY_ACTION

31

إجراء يتعلق بمفتاح المرور (passkey) مطلوب كجزء من الإقران. استجِب باستخدام gap_passkey؛ راجع جدول إجراءات مفتاح المرور أدناه للاطلاع على الإجراءات الممكنة.

(conn_handle, action, passkey)

بالنسبة للحدث _IRQ_GATTS_READ_REQUEST، رموز الإرجاع المتاحة هي:

الثابت

القيمة

المعنى

_GATTS_NO_ERROR

0x00

قبول القراءة.

_GATTS_ERROR_READ_NOT_PERMITTED

0x02

القراءة غير مسموح بها.

_GATTS_ERROR_WRITE_NOT_PERMITTED

0x03

الكتابة غير مسموح بها.

_GATTS_ERROR_INSUFFICIENT_AUTHENTICATION

0x05

العميل غير مُصادَق عليه.

_GATTS_ERROR_INSUFFICIENT_AUTHORIZATION

0x08

العميل غير مُخوَّل.

_GATTS_ERROR_INSUFFICIENT_ENCRYPTION

0x0f

الرابط غير مشفّر.

بالنسبة للحدث _IRQ_PASSKEY_ACTION، الإجراءات المتاحة هي:

الثابت

القيمة

المعنى

_PASSKEY_ACTION_NONE

0

لا يلزم أي إجراء.

_PASSKEY_ACTION_INPUT

2

مطالبة المستخدم بإدخال مفتاح المرور المعروض على الجهاز البعيد.

_PASSKEY_ACTION_DISPLAY

3

عرض مفتاح مرور مكوّن من 6 أرقام ليُدخله الجهاز البعيد.

_PASSKEY_ACTION_NUMERIC_COMPARISON

4

تأكيد أن مفتاح المرور يطابق المفتاح المعروض على الجهاز البعيد.

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

دور Broadcaster (المُعلِن)

gap_advertise(interval_us: int | None, adv_data: bytes | None = None, *, resp_data: bytes | None = None, connectable: bool = True) None

يبدأ الإعلان بالفاصل الزمني المحدد (بالميكروثانية). سيُقرَّب هذا الفاصل الزمني نزولاً إلى أقرب مضاعف لـ 625us. لإيقاف الإعلان، اضبط interval_us على None.

يمكن أن تكون adv_data وresp_data من أي نوع يطبّق بروتوكول المخزن المؤقت (buffer protocol) (مثل bytes وbytearray وstr). تُضمَّن adv_data في كل عمليات البث، وتُرسَل resp_data رداً على مسح نشط.

ملاحظة: إذا كانت adv_data (أو resp_data) هي None، فستُعاد عندئذٍ استخدام البيانات المُمرَّرة إلى الاستدعاء السابق لـ gap_advertise. وهذا يتيح للمُعلِن استئناف الإعلان باستخدام gap_advertise(interval_us) فقط. ولمسح حمولة الإعلان مرّر قيمة bytes فارغة، أي b''.

دور Observer (الماسح)

gap_scan(duration_ms: int | None, interval_us: int = 1280000, window_us: int = 11250, active: bool = False, /) None

يُجري عملية مسح تستمر للمدة المحددة (بالملي ثانية).

للمسح إلى أجل غير مسمى، اضبط duration_ms على 0.

لإيقاف المسح، اضبط duration_ms على None.

استخدم interval_us وwindow_us لتهيئة دورة العمل (duty cycle) اختيارياً. سيعمل الماسح لمدة window_us ميكروثانية كل interval_us ميكروثانية لإجمالي duration_ms ملي ثانية. الفاصل الزمني والنافذة الافتراضيان هما 1.28 ثانية و11.25 ملي ثانية على التوالي (المسح في الخلفية).

لكل نتيجة مسح، سيُطلَق الحدث _IRQ_SCAN_RESULT مع بيانات الحدث (addr_type, addr, adv_type, rssi, adv_data).

تشير قيم addr_type إلى عناوين عامة (public) أو عشوائية (random):

القيمة

الاسم

المعنى

0x00

PUBLIC

عنوان جهاز عام (public).

0x01

RANDOM

عنوان عشوائي (إما ثابت أو RPA أو NRPA؛ ويُرمَّز النوع داخل العنوان نفسه).

تقابل قيم adv_type مواصفة Bluetooth:

القيمة

الاسم

المعنى

0x00

ADV_IND

إعلان غير موجّه قابل للاتصال والمسح.

0x01

ADV_DIRECT_IND

إعلان موجّه قابل للاتصال.

0x02

ADV_SCAN_IND

إعلان غير موجّه قابل للمسح.

0x03

ADV_NONCONN_IND

إعلان غير موجّه وغير قابل للاتصال.

0x04

SCAN_RSP

رد المسح (scan response).

يمكن ضبط active على True إذا كنت ترغب في استقبال ردود المسح في النتائج.

عند إيقاف المسح (إما بسبب انتهاء المدة أو عند إيقافه صراحةً)، سيُطلَق الحدث _IRQ_SCAN_DONE.

دور Central

يمكن لجهاز central أن يتصل بالأجهزة الطرفية (peripherals) التي اكتشفها باستخدام دور observer (راجع gap_scan) أو بعنوان معروف.

gap_connect(addr_type: int | None, addr: bytes | None = None, scan_duration_ms: int = 2000, min_conn_interval_us: int | None = None, max_conn_interval_us: int | None = None, /) None

الاتصال بجهاز طرفي (peripheral).

راجع gap_scan للاطلاع على تفاصيل أنواع العناوين.

لإلغاء محاولة اتصال معلّقة مبكراً، استدعِ gap_connect(None).

عند النجاح، سيُطلَق الحدث _IRQ_PERIPHERAL_CONNECT. وعند إلغاء محاولة اتصال، سيُطلَق الحدث _IRQ_PERIPHERAL_DISCONNECT.

سينتظر الجهاز حتى scan_duration_ms لاستقبال حمولة إعلان من الجهاز.

يمكن تهيئة فاصل الاتصال الزمني بالميكروثانية باستخدام min_conn_interval_us أو max_conn_interval_us أو كليهما. وإلا فسيُختار فاصل زمني افتراضي، عادةً بين 30000 و50000 ميكروثانية. الفاصل الزمني الأقصر سيزيد الإنتاجية، على حساب استهلاك الطاقة.

دور Peripheral

يُتوقع من الجهاز الطرفي (peripheral) أن يرسل إعلانات قابلة للاتصال (راجع gap_advertise). وعادةً ما يعمل كخادم GATT، بعد أن يكون قد سجّل أولاً الخدمات والخصائص باستخدام gatts_register_services.

عندما يتصل جهاز central، سيُطلَق الحدث _IRQ_CENTRAL_CONNECT.

دورا Central وPeripheral

gap_disconnect(conn_handle: int, /) bool

يقطع اتصال معرّف الاتصال (connection handle) المحدد. يمكن أن يكون هذا إما جهاز central متصلاً بهذا الجهاز (إن كان يعمل كجهاز طرفي) أو جهازاً طرفياً (peripheral) اتصل به هذا الجهاز سابقاً (إن كان يعمل كجهاز central).

عند النجاح، سيُطلَق الحدث _IRQ_PERIPHERAL_DISCONNECT أو _IRQ_CENTRAL_DISCONNECT.

يُرجع False إذا لم يكن معرّف الاتصال متصلاً، وTrue خلاف ذلك.

GATT Server

يملك خادم GATT مجموعة من الخدمات المسجّلة. قد تحتوي كل خدمة على خصائص، ولكل منها قيمة. كما يمكن أن تحتوي الخصائص على واصفات، ولهذه الواصفات بدورها قيم.

تُخزَّن هذه القيم محلياً، ويُوصل إليها عبر "معرّف القيمة" (value handle) الخاص بها والذي يُولَّد أثناء تسجيل الخدمة. كما يمكن قراءتها أو الكتابة إليها بواسطة جهاز عميل بعيد. إضافة إلى ذلك، يمكن للخادم أن "يُشعِر" (notify) عميلاً متصلاً بخاصية ما عبر معرّف الاتصال.

يمكن لجهاز في دور central أو peripheral أن يعمل كخادم GATT، إلا أنه في معظم الحالات سيكون من الأكثر شيوعاً أن يعمل الجهاز الطرفي (peripheral) كخادم.

للخصائص والواصفات حجم أقصى افتراضي يبلغ 20 بايت (قيمة ATT MTU الافتراضية البالغة 23 بايت ناقص ترويسة ATT بحجم 3 بايت؛ والتفاوض على قيمة MTU أكبر لا يرفع هذا الحد بذاته). أي شيء يكتبه العميل إليها سيُقتطع إلى هذا الطول. غير أن أي كتابة محلية ستزيد الحجم الأقصى، لذا إذا أردت السماح بكتابات أكبر من عميل إلى خاصية معيّنة، فاستخدم gatts_write بعد التسجيل. مثال gatts_write(char_handle, bytes(100)).

gatts_register_services(services_definition: Sequence[Sequence], /) Sequence[Sequence[int]]

يُهيّئ الخادم بالخدمات المحددة، مع استبدال أي خدمات موجودة.

services_definition هي قائمة من services، حيث تكون كل service صفّاً (tuple) من عنصرين يحتوي على معرّف UUID وقائمة من characteristics.

كل characteristic هي صفّ (tuple) من عنصرين أو ثلاثة عناصر يحتوي على معرّف UUID وقيمة flags واختيارياً قائمة من descriptors.

كل descriptor هي صفّ (tuple) من عنصرين يحتوي على معرّف UUID وقيمة flags.

قيمة flags هي مزيج بعملية OR على مستوى البتات من الأعلام المعرّفة أدناه. وهي تضبط كلاً من سلوك الخاصية (أو الواصف) ومتطلبات الأمان والخصوصية.

القيمة المُرجَعة هي قائمة (عنصر واحد لكل خدمة) من الصفوف (tuples) (كل عنصر هو معرّف قيمة). تُسطَّح معرّفات الخصائص والواصفات في الصفّ نفسه، بالترتيب الذي عُرّفت به.

يسجّل المثال التالي خدمتين (Heart Rate وNordic UART):

bt = bluetooth.BLE()
bt.active(True)

# Heart Rate service: one Heart Rate Measurement characteristic.
HR_SERVICE = (
    bluetooth.UUID(0x180D),
    (
        (bluetooth.UUID(0x2A37),
         bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY),
    ),
)

# Nordic UART service: a TX characteristic the client subscribes
# to for notifications, and an RX characteristic it writes to.
UART_SERVICE = (
    bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E'),
    (
        (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'),
         bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY),
        (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'),
         bluetooth.FLAG_WRITE),
    ),
)

((hr,), (tx, rx)) = bt.gatts_register_services(
    (HR_SERVICE, UART_SERVICE),
)

يمكن استخدام معرّفات القيم الثلاثة (hr وtx وrx) مع gatts_read وgatts_write وgatts_notify وgatts_indicate.

ملاحظة: يجب إيقاف الإعلان قبل تسجيل الخدمات.

الأعلام المتاحة للخصائص والواصفات هي:

الثابت

القيمة

المعنى

_FLAG_BROADCAST

0x0001

يمكن بث الخاصية.

_FLAG_READ

0x0002

يمكن للعميل قراءة القيمة.

_FLAG_WRITE_NO_RESPONSE

0x0004

يمكن للعميل الكتابة دون توقّع رد.

_FLAG_WRITE

0x0008

يمكن للعميل الكتابة مع رد مُقَر باستلامه.

_FLAG_NOTIFY

0x0010

يمكن للخادم إرسال إشعارات (غير مُقَر باستلامها).

_FLAG_INDICATE

0x0020

يمكن للخادم إرسال دلالات (indications) (مُقَر باستلامها).

_FLAG_AUTHENTICATED_SIGNED_WRITE

0x0040

يمكن للعميل إصدار كتابات موقّعة.

_FLAG_AUX_WRITE

0x0100

خصائص موسّعة: الكتابات المُصفّفة/الموثوقة مسموح بها.

_FLAG_READ_ENCRYPTED

0x0200

تتطلب القراءة رابطاً مشفّراً.

_FLAG_READ_AUTHENTICATED

0x0400

تتطلب القراءة رابطاً موثّقاً (محمياً من هجمات MITM).

_FLAG_READ_AUTHORIZED

0x0800

تتطلب القراءة تخويلاً على مستوى التطبيق.

_FLAG_WRITE_ENCRYPTED

0x1000

تتطلب الكتابة رابطاً مشفّراً.

_FLAG_WRITE_AUTHENTICATED

0x2000

تتطلب الكتابة رابطاً موثّقاً (محمياً من هجمات MITM).

_FLAG_WRITE_AUTHORIZED

0x4000

تتطلب الكتابة تخويلاً على مستوى التطبيق.

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

gatts_read(value_handle: int, /) bytes

تقرأ القيمة المحلية لهذا المعرّف (والتي إما كُتبت بواسطة gatts_write أو بواسطة عميل بعيد).

gatts_write(value_handle: int, data: bytes, send_update: bool = False, /) None

تكتب القيمة المحلية لهذا المعرّف، والتي يمكن للعميل قراءتها.

إذا كانت قيمة send_update هي True، فسيُشعَر (notify) أي عملاء مشتركين (أو ستُرسل لهم دلالة indication، حسب ما اشتركوا فيه والعمليات التي تدعمها الخاصية) بهذه الكتابة.

gatts_notify(conn_handle: int, value_handle: int, data: bytes | None = None, /) None

يرسل طلب إشعار (notification) إلى عميل متصل.

إذا كانت قيمة data هي None (القيمة الافتراضية)، فستُرسل القيمة المحلية الحالية (كما ضُبطت بواسطة gatts_write).

وإلا، إذا لم تكن قيمة data هي None، فستُرسل تلك القيمة إلى العميل كجزء من الإشعار. ولن تُعدَّل القيمة المحلية.

ملاحظة: سيُرسل الإشعار بصرف النظر عن حالة اشتراك العميل في هذه الخاصية.

gatts_indicate(conn_handle: int, value_handle: int, data: bytes | None = None, /) None

يرسل طلب دلالة (indication) إلى عميل متصل.

إذا كانت قيمة data هي None (القيمة الافتراضية)، فستُرسل القيمة المحلية الحالية (كما ضُبطت بواسطة gatts_write).

وإلا، إذا لم تكن قيمة data هي None، فستُرسل تلك القيمة إلى العميل كجزء من الدلالة (indication). ولن تُعدَّل القيمة المحلية.

عند الإقرار بالاستلام (أو عند الفشل، مثل انتهاء المهلة)، سيُطلَق الحدث _IRQ_GATTS_INDICATE_DONE.

ملاحظة: ستُرسل الدلالة (indication) بصرف النظر عن حالة اشتراك العميل في هذه الخاصية.

gatts_set_buffer(value_handle: int, len: int, append: bool = False, /) None

يضبط حجم المخزن المؤقت الداخلي لقيمة ما بالبايت. سيحدّ هذا من أكبر كتابة ممكنة يمكن استقبالها. القيمة الافتراضية هي 20 بايت (قيمة ATT MTU الافتراضية البالغة 23 ناقص ترويسة ATT بحجم 3 بايت).

ضبط append على True سيجعل جميع الكتابات البعيدة تُلحَق بالقيمة الحالية بدلاً من استبدالها. ويمكن تخزين len بايت كحد أقصى مؤقتاً بهذه الطريقة. وعند استخدام gatts_read، ستُمسح القيمة بعد القراءة. هذه الميزة مفيدة عند تنفيذ شيء مثل خدمة Nordic UART.

GATT Client

يمكن لعميل GATT اكتشاف الخصائص الموجودة على خادم GATT بعيد وقراءتها/الكتابة إليها.

من الأكثر شيوعاً أن يعمل جهاز في دور central كعميل GATT، إلا أنه من الممكن أيضاً أن يعمل جهاز طرفي (peripheral) كعميل لاكتشاف معلومات حول جهاز central الذي اتصل به (مثلاً لقراءة اسم الجهاز من خدمة معلومات الجهاز).

gattc_discover_services(conn_handle: int, uuid: UUID | None = None, /) None

يستعلم من خادم متصل عن خدماته.

حدّد اختيارياً معرّف uuid لخدمة للاستعلام عن تلك الخدمة فقط.

لكل خدمة مكتشفة، سيُطلَق الحدث _IRQ_GATTC_SERVICE_RESULT، متبوعاً بـ _IRQ_GATTC_SERVICE_DONE عند الاكتمال.

gattc_discover_characteristics(conn_handle: int, start_handle: int, end_handle: int, uuid: UUID | None = None, /) None

يستعلم من خادم متصل عن الخصائص في النطاق المحدد.

حدّد اختيارياً معرّف uuid لخاصية للاستعلام عن تلك الخاصية فقط.

تمرير start_handle=1 وend_handle=0xffff يغطي كامل نطاق معرّفات سمات GATT، لذا فإن هذا المزيج يبحث فعلياً في كل خدمة على الجهاز البعيد.

لكل خاصية مكتشفة، سيُطلَق الحدث _IRQ_GATTC_CHARACTERISTIC_RESULT، متبوعاً بـ _IRQ_GATTC_CHARACTERISTIC_DONE عند الاكتمال.

gattc_discover_descriptors(conn_handle: int, start_handle: int, end_handle: int, /) None

يستعلم من خادم متصل عن الواصفات في النطاق المحدد.

لكل واصف مكتشف، سيُطلَق الحدث _IRQ_GATTC_DESCRIPTOR_RESULT، متبوعاً بـ _IRQ_GATTC_DESCRIPTOR_DONE عند الاكتمال.

gattc_read(conn_handle: int, value_handle: int, /) None

يصدر طلب قراءة بعيدة إلى خادم متصل لمعرّف الخاصية أو الواصف المحدد.

عندما تتوفر قيمة، سيُطلَق الحدث _IRQ_GATTC_READ_RESULT، متبوعاً بـ _IRQ_GATTC_READ_DONE عند الاكتمال.

gattc_write(conn_handle: int, value_handle: int, data: bytes, mode: int = 0, /) None

يصدر طلب كتابة بعيدة إلى خادم متصل لمعرّف الخاصية أو الواصف المحدد.

يحدد الوسيط mode سلوك الكتابة، والقيم المدعومة حالياً هي:

  • mode=0 (الافتراضي) هي كتابة بلا رد: ستُرسل الكتابة إلى الخادم البعيد لكن لن يُعاد أي تأكيد، ولن يُطلَق أي حدث.

  • mode=1 هي كتابة مع رد: يُطلب من الخادم البعيد إرسال رد/إقرار بأنه استلم البيانات.

إذا وُرد رد من الخادم البعيد، فسيُطلَق الحدث _IRQ_GATTC_WRITE_DONE.

gattc_exchange_mtu(conn_handle: int, /) None

يبدأ تبادل MTU مع خادم متصل، باستخدام قيمة MTU المُفضَّلة المضبوطة عبر BLE.config(mtu=value).

سيُطلَق الحدث _IRQ_MTU_EXCHANGED عند اكتمال تبادل MTU.

عادةً ما يبدأ تبادل MTU جهاز central؛ ويدعم NimBLE كلا الدورين.

قنوات L2CAP الموجهة بالاتصال (Connection-Oriented Channels)

تتيح هذه الميزة تبادل البيانات بطريقة شبيهة بالمقابس (sockets) بين جهازي BLE. بمجرد اتصال الجهازين عبر GAP، يمكن لأي من الجهازين أن يستمع لاتصال الآخر على معرّف PSM رقمي (Protocol/Service Multiplexer).

يمكن أن تكون قناة L2CAP واحدة فقط نشطة في أي وقت معيّن (أي لا يمكنك الاتصال أثناء الاستماع).

تُعرَّف قنوات L2CAP النشطة بواسطة معرّف الاتصال الذي أُنشئت عليه ومعرّف قناة CID (channel ID).

تملك القنوات الموجهة بالاتصال تحكماً مدمجاً في تدفق البيانات قائماً على الأرصدة (credit-based flow control). على عكس ATT، حيث تتفاوض الأجهزة على قيمة MTU مشتركة، يضبط كل من الجهاز المستمِع والجهاز المتصل قيمة MTU مستقلة تحدّ من أقصى كمية بيانات معلّقة يمكن للجهاز البعيد إرسالها قبل استهلاكها بالكامل في l2cap_recvinto.

l2cap_listen(psm: int, mtu: int, /) None

يبدأ الاستماع لطلبات قنوات L2CAP الواردة على معرّف psm المحدد مع ضبط قيمة MTU المحلية على mtu.

عندما يبادر جهاز بعيد بالاتصال، سيُطلَق الحدث _IRQ_L2CAP_ACCEPT، الذي يمنح الخادم المستمِع فرصة لرفض الاتصال الوارد (بإرجاع عدد صحيح غير صفري).

بمجرد قبول الاتصال، سيُطلَق الحدث _IRQ_L2CAP_CONNECT، مما يتيح للخادم الحصول على معرّف القناة (CID) وقيمتي MTU المحلية والبعيدة.

ملاحظة: ليس من الممكن حالياً إيقاف الاستماع.

l2cap_connect(conn_handle: int, psm: int, mtu: int, /) None

يتصل بنظير مستمِع على معرّف psm المحدد مع ضبط قيمة MTU المحلية على mtu.

عند نجاح الاتصال، سيُطلَق الحدث _IRQ_L2CAP_CONNECT، مما يتيح للعميل الحصول على معرّف القناة (CID) وقيمتي MTU المحلية والبعيدة (peer).

الاتصال غير الناجح سيُطلق الحدث _IRQ_L2CAP_DISCONNECT مع حالة غير صفرية.

l2cap_disconnect(conn_handle: int, cid: int, /) None

يقطع اتصال قناة L2CAP نشطة بمعرّف الاتصال conn_handle ومعرّف القناة cid المحددين.

l2cap_send(conn_handle: int, cid: int, buf: bytes, /) bool

يرسل المخزن المؤقت buf المحدد (والذي يجب أن يدعم بروتوكول المخزن المؤقت) على قناة L2CAP المعرّفة بـ conn_handle وcid.

يجب أن يحقق المخزن المؤقت كلا الحدّين: يجب ألا يتجاوز قيمة MTU البعيدة (peer)، ويجب ألا يتجاوز ضعف قيمة MTU المحلية.

سيُرجع هذا False إذا كانت القناة الآن "متوقفة" (stalled)، مما يعني أنه يجب عدم استدعاء l2cap_send مرة أخرى حتى يُستقبل الحدث _IRQ_L2CAP_SEND_READY (والذي سيحدث عندما يمنح الجهاز البعيد مزيداً من الأرصدة، عادةً بعد أن يكون قد استقبل البيانات وعالجها).

l2cap_recvinto(conn_handle: int, cid: int, buf: Any | None, /) int

يستقبل بيانات من conn_handle وcid المحددين إلى المخزن المؤقت buf المقدَّم (والذي يجب أن يدعم بروتوكول المخزن المؤقت، مثل bytearray أو memoryview).

يُرجع عدد البايتات المقروءة من القناة.

إذا كانت قيمة buf هي None، فإنه يُرجع عدد البايتات المتاحة.

ملاحظة: بعد استقبال الحدث _IRQ_L2CAP_RECV، ينبغي للتطبيق أن يستمر في استدعاء l2cap_recvinto حتى لا تبقى بايتات متاحة في مخزن الاستقبال المؤقت (عادةً حتى حجم قيمة MTU البعيدة (peer)).

إلى أن يصبح مخزن الاستقبال المؤقت فارغاً، لن يُمنح الجهاز البعيد مزيداً من أرصدة القناة ولن يتمكن من إرسال أي بيانات إضافية.

الإقران والربط (Pairing and Bonding)

يتيح الإقران تشفير الاتصال وتوثيقه عبر تبادل الأسرار (مع حماية اختيارية من هجمات MITM عبر توثيق مفتاح المرور passkey).

الربط (Bonding) هو عملية تخزين تلك الأسرار في وحدة تخزين غير متطايرة. عندما يكون الجهاز مرتبطاً، يصبح قادراً على حلّ عنوان خاص قابل للحل (RPA) من جهاز آخر بناءً على مفتاح حلّ الهوية (IRK) المخزّن. ولدعم الربط، يجب على التطبيق تنفيذ الحدثين _IRQ_GET_SECRET و_IRQ_SET_SECRET.

gap_pair(conn_handle: int, /) None

يبدأ الإقران مع الجهاز البعيد.

قبل استدعاء هذا، تأكد من ضبط خيارات التهيئة io وmitm وle_secure وbond (عبر config).

عند نجاح الإقران، سيُطلَق الحدث _IRQ_ENCRYPTION_UPDATE.

gap_passkey(conn_handle: int, action: int, passkey: int, /) None

يستجيب لحدث _IRQ_PASSKEY_ACTION لمعرّف الاتصال conn_handle والإجراء action المحددين. يعتمد معنى passkey على action (والذي يعتمد بدوره على قدرة الإدخال/الإخراج (I/O) المُهيّأة):

الإجراء

استجابة passkey المطلوبة

_PASSKEY_ACTION_INPUT

مفتاح المرور الذي يقرأه المستخدم من الجهاز البعيد.

_PASSKEY_ACTION_DISPLAY

مفتاح مرور عشوائي مكوّن من 6 أرقام مُولَّد محلياً ومعروض للمستخدم.

_PASSKEY_ACTION_NUMERIC_COMPARISON

1 لقبول مفتاح المرور المعروض في الحدث _IRQ_PASSKEY_ACTION، أو 0 لإلغاء الإقران.

class UUID

class bluetooth.UUID(value: int | bytes | str, /)

ينشئ نسخة UUID بالقيمة value المحددة. يستخدم Bluetooth ثلاثة أعراض لـ UUID؛ ويقبل UUID أياً منها:

عرض UUID

أنواع value المقبولة

مثال

16-bit

int أو مخزن مؤقت بحجم 2 بايت (little-endian)

UUID(0x2908) أو UUID(b'\x08\x29')

32-bit

مخزن مؤقت بحجم 4 بايت (little-endian)

UUID(b'\x08\x29\x00\x00')

128-bit

مخزن مؤقت بحجم 16 بايت أو سلسلة نصية مفصولة بشرطات

UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')

عادةً ما تكون معرّفات UUID بعرض 16-bit و32-bit معرّفات مخصّصة من SIG (راجع أرقام Bluetooth المخصّصة)؛ أما معرّفات UUID بعرض 128-bit فتكون عادةً معرّفة من قِبل المورّد.