bluetooth --- Bluetooth ระดับต่ำ

โมดูลนี้มอบอินเทอร์เฟซสำหรับควบคุม Bluetooth บนบอร์ด รองรับ Bluetooth Low Energy (BLE) ในบทบาท Central, Peripheral, Broadcaster และ Observer รวมถึง GATT Server, GATT Client และช่องทาง L2CAP แบบ connection-oriented อุปกรณ์หนึ่งเครื่องสามารถทำงานได้หลายบทบาทพร้อมกัน และยังรองรับการจับคู่ (Pairing) และการผูกอุปกรณ์ (Bonding) อีกด้วย

API นี้ออกแบบให้สอดคล้องกับโปรโตคอล Bluetooth ระดับต่ำ และทำหน้าที่เป็นส่วนประกอบพื้นฐานสำหรับสร้างนามธรรมในระดับที่สูงขึ้น เช่น ประเภทอุปกรณ์เฉพาะทาง

Tip

สำหรับแอปพลิเคชันส่วนใหญ่ แนะนำให้ใช้ไลบรารี aioble ระดับสูงกว่า ซึ่งให้ wrapper แบบ asyncio รอบโมดูลนี้ ดูรายละเอียดได้ที่ aioble --- Async BLE

class BLE

class bluetooth.BLE

คืนค่าออบเจกต์ BLE เดี่ยว (singleton)

การกำหนดค่า

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

เปลี่ยนสถานะการทำงานของวิทยุ BLE (ไม่บังคับ) และคืนค่าสถานะปัจจุบัน

ต้องเปิดใช้งานวิทยุก่อนจึงจะเรียกใช้เมธอดอื่น ๆ ในคลาสนี้ได้

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

อ่านหรือตั้งค่าคอนฟิกของอินเทอร์เฟซ BLE หากต้องการอ่านค่า ให้ระบุชื่อพารามิเตอร์เป็นสตริง และสอบถามได้ครั้งละหนึ่งพารามิเตอร์เท่านั้น หากต้องการตั้งค่า ให้ใช้ syntax แบบ keyword และสามารถตั้งค่าได้หลายพารามิเตอร์พร้อมกัน

ค่าที่รองรับในปัจจุบัน ได้แก่:

  • 'mac': ที่อยู่ปัจจุบันที่ใช้งานอยู่ ขึ้นอยู่กับโหมดที่อยู่ปัจจุบัน จะคืนค่าเป็น tuple (addr_type, addr)

    ดูรายละเอียดเกี่ยวกับประเภทที่อยู่ได้ที่ gap_scan

    สามารถสอบถามค่านี้ได้เฉพาะเมื่ออินเทอร์เฟซกำลังทำงานอยู่เท่านั้น

  • 'addr_mode': ตั้งค่าโหมดที่อยู่ ค่าที่รองรับ ได้แก่:

    ค่า

    ชื่อ

    พฤติกรรม

    0x00

    PUBLIC

    ใช้ที่อยู่สาธารณะของตัวควบคุม

    0x01

    RANDOM

    ใช้ที่อยู่สถิตที่สร้างขึ้น

    0x02

    RPA

    ใช้ที่อยู่ส่วนตัวแบบแก้ไขได้ (Resolvable Private Address)

    0x03

    NRPA

    ใช้ที่อยู่ส่วนตัวแบบแก้ไขไม่ได้ (Non-Resolvable Private Address)

    โดยค่าเริ่มต้น อินเทอร์เฟซจะใช้ที่อยู่ PUBLIC หากมี มิฉะนั้นจะใช้ที่อยู่ RANDOM

  • 'gap_name': อ่าน/ตั้งค่าชื่ออุปกรณ์ GAP ที่ใช้โดยบริการ Generic Access (UUID 0x1800) คุณลักษณะ Device Name (UUID 0x2a00) สามารถตั้งค่าได้ตลอดเวลาและเปลี่ยนแปลงได้หลายครั้ง

  • 'rxbuf': อ่าน/ตั้งค่าขนาดเป็นไบต์ของบัฟเฟอร์ภายในที่ใช้เก็บอีเวนต์ที่รับเข้ามา บัฟเฟอร์นี้ใช้ร่วมกันทั้งไดรเวอร์ BLE และรองรับข้อมูลที่รับเข้าจากทุกอีเวนต์ รวมถึงทุก characteristic การเพิ่มขนาดจะช่วยรองรับข้อมูลที่รับเข้าแบบพุ่งกระชากได้ดีขึ้น (เช่น ผลการสแกน) และรองรับค่า characteristic ขนาดใหญ่ขึ้นได้

  • '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

    แสดงผลพร้อมรับข้อมูล yes/no

    _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

ลงทะเบียน callback สำหรับอีเวนต์จาก BLE stack โดย handler รับสองอาร์กิวเมนต์ คือ event (ซึ่งจะเป็นหนึ่งในโค้ดด้านล่าง) และ data (ซึ่งเป็น tuple ของค่าที่เฉพาะเจาะจงสำหรับแต่ละอีเวนต์)

หมายเหตุ: เพื่อเพิ่มประสิทธิภาพและลดการจัดสรรหน่วยความจำที่ไม่จำเป็น รายการ addr, adv_data, char_data, notify_data และ uuid ใน tuple จะเป็น instance ของ memoryview แบบอ่านอย่างเดียวที่ชี้ไปยัง ringbuffer ภายในของ bluetooth และใช้ได้เฉพาะในระหว่างการเรียกใช้ฟังก์ชัน IRQ handler เท่านั้น หากโปรแกรมต้องการบันทึกค่าใดค่าหนึ่งเพื่อเข้าถึงหลังจาก IRQ handler คืนค่าแล้ว (เช่น โดยการบันทึกลงใน instance ของคลาสหรือตัวแปร global) จำเป็นต้องคัดลอกข้อมูลโดยใช้ bytes() หรือ bluetooth.UUID() ดังนี้:

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

ตัวอย่างเช่น IRQ handler สำหรับผลการสแกนอาจตรวจสอบ adv_data เพื่อตัดสินว่าเป็นอุปกรณ์ที่ถูกต้องหรือไม่ แล้วจึงคัดลอกข้อมูลที่อยู่เพื่อนำไปใช้งานที่อื่นในโปรแกรม และในการพิมพ์ข้อมูลจาก IRQ handler จำเป็นต้องใช้ print(bytes(addr))

handler มักจะกระจายการทำงานตามโค้ดอีเวนต์และแกะ tuple ข้อมูลของแต่ละอีเวนต์:

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
        ...

รายการโค้ดอีเวนต์ทุกตัว payload ที่ส่งมา และคำอธิบายสั้น ๆ แสดงไว้ด้านล่าง สำหรับอีเวนต์ที่กล่าวถึงฟิลด์ status นั้น status จะเป็น 0 เมื่อสำเร็จ และเป็นค่าที่ไม่เป็นศูนย์ที่เฉพาะเจาะจงกับ implementation เมื่อเกิดความผิดพลาด

ค่าคงที่

ค่า

อีเวนต์

Payload 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

Client ที่เชื่อมต่ออยู่ได้เขียนข้อมูลลงใน characteristic หรือ descriptor ในเครื่อง ใช้ gatts_read เพื่ออ่านค่าใหม่

(conn_handle, attr_handle)

_IRQ_GATTS_READ_REQUEST

4

Client ที่เชื่อมต่ออยู่ได้ออก read request ส่งคืนโค้ดข้อผิดพลาดที่ไม่ใช่ศูนย์จากตารางด้านล่างเพื่อปฏิเสธการอ่าน หรือ 0 / None เพื่อยอมรับ

(conn_handle, attr_handle)

_IRQ_SCAN_RESULT

5

ได้รับ advertising packet เดี่ยวในระหว่างการสแกนแบบ active

(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

พบ characteristic หนึ่งรายการโดย gattc_discover_characteristics

(conn_handle, end_handle, value_handle, properties, uuid)

_IRQ_GATTC_CHARACTERISTIC_DONE

12

การค้นหา characteristic เสร็จสิ้นแล้ว

(conn_handle, status)

_IRQ_GATTC_DESCRIPTOR_RESULT

13

พบ descriptor หนึ่งรายการโดย gattc_discover_descriptors

(conn_handle, dsc_handle, uuid)

_IRQ_GATTC_DESCRIPTOR_DONE

14

การค้นหา descriptor เสร็จสิ้นแล้ว

(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

เซิร์ฟเวอร์ระยะไกลได้ส่ง notification (แบบไม่มีการยืนยัน)

(conn_handle, value_handle, notify_data)

_IRQ_GATTC_INDICATE

19

เซิร์ฟเวอร์ระยะไกลได้ส่ง indication (แบบมีการยืนยัน)

(conn_handle, value_handle, notify_data)

_IRQ_GATTS_INDICATE_DONE

20

indication ที่ส่งออกไปก่อนหน้านี้ได้รับการยืนยันจาก client แล้ว (หรือหมดเวลา)

(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 ที่ไม่ใช่ศูนย์หมายความว่า transmit buffer ล้น และแอปพลิเคชันต้องส่งข้อมูลใหม่อีกครั้ง

(conn_handle, cid, status)

_IRQ_CONNECTION_UPDATE

27

อุปกรณ์ระยะไกลได้อัปเดตพารามิเตอร์การเชื่อมต่อ (interval, latency, supervision timeout)

(conn_handle, conn_interval, conn_latency, supervision_timeout, status)

_IRQ_ENCRYPTION_UPDATE

28

สถานะการเข้ารหัสของการเชื่อมต่อได้เปลี่ยนแปลง โดยทั่วไปหลังจากการจับคู่หรือ bonding เสร็จสิ้น

(conn_handle, encrypted, authenticated, bonded, key_size)

_IRQ_GET_SECRET

29

stack กำลังร้องขอความลับ bonding ที่จัดเก็บไว้ หาก key เป็น None ให้คืนค่าที่จัดเก็บไว้ลำดับที่ index ของ sec_type มิฉะนั้นคืนค่าที่เกี่ยวข้องกับ (sec_type, key) ที่กำหนด หากไม่มีข้อมูลจัดเก็บให้คืนค่า None

(sec_type, index, key)

_IRQ_SET_SECRET

30

stack กำลังขอให้แอปพลิเคชันบันทึกความลับ bonding ส่งคืน True เมื่อบันทึกแล้ว

(sec_type, key, value)

_IRQ_PASSKEY_ACTION

31

ต้องดำเนินการ passkey เป็นส่วนหนึ่งของการจับคู่ ตอบสนองโดยใช้ gap_passkey ดูตาราง passkey-action ด้านล่างสำหรับการดำเนินการที่เป็นไปได้

(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

Client ยังไม่ได้รับการยืนยันตัวตน

_GATTS_ERROR_INSUFFICIENT_AUTHORIZATION

0x08

Client ยังไม่ได้รับการอนุญาต

_GATTS_ERROR_INSUFFICIENT_ENCRYPTION

0x0f

การเชื่อมต่อยังไม่ได้เข้ารหัส

สำหรับอีเวนต์ _IRQ_PASSKEY_ACTION การดำเนินการที่รองรับ ได้แก่:

ค่าคงที่

ค่า

ความหมาย

_PASSKEY_ACTION_NONE

0

ไม่ต้องดำเนินการใด

_PASSKEY_ACTION_INPUT

2

แจ้งให้ผู้ใช้ป้อน passkey ที่แสดงบนอุปกรณ์ระยะไกล

_PASSKEY_ACTION_DISPLAY

3

แสดง passkey 6 หลักเพื่อให้อุปกรณ์ระยะไกลป้อน

_PASSKEY_ACTION_NUMERIC_COMPARISON

4

ยืนยันว่า passkey ตรงกับที่แสดงบนอุปกรณ์ระยะไกล

เพื่อประหยัดพื้นที่ใน firmware ค่าคงที่เหล่านี้ไม่ได้รวมอยู่ในโมดูล bluetooth ให้เพิ่มเฉพาะค่าที่ต้องการจากรายการด้านบนลงในโปรแกรมของคุณ

บทบาท Broadcaster (ผู้โฆษณา)

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

เริ่มต้นการโฆษณาด้วย interval ที่กำหนด (เป็นไมโครวินาที) โดย interval นี้จะถูกปัดลงเป็นทวีคูณของ 625us หยุดการโฆษณาโดยตั้งค่า interval_us เป็น None

adv_data และ resp_data สามารถเป็นประเภทใดก็ได้ที่รองรับ buffer protocol (เช่น bytes, bytearray, str) โดย adv_data จะรวมอยู่ใน broadcast ทุกครั้ง และ resp_data จะถูกส่งเป็นการตอบสนองต่อการสแกนแบบ active

หมายเหตุ: หาก adv_data (หรือ resp_data) เป็น None ข้อมูลที่ส่งให้กับการเรียก gap_advertise ครั้งก่อนจะถูกนำมาใช้ซ้ำ ซึ่งช่วยให้ broadcaster สามารถกลับมาโฆษณาด้วย gap_advertise(interval_us) เพียงอย่างเดียว หากต้องการล้าง advertising payload ให้ส่ง 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 มิลลิวินาที ค่าเริ่มต้นของ interval และ window คือ 1.28 วินาทีและ 11.25 มิลลิวินาทีตามลำดับ (การสแกนพื้นหลัง)

สำหรับผลการสแกนแต่ละครั้ง อีเวนต์ _IRQ_SCAN_RESULT จะถูกเรียกขึ้น โดยมีข้อมูลอีเวนต์ (addr_type, addr, adv_type, rssi, adv_data)

ค่า addr_type ระบุที่อยู่แบบ public หรือ random:

ค่า

ชื่อ

ความหมาย

0x00

PUBLIC

ที่อยู่อุปกรณ์แบบ public

0x01

RANDOM

ที่อยู่แบบ random (ไม่ว่าจะเป็นแบบ static, RPA หรือ NRPA โดยประเภทจะถูกเข้ารหัสในที่อยู่เอง)

ค่า adv_type สอดคล้องกับ Bluetooth Specification:

ค่า

ชื่อ

ความหมาย

0x00

ADV_IND

การโฆษณาแบบ connectable และ scannable undirected

0x01

ADV_DIRECT_IND

การโฆษณาแบบ connectable directed

0x02

ADV_SCAN_IND

การโฆษณาแบบ scannable undirected

0x03

ADV_NONCONN_IND

การโฆษณาแบบ non-connectable undirected

0x04

SCAN_RSP

Scan response

สามารถตั้งค่า active เป็น True หากต้องการรับ scan response ในผลลัพธ์

เมื่อการสแกนหยุดลง (ไม่ว่าจะเป็นเพราะระยะเวลาสิ้นสุดหรือหยุดอย่างชัดเจน) อีเวนต์ _IRQ_SCAN_DONE จะถูกเรียกขึ้น

บทบาท Central

อุปกรณ์ Central สามารถเชื่อมต่อกับ peripheral ที่ค้นพบผ่านบทบาท 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 จะถูกเรียกขึ้น

อุปกรณ์จะรอรับ advertising payload จากอุปกรณ์นานถึง scan_duration_ms

สามารถกำหนดค่า connection interval เป็นไมโครวินาทีโดยใช้ min_conn_interval_us และ/หรือ max_conn_interval_us มิฉะนั้นจะเลือก interval เริ่มต้น โดยทั่วไปอยู่ระหว่าง 30000 ถึง 50000 ไมโครวินาที Interval ที่สั้นลงจะเพิ่ม throughput แต่ใช้พลังงานมากขึ้น

บทบาท Peripheral

อุปกรณ์ peripheral คาดว่าจะส่ง connectable advertisement (ดู gap_advertise) โดยปกติจะทำหน้าที่เป็น GATT server โดยลงทะเบียนบริการและ characteristic ก่อนโดยใช้ gatts_register_services

เมื่อ central เชื่อมต่อ อีเวนต์ _IRQ_CENTRAL_CONNECT จะถูกเรียกขึ้น

บทบาท Central & Peripheral

gap_disconnect(conn_handle: int, /) bool

ตัดการเชื่อมต่อ connection handle ที่กำหนด ซึ่งอาจเป็น central ที่เชื่อมต่อกับอุปกรณ์นี้ (เมื่อทำหน้าที่เป็น peripheral) หรือ peripheral ที่อุปกรณ์นี้เชื่อมต่อก่อนหน้า (เมื่อทำหน้าที่เป็น central)

เมื่อสำเร็จ อีเวนต์ _IRQ_PERIPHERAL_DISCONNECT หรือ _IRQ_CENTRAL_DISCONNECT จะถูกเรียกขึ้น

คืนค่า False หาก connection handle ไม่ได้เชื่อมต่ออยู่ และ True ในกรณีอื่น

GATT Server

GATT server มีชุดบริการที่ลงทะเบียนไว้ แต่ละบริการอาจประกอบด้วย characteristic ซึ่งแต่ละตัวมีค่า characteristic ยังสามารถมี descriptor ซึ่งก็มีค่าเช่นกัน

ค่าเหล่านี้ถูกจัดเก็บไว้ในเครื่อง และเข้าถึงได้ผ่าน "value handle" ที่สร้างขึ้นระหว่างการลงทะเบียนบริการ นอกจากนี้ยังสามารถอ่านหรือเขียนโดยอุปกรณ์ client ระยะไกลได้ และ server ยังสามารถ "notify" characteristic ไปยัง client ที่เชื่อมต่ออยู่ผ่าน connection handle ได้อีกด้วย

อุปกรณ์ในบทบาท central หรือ peripheral ก็สามารถทำหน้าที่เป็น GATT server ได้ อย่างไรก็ตามในกรณีส่วนใหญ่อุปกรณ์ peripheral มักทำหน้าที่เป็น server มากกว่า

Characteristic และ descriptor มีขนาดสูงสุดเริ่มต้นที่ 20 ไบต์ (ATT MTU เริ่มต้น 23 ไบต์ลบด้วย ATT header 3 ไบต์ การเจรจา MTU ที่ใหญ่ขึ้นไม่ได้เพิ่มขีดจำกัดนี้โดยอัตโนมัติ) ข้อมูลใดที่ client เขียนจะถูกตัดให้อยู่ในความยาวนี้ อย่างไรก็ตาม การเขียนในเครื่องจะเพิ่มขนาดสูงสุด ดังนั้นหากต้องการอนุญาตให้ client เขียนข้อมูลขนาดใหญ่กว่านี้ไปยัง characteristic ที่กำหนด ให้ใช้ gatts_write หลังจากลงทะเบียน เช่น gatts_write(char_handle, bytes(100))

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

กำหนดค่า server ด้วยบริการที่ระบุ แทนที่บริการที่มีอยู่ทั้งหมด

services_definition คือรายการของ บริการ โดยแต่ละ บริการ เป็น tuple สองส่วนที่ประกอบด้วย UUID และรายการ characteristic

แต่ละ characteristic เป็น tuple สองหรือสามส่วนที่ประกอบด้วย UUID ค่า flags และรายการ descriptor (ไม่บังคับ)

แต่ละ descriptor เป็น tuple สองส่วนที่ประกอบด้วย UUID และค่า flags

flags คือการรวมกันแบบ bitwise-OR ของแฟล็กที่กำหนดด้านล่าง โดยกำหนดทั้งพฤติกรรมของ characteristic (หรือ descriptor) รวมถึงข้อกำหนดด้านความปลอดภัยและความเป็นส่วนตัว

ค่าที่คืนมาคือรายการ (หนึ่งส่วนต่อบริการ) ของ tuple (แต่ละส่วนเป็น value handle) โดย handle ของ characteristic และ descriptor จะถูกรวมลงใน tuple เดียวกันตามลำดับที่กำหนด

ตัวอย่างต่อไปนี้ลงทะเบียนสองบริการ (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),
)

value handle สามทัว (hr, tx, rx) สามารถใช้กับ gatts_read, gatts_write, gatts_notify และ gatts_indicate

หมายเหตุ: ต้องหยุด advertising ก่อนลงทะเบียนบริการ

แฟล็กที่รองรับสำหรับ characteristic และ descriptor ได้แก่:

ค่าคงที่

ค่า

ความหมาย

_FLAG_BROADCAST

0x0001

Characteristic อาจถูก broadcast ได้

_FLAG_READ

0x0002

Client อาจอ่านค่าได้

_FLAG_WRITE_NO_RESPONSE

0x0004

Client อาจเขียนโดยไม่ต้องรอการตอบกลับ

_FLAG_WRITE

0x0008

Client อาจเขียนพร้อมการตอบกลับที่มีการยืนยัน

_FLAG_NOTIFY

0x0010

Server อาจส่ง notification (แบบไม่มีการยืนยัน)

_FLAG_INDICATE

0x0020

Server อาจส่ง indication (แบบมีการยืนยัน)

_FLAG_AUTHENTICATED_SIGNED_WRITE

0x0040

Client อาจออก signed write ได้

_FLAG_AUX_WRITE

0x0100

Extended properties: อนุญาตให้ใช้ queued/reliable write ได้

_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

อ่านค่าในเครื่องสำหรับ handle นี้ (ซึ่งถูกเขียนโดย gatts_write หรือโดย client ระยะไกล)

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

เขียนค่าในเครื่องสำหรับ handle นี้ ซึ่ง client สามารถอ่านได้

หาก send_update เป็น True client ที่สมัครรับข้อมูลอยู่จะได้รับแจ้ง (หรือ indication ขึ้นอยู่กับสิ่งที่ client สมัครรับและการดำเนินการที่ characteristic รองรับ) เกี่ยวกับการเขียนนี้

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

ส่งคำขอ notification ไปยัง client ที่เชื่อมต่ออยู่

หาก data เป็น None (ค่าเริ่มต้น) ค่าในเครื่องปัจจุบัน (ที่ตั้งค่าโดย gatts_write) จะถูกส่ง

หาก data ไม่ใช่ None ค่านั้นจะถูกส่งไปยัง client เป็นส่วนหนึ่งของ notification โดยค่าในเครื่องจะไม่ถูกแก้ไข

หมายเหตุ: notification จะถูกส่งโดยไม่คำนึงถึงสถานะการสมัครรับ characteristic ของ client

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

ส่งคำขอ indication ไปยัง client ที่เชื่อมต่ออยู่

หาก data เป็น None (ค่าเริ่มต้น) ค่าในเครื่องปัจจุบัน (ที่ตั้งค่าโดย gatts_write) จะถูกส่ง

หาก data ไม่ใช่ None ค่านั้นจะถูกส่งไปยัง client เป็นส่วนหนึ่งของ indication โดยค่าในเครื่องจะไม่ถูกแก้ไข

เมื่อได้รับการยืนยัน (หรือเกิดความล้มเหลว เช่น timeout) อีเวนต์ _IRQ_GATTS_INDICATE_DONE จะถูกเรียกขึ้น

หมายเหตุ: indication จะถูกส่งโดยไม่คำนึงถึงสถานะการสมัครรับ characteristic ของ client

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

ตั้งค่าขนาดบัฟเฟอร์ภายในสำหรับค่าหนึ่งเป็นไบต์ ซึ่งจะจำกัดขนาดการเขียนสูงสุดที่รับได้ ค่าเริ่มต้นคือ 20 ไบต์ (ATT MTU เริ่มต้น 23 ลบด้วย ATT header 3 ไบต์)

การตั้งค่า append เป็น True จะทำให้การเขียนทั้งหมดจากระยะไกลต่อท้ายแทนที่จะแทนที่ค่าปัจจุบัน โดยบัฟเฟอร์ได้สูงสุด len ไบต์ด้วยวิธีนี้ เมื่อใช้ gatts_read ค่าจะถูกล้างหลังจากอ่าน ฟีเจอร์นี้มีประโยชน์เมื่อ implement บางอย่าง เช่น Nordic UART Service

GATT Client

GATT client สามารถค้นพบและอ่าน/เขียน characteristic บน GATT server ระยะไกลได้

โดยทั่วไปอุปกรณ์ที่มีบทบาท central จะทำหน้าที่เป็น GATT client อย่างไรก็ตาม peripheral ก็สามารถทำหน้าที่เป็น client ได้เช่นกัน เพื่อค้นหาข้อมูลเกี่ยวกับ central ที่เชื่อมต่ออยู่ (เช่น การอ่านชื่ออุปกรณ์จากบริการข้อมูลอุปกรณ์)

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

สอบถาม server ที่เชื่อมต่ออยู่เพื่อค้นหาบริการ

ระบุ 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

สอบถาม server ที่เชื่อมต่ออยู่เพื่อค้นหา characteristic ในช่วงที่กำหนด

ระบุ uuid ของ characteristic เพื่อสอบถามเฉพาะ characteristic นั้นได้ (ไม่บังคับ)

การส่ง start_handle=1 และ end_handle=0xffff ครอบคลุมช่วง GATT attribute-handle ทั้งหมด ดังนั้นการรวมกันนี้จะค้นหาทุกบริการบนอุปกรณ์ระยะไกลอย่างมีประสิทธิภาพ

สำหรับแต่ละ characteristic ที่ค้นพบ อีเวนต์ _IRQ_GATTC_CHARACTERISTIC_RESULT จะถูกเรียกขึ้น ตามด้วย _IRQ_GATTC_CHARACTERISTIC_DONE เมื่อเสร็จสมบูรณ์

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

สอบถาม server ที่เชื่อมต่ออยู่เพื่อค้นหา descriptor ในช่วงที่กำหนด

สำหรับแต่ละ descriptor ที่ค้นพบ อีเวนต์ _IRQ_GATTC_DESCRIPTOR_RESULT จะถูกเรียกขึ้น ตามด้วย _IRQ_GATTC_DESCRIPTOR_DONE เมื่อเสร็จสมบูรณ์

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

ออก remote read ไปยัง server ที่เชื่อมต่ออยู่สำหรับ characteristic หรือ descriptor handle ที่กำหนด

เมื่อค่าพร้อมใช้งาน อีเวนต์ _IRQ_GATTC_READ_RESULT จะถูกเรียกขึ้น ตามด้วย _IRQ_GATTC_READ_DONE เมื่อเสร็จสมบูรณ์

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

ออก remote write ไปยัง server ที่เชื่อมต่ออยู่สำหรับ characteristic หรือ descriptor handle ที่กำหนด

อาร์กิวเมนต์ mode ระบุพฤติกรรมการเขียน โดยค่าที่รองรับในปัจจุบัน ได้แก่:

  • mode=0 (ค่าเริ่มต้น) คือ write-without-response: การเขียนจะถูกส่งไปยัง server ระยะไกลแต่จะไม่มีการยืนยันส่งกลับมา และจะไม่มีการเรียกอีเวนต์

  • mode=1 คือ write-with-response: server ระยะไกลจะถูกขอให้ส่งการตอบกลับ/การยืนยันว่าได้รับข้อมูลแล้ว

หาก server ระยะไกลส่งการตอบกลับมา อีเวนต์ _IRQ_GATTC_WRITE_DONE จะถูกเรียกขึ้น

gattc_exchange_mtu(conn_handle: int, /) None

เริ่มต้นการแลกเปลี่ยน MTU กับ server ที่เชื่อมต่ออยู่ โดยใช้ค่า MTU ที่ต้องการซึ่งตั้งค่าด้วย BLE.config(mtu=value)

อีเวนต์ _IRQ_MTU_EXCHANGED จะถูกเรียกขึ้นเมื่อการแลกเปลี่ยน MTU เสร็จสมบูรณ์

โดยทั่วไป central จะเป็นผู้เริ่มต้นการแลกเปลี่ยน MTU โดย NimBLE รองรับทั้งสองบทบาท

ช่อง L2CAP แบบ Connection-Oriented

ฟีเจอร์นี้ช่วยให้แลกเปลี่ยนข้อมูลแบบ socket ระหว่างอุปกรณ์ BLE สองเครื่องได้ เมื่ออุปกรณ์เชื่อมต่อกันผ่าน GAP แล้ว อุปกรณ์ใดก็ได้สามารถฟังอีกฝ่ายเพื่อเชื่อมต่อบน PSM (Protocol/Service Multiplexer) ที่กำหนด

สามารถใช้งาน L2CAP channel ได้เพียงหนึ่งช่องในเวลาหนึ่ง (ไม่สามารถเชื่อมต่อขณะกำลังฟังอยู่)

L2CAP channel ที่ใช้งานอยู่จะถูกระบุโดย connection handle ที่ถูกสร้างขึ้นและ CID (channel ID)

ช่องทาง connection-oriented มีการควบคุมการไหลแบบ credit-based ในตัว ต่างจาก ATT ที่อุปกรณ์เจรจา MTU ร่วมกัน อุปกรณ์ฟังและอุปกรณ์เชื่อมต่อต่างตั้งค่า MTU อิสระของตนเอง ซึ่งจำกัดจำนวนข้อมูลค้างส่งสูงสุดที่อุปกรณ์ระยะไกลสามารถส่งก่อนที่จะถูกบริโภคใน l2cap_recvinto

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

เริ่มฟังคำขอ L2CAP channel ขาเข้าบน psm ที่กำหนด โดยตั้งค่า local MTU เป็น mtu

เมื่ออุปกรณ์ระยะไกลเริ่มต้นการเชื่อมต่อ อีเวนต์ _IRQ_L2CAP_ACCEPT จะถูกเรียกขึ้น ซึ่งให้โอกาส server ที่กำลังฟังปฏิเสธการเชื่อมต่อขาเข้า (โดยคืนค่าจำนวนเต็มที่ไม่ใช่ศูนย์)

เมื่อการเชื่อมต่อได้รับการยอมรับ อีเวนต์ _IRQ_L2CAP_CONNECT จะถูกเรียกขึ้น ซึ่งให้ server ได้รับ CID และค่า local และ remote MTU

หมายเหตุ: ขณะนี้ยังไม่สามารถหยุดการฟังได้

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

เชื่อมต่อกับ peer ที่กำลังฟังบน psm ที่กำหนด โดยตั้งค่า local MTU เป็น mtu

เมื่อเชื่อมต่อสำเร็จ อีเวนต์ _IRQ_L2CAP_CONNECT จะถูกเรียกขึ้น ให้ client ได้รับ CID และค่า local และ remote (peer) MTU

การเชื่อมต่อที่ไม่สำเร็จจะเรียกอีเวนต์ _IRQ_L2CAP_DISCONNECT พร้อม status ที่ไม่ใช่ศูนย์

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

ตัดการเชื่อมต่อ L2CAP channel ที่ใช้งานอยู่ด้วย conn_handle และ cid ที่กำหนด

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

ส่ง buf ที่กำหนด (ซึ่งต้องรองรับ buffer protocol) บน L2CAP channel ที่ระบุโดย conn_handle และ cid

บัฟเฟอร์ต้องปฏิบัติตามข้อจำกัดทั้งสอง: ต้องไม่เกิน remote (peer) MTU และต้องไม่เกินสองเท่าของ local MTU

จะคืนค่า False หากช่อง "stalled" อยู่ ซึ่งหมายความว่าต้องไม่เรียก l2cap_send อีกจนกว่าจะได้รับอีเวนต์ _IRQ_L2CAP_SEND_READY (ซึ่งจะเกิดขึ้นเมื่ออุปกรณ์ระยะไกลให้ credit เพิ่มเติม โดยทั่วไปหลังจากได้รับและประมวลผลข้อมูล)

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

รับข้อมูลจาก conn_handle และ cid ที่กำหนดลงใน buf ที่ให้มา (ซึ่งต้องรองรับ buffer protocol เช่น bytearray หรือ memoryview)

คืนค่าจำนวนไบต์ที่อ่านจาก channel

หาก buf เป็น None จะคืนค่าจำนวนไบต์ที่พร้อมใช้งาน

หมายเหตุ: หลังจากได้รับอีเวนต์ _IRQ_L2CAP_RECV แอปพลิเคชันควรเรียก l2cap_recvinto ต่อไปจนกว่าไม่มีไบต์เหลืออยู่ใน receive buffer (โดยทั่วไปไม่เกินขนาดของ remote (peer) MTU)

จนกว่า receive buffer จะว่างเปล่า อุปกรณ์ระยะไกลจะไม่ได้รับ channel credit เพิ่มและไม่สามารถส่งข้อมูลเพิ่มเติมได้

การจับคู่และการผูกอุปกรณ์

การจับคู่ช่วยให้การเชื่อมต่อถูกเข้ารหัสและยืนยันตัวตนผ่านการแลกเปลี่ยนความลับ (พร้อมการป้องกัน MITM ผ่านการยืนยัน passkey ตามต้องการ)

Bonding คือกระบวนการจัดเก็บความลับเหล่านั้นลงใน non-volatile storage เมื่อ bonded แล้ว อุปกรณ์สามารถแก้ไข resolvable private address (RPA) จากอุปกรณ์อื่นได้โดยอ้างอิงจาก identity resolving key (IRK) ที่จัดเก็บไว้ เพื่อรองรับ bonding แอปพลิเคชันต้อง implement อีเวนต์ _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 ที่ผู้ใช้อ่านจากอุปกรณ์ระยะไกล

_PASSKEY_ACTION_DISPLAY

passkey 6 หลักแบบสุ่มที่สร้างในเครื่องและแสดงให้ผู้ใช้เห็น

_PASSKEY_ACTION_NUMERIC_COMPARISON

1 เพื่อยอมรับ passkey ที่แสดงในอีเวนต์ _IRQ_PASSKEY_ACTION หรือ 0 เพื่อยกเลิกการจับคู่

class UUID

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

สร้าง instance ของ UUID ด้วย value ที่กำหนด Bluetooth ใช้ UUID สามขนาด โดย UUID รองรับทั้งหมด:

ความกว้าง UUID

ประเภท value ที่รองรับ

ตัวอย่าง

16 บิต

int หรือบัฟเฟอร์ 2 ไบต์ (little-endian)

UUID(0x2908) หรือ UUID(b'\x08\x29')

32 บิต

บัฟเฟอร์ 4 ไบต์ (little-endian)

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

128 บิต

บัฟเฟอร์ 16 ไบต์หรือสตริงแบบมีเครื่องหมายยัติภังค์

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

UUID แบบ 16 บิตและ 32 บิตมักเป็นตัวระบุที่ SIG จัดสรร (ดู Bluetooth assigned numbers) ส่วน UUID แบบ 128 บิตมักกำหนดโดยผู้ผลิต