11.7. การดำเนินการ GATT¶
characteristic เพียงแค่อยู่ในฐานข้อมูล GATT ในฐานะค่าที่มีชื่อ สิ่งที่ทำให้มีประโยชน์คือชุดการดำเนินการที่กำหนดไว้ชัดเจนซึ่ง client สามารถรันได้ characteristic แต่ละตัวจะประกาศว่ารองรับการดำเนินการใดบ้างในรูปแบบ property bitmask ซึ่ง server ที่ไม่มีอะไรเปิดเผยสามารถเผยแพร่ค่าแบบอ่านอย่างเดียว register ควบคุมอาจเป็นแบบเขียนอย่างเดียว และ sensor ที่สตรีมการอัปเดตจะตั้งบิต notify ไว้ client จะค้นพบ bitmask ในระหว่างการค้นหาและปฏิบัติตาม
การดำเนินการห้าประเภทได้แก่ read, write, write without response, notify และ indicate โดยแบ่งออกเป็นสองกลุ่ม คือ pull (client ขอ) และ push (server ส่ง)
11.7.1. Pull: read และ write¶
ทั้งสองอย่างนี้เรียบง่ายที่สุดและดูเหมือน function call ทั่วไป
Read. client ขอค่าปัจจุบัน server ส่งกลับ รอบเดียว client ได้รับไบต์ที่ server ตั้งค่าไว้สำหรับ characteristic นั้น และ server ไม่ได้รับข้อมูลว่าใครเป็นผู้อ่าน
Write. client ส่งไบต์ใหม่ server เก็บไว้ (และอาจรัน logic ของแอปพลิเคชันกับค่าใหม่) มีสองรูปแบบ:
Write with response ซึ่ง server จะยืนยัน โดยยก error ของแอปพลิเคชันเมื่อสถานะไม่ใช่ศูนย์ เชื่อถือได้ ใช้เวลาหนึ่งรอบ
Write without response ซึ่ง server เก็บไบต์อย่างเงียบๆ โดย client ไม่ได้รับการยืนยันใดๆ เร็วกว่า (ไม่ต้องรอ ack) และมีประโยชน์สำหรับการสตรีม แต่แลกกับการทราบข้อผิดพลาดผ่านการอ่านกลับทางอ้อมเท่านั้น
ใน aioble API ฝั่ง client จะซ่อนตัวเลือกไว้ด้วย method aioble.ClientCharacteristic.write() เพียงตัวเดียว โดยมีคีย์เวิร์ด response (True / False / None เพื่อเลือกอัตโนมัติตามที่ peer โฆษณาไว้)
11.7.2. Push: notify และ indicate¶
โมเดล pull ไม่เหมาะกับข้อมูล sensor หากโทรศัพท์ต้องโพล heart-rate strap ทุกวินาทีจะสิ้นเปลืองพลังงานกับ radio event ที่ไม่จำเป็นนับร้อยครั้ง แต่การที่ strap ผลักค่าออกมาเฉพาะเมื่อมีการอ่านใหม่คือจุดประสงค์หลักของ BLE
GATT แก้ปัญหานี้ด้วยการดำเนินการที่ server เป็นผู้ริเริ่ม โดย client จะ สมัครสมาชิก characteristic และจากนั้นเป็นต้นไปทุกครั้งที่ server อัปเดตค่า ค่าใหม่จะถูกส่งไปยัง client ผ่านลิงก์ มีสองรูปแบบ:
Notify. ส่งแล้วลืม server จัดคิว notification ส่ง link layer ส่งในเหตุการณ์การเชื่อมต่อถัดไป client รับ ไม่มีการยืนยันในระดับ GATT โดย link layer จัดการการส่งซ้ำตามปกติสำหรับการสูญหายทางวิทยุ แต่แอปพลิเคชันจะไม่ได้รับการยืนยันว่าค่าถูกประมวลผลแล้ว
Indicate. server ส่ง notification และ รอการยืนยันระดับ GATT จาก client ก่อนส่งครั้งถัดไป ส่งได้ครั้งละหนึ่งครั้ง ใช้เมื่อ server จำเป็นต้องรู้ว่า client ได้รับค่าจริงๆ เช่น characteristic แจ้งเตือนวิกฤต หรือการยืนยันการกำหนดค่า
Pull (read) เทียบกับ push (notify) ด้วย notification client สมัครสมาชิกครั้งเดียวและ server ผลักค่าใหม่ทุกครั้งที่มีการเปลี่ยนแปลง¶
การสมัครสมาชิกเกิดขึ้นโดยการเขียนไปยัง descriptor ที่แนบกับ characteristic ซึ่งเรียกว่า Client Characteristic Configuration Descriptor (CCCD, 0x2902) การเขียน 0x0001 เปิดใช้งาน notification 0x0002 เปิดใช้งาน indication และ 0x0000 ปิดทั้งสอง method aioble.ClientCharacteristic.subscribe() จะทำการเขียนให้คุณ โดยมีแฟล็ก notify=True และ indicate=True
เมื่อสมัครสมาชิกแล้ว client จะรอการผลักขาเข้าด้วย notified() และ indicated() ซึ่งทั้งสองเป็น async coroutine ที่จะหยุดรอจนกว่าการผลักครั้งถัดไปจะมาถึง
11.7.3. MTU กำหนดขนาด payload¶
ทุกการดำเนินการถูกจำกัดด้วย MTU ที่เจรจาไว้ซึ่งการเชื่อมต่อตกลงกันตอนเชื่อมต่อ MTU เริ่มต้นคือ 23 ไบต์ ซึ่งเหลือ 20 ไบต์สำหรับค่า characteristic หลังจาก GATT header ข้อมูลที่ใหญ่กว่านั้นต้องพอดีกับ MTU ที่ใหญ่ขึ้น (เจรจาผ่าน aioble.DeviceConnection.exchange_mtu() สูงสุด 512 ไบต์บนกล้อง) หรือแบ่งออกเป็นหลาย characteristic หรือหลาย notification
การ read และ write ที่ client ริเริ่มสำหรับค่าที่ใหญ่กว่า MTU จะถูกจัดการโดยขั้นตอน long ของ GATT เบื้องหลัง (Read Long / Prepare-Write + Execute-Write) โดย aioble จะรันขั้นตอนเหล่านี้อย่างโปร่งใส ดังนั้นการเรียก read() / write() ด้วยค่าขนาดใหญ่เกินไปจะแค่ใช้เวลามากรอบขึ้น notification และ indication ที่ server ริเริ่มนั้น ไม่ได้ถูกแยกส่ง โดยการผลักหนึ่งครั้งถูกจำกัดด้วย MTU และแอปพลิเคชันต้องแบ่งสิ่งที่ใหญ่กว่านั้นออกเป็นหลาย notification หรือก้าวออกจาก GATT ทั้งหมด
สำหรับการถ่ายโอนข้อมูลขนาดใหญ่จริงๆ เช่น เฟรมที่ถ่ายไว้ ชุดการวัด หรือ firmware blob คำตอบที่ถูกต้องมักจะเป็นการก้าวออกจาก GATT ทั้งหมดและใช้ L2CAP channel แทน (ดู ช่องสัญญาณ L2CAP)
11.7.4. ภาพรวมทั้งสองฝั่ง¶
การดำเนินการห้าประเภทเปิดเผยแตกต่างกันในแต่ละฝั่งของการเชื่อมต่อ:
บน server (peripheral ในรูปแบบทั่วไป):
aioble.Characteristic.read()ซึ่งอ่านค่าท้องถิ่นปัจจุบันออกจากฐานข้อมูล GATT (ฝั่ง server ของ "สิ่งที่ client จะเห็น")aioble.Characteristic.write()ซึ่งอัปเดตค่าท้องถิ่น และอาจผลักการอัปเดตไปยัง client ทุกตัวที่สมัครสมาชิกaioble.Characteristic.notify()/indicate()ซึ่งส่งการผลักไปยัง client ที่ระบุหนึ่งตัวaioble.Characteristic.written()ซึ่งรอการเขียนขาเข้าครั้งถัดไปจาก client ตัวใดก็ได้aioble.Characteristic.on_read()ซึ่งเป็น callback ที่ถูกเรียกแบบ synchronous เมื่อ client อ่าน มีประโยชน์สำหรับการคำนวณค่าตามต้องการ
บน client (central ในรูปแบบทั่วไป):
aioble.ClientCharacteristic.read()ซึ่งขอค่าปัจจุบันจาก serveraioble.ClientCharacteristic.write()ซึ่งส่งค่าใหม่ โดยมีหรือไม่มีการตอบกลับก็ได้aioble.ClientCharacteristic.subscribe()ซึ่งเปิด/ปิดการใช้งาน notification และ indicationaioble.ClientCharacteristic.notified()/indicated()ซึ่งรอการผลักครั้งถัดไป