11.14. สรุป¶
คุณได้เดินทางผ่าน Bluetooth Low Energy ตั้งแต่ระดับวิทยุจนถึง Python API ที่ใช้ขับเคลื่อนมัน:
แรงจูงใจ -- BLE คือคำตอบเมื่อกล้องต้องการคุยกับสิ่งที่อยู่ใกล้ๆ โดยไม่มีโครงสร้างพื้นฐานระหว่างกัน โทรศัพท์ในห้องเดียวกัน อุปกรณ์สวมใส่บนข้อมือ beacon บนผนัง ระยะสั้น ไม่ต้องเข้าร่วมเครือข่าย แทบไม่ใช้พลังงาน
วิทยุ -- 2.4 GHz, 40 ช่อง: สามสำหรับการโฆษณา, 37 สำหรับข้อมูลการเชื่อมต่อ, กระโดดตามลำดับสุ่มเสมือนพร้อมการหลีกเลี่ยงช่องที่มีสัญญาณรบกวนแบบปรับได้ แพ็กเก็ตสั้น วิทยุที่ส่วนใหญ่อยู่ในโหมดหลับ
เลเยอร์ลิงก์ -- การจัดกรอบแพ็กเก็ต การกำหนดที่อยู่ การกำหนดการเชื่อมต่อ การส่งใหม่ และการเข้ารหัสเลเยอร์ลิงก์ ไม่มีสิ่งใดที่กำหนดค่าจาก Python; ทุกอย่างแสดงผ่านพารามิเตอร์การเชื่อมต่อและ MTU
Generic Access Profile (GAP) -- การค้นหาและการจัดการการเชื่อมต่อ สี่บทบาท: peripheral และ broadcaster (โฆษณา), central และ observer (สแกน) payload การโฆษณาบรรจุชื่อท้องถิ่น service UUIDs รูปลักษณ์ และข้อมูลเฉพาะผู้ผลิต -- 31 ไบต์บวก scan response 31 ไบต์ทางเลือก ช่วงการเชื่อมต่อ peripheral latency และ supervision timeout ควบคุมความรู้สึกของการเชื่อมต่อที่เปิดอยู่
Generic Attribute Profile (GATT) -- ต้นไม้ของ services แต่ละอันมี characteristics แต่ละอันมี descriptors ทางเลือก ระบุด้วย UUIDs (16 บิตสำหรับมาตรฐาน Bluetooth-SIG, 128 บิตสำหรับแบบกำหนดเอง) ห้าการดำเนินการ: read และ write (ดึง, client เริ่มต้น), notify และ indicate (ผลัก, server เริ่มต้น, สมัครรับผ่าน Client Characteristic Configuration Descriptor) ขนาด payload ถูกจำกัดโดย MTU ที่เจรจา
Python API --
aiobleเปลี่ยนทุก BLE pattern เป็น asyncio coroutine peripheral คือaioble.advertise()วนซ้ำการเชื่อมต่อ พร้อมออบเจ็กต์Service/Characteristicที่สร้างครั้งเดียวและส่งมอบโดยaioble.register_services()central คือaioble.scan()เพื่อค้นหาเพียร์,connect()เพื่อเปิดลิงก์,service()และcharacteristic()เพื่อเดิน GATT tree ระยะไกล จากนั้นread()/write()/subscribe()/notified()สำหรับข้อมูลจริง การตัดการเชื่อมต่อแสดงเป็นaioble.DeviceDisconnectedErrorภายใน coroutine ที่กำลังรอช่อง L2CAP -- ทางออกสำหรับสตรีมไบต์จำนวนมากที่ไม่เหมาะกับโมเดล key/value ของ GATT
aioble.DeviceConnection.l2cap_accept()/l2cap_connect()เปิดช่องเฉพาะแอปพลิเคชันบนการเชื่อมต่อ GAP พร้อม send / recv ที่ควบคุมด้วยเครดิต-ไหล และ MTU ที่ใหญ่กว่าที่ GATT สามารถรองรับการจับคู่และการเข้ารหัส -- ลิงก์ BLE เป็นสาธารณะโดยค่าเริ่มต้น
aioble.DeviceConnection.pair()เริ่มต้นการแลกเปลี่ยนคีย์ที่ผลิตลิงก์ที่เข้ารหัส;bond=True(ค่าเริ่มต้น) จัดเก็บคีย์เพื่อให้การเชื่อมต่อถัดไปข้ามขั้นตอนการสื่อสาร หากไม่มีmitm=Trueและความสามารถ IO ที่ใช้งานได้ การเข้ารหัสป้องกันผู้ดักฟังเฉยๆ แต่ไม่ป้องกันการเปลี่ยนเส้นทางอย่างแข็งขันในระหว่างการจับคู่ครั้งแรก
นั่นเพียงพอแล้วสำหรับการเขียนแอปพลิเคชันกล้องที่เผยแพร่สถานะเป็น peripheral อ่านข้อมูล sensor เป็น central ผลักค่าสดไปยังโทรศัพท์ผ่าน BLE รักษาความปลอดภัยลิงก์ด้วยขั้นตอนจับคู่และผูกพัน และ -- สำหรับกรณีการถ่ายโอนข้อมูลจำนวนมากที่หาได้ยาก -- ออกจาก GATT ไปยังช่อง L2CAP
11.14.1. การแก้ไขปัญหา¶
ความล้มเหลวของ BLE ส่วนใหญ่เกิดจากความไม่ตรงกันระหว่างสิ่งที่ทั้งสองฝ่ายคาดหวัง และ inspector ฝั่งโทรศัพท์เป็นวิธีที่เร็วที่สุดในการดูว่าความคาดหวังของใครผิด เครื่องมือมาตรฐานคือ nRF Connect for Mobile (Nordic Semiconductor, ฟรีบน Android และ iOS): มันสแกน เชื่อมต่อ เดิน GATT database อ่านและเขียน characteristics และสมัครรับการแจ้งเตือน -- ดังนั้นพฤติกรรมฝั่งกล้องสามารถทดสอบแบบแยกส่วนได้ โดยไม่ต้องเขียนแอปเสริมเลย
โหมดความล้มเหลวที่พบบ่อย:
"อุปกรณ์ของฉันปรากฏในเครื่องสแกนแต่ไม่ยอมเชื่อมต่อ" บ่อยที่สุดคือ advertising packet มี
connectable=False(โหมด broadcaster) หรือการเชื่อมต่อก่อนหน้ายังเปิดอยู่และกล้องผ่านaioble.advertise()ไปแล้ว เพิ่ม print statements รอบการเรียก advertise เพื่อยืนยัน"exchange_mtu(512) ทำงานแล้วแต่การแจ้งเตือนของฉันยังถูกจำกัดที่ 20 ไบต์" MTU ที่เจรจาคือ
min(local, peer)-- โทรศัพท์หรือไลบรารี central อาจไม่ได้ขอ MTU ที่ใหญ่กว่าจากฝั่งของมัน ซึ่งในกรณีนั้นการเชื่อมต่อยังอยู่ที่ 23 ตรวจสอบmtuหลังจากexchange_mtu()คืนค่า และโปรดทราบว่าexchange_mtu()ทำงานได้เพียงครั้งเดียวต่อการเชื่อมต่อ; เรียกมันก่อนการดำเนินการขนาดใหญ่ครั้งแรก"การจับคู่ล้มเหลวด้วยข้อผิดพลาดทั่วไป" สองสาเหตุปกติ: ความไม่ตรงกันของ IO-capability (ขอ
mitm=Trueบนกล้องที่ประกาศio=3/ ไม่มีอินพุตไม่มีเอาต์พุต -- ไม่มีทางยืนยันรหัสตัวเลขได้ ดังนั้น pairing engine จึงหยุด) และเวลานาฬิกาที่ผิดพลาดมากบนกล้องเมื่อเพียร์ต้องการมัน ตั้งนาฬิกาด้วยntptime.settime()ก่อนการจับคู่ครั้งแรก"การแจ้งเตือนไม่เคยมาถึงที่ client" สองสิ่งที่ต้องตรวจสอบตามลำดับ: (a) characteristic ถูกประกาศด้วย
notify=Trueหรือไม่? -- บิต property ต้องถูกตั้งค่าจากฝั่ง server; (b) client เรียกsubscribe()หรือไม่? -- หากไม่เขียน Client Characteristic Configuration Descriptor (CCCD) server จะได้รับแจ้งว่าไม่มี client ต้องการการแจ้งเตือนและทิ้งมันอย่างเงียบๆ"ชื่อที่โฆษณาถูกตัดหรือหายไป" payload การโฆษณาคือ 31 ไบต์ และ flags + service-UUID + appearance fields แต่ละอันใช้ไบต์จากด้านบน
name=ที่ยาวบวก service UUIDs หลายตัวจะล้น ย่อชื่อหรือใช้การสแกนแบบ active เพื่อให้ scan response (อีก 31 ไบต์) รับภาระที่เกิน nRF Connect แสดงทั้งสองส่วนแยกกัน ซึ่งทำให้เห็นการแบ่งได้ชัดเจน"L2CAP connect เกิด exception ทันที" มักเกิดจาก PSM ไม่ตรงกัน -- ทั้งสองฝ่ายต้องตกลงกันบนหมายเลข PSM เดียวกันนอกแบนด์
L2CAPConnectionErrorมีสถานะ Bluetooth เป็นอาร์กิวเมนต์แรก; สถานะ2("PSM not supported") คือสัญญาณบอก"การเชื่อมต่อที่ผูกพันยังคงกระตุ้นขั้นตอนการจับคู่เต็มรูปแบบทุกครั้งที่เชื่อมต่อใหม่"
aioble.security.load_secrets()ไม่ถูกเรียกเมื่อเริ่มต้น หากไม่มีมัน คีย์ที่บันทึกไว้จะอยู่บนแฟลชแต่ไม่เคยถูกโหลดเข้าหน่วยความจำ ดังนั้น identity ของเพียร์จึงไม่ทราบและการจับคู่จะทำงานตั้งแต่ต้นทุกครั้ง
เมื่อทุกอย่างล้มเหลว โมดูล bluetooth ระดับต่ำกว่าเปิดเผย IRQ callback ที่ทำงานสำหรับทุกเหตุการณ์พื้นฐาน; การสมัครรับมันชั่วคราวและพิมพ์เหตุการณ์เทียบเท่ากับ Wireshark trace สำหรับฝั่งกล้อง
11.14.2. การใช้เอกสารอ้างอิงนี้ในภายหลัง¶
ถือว่าบทต่างๆ เกี่ยวกับ Bluetooth เป็นเอกสารอ้างอิง; การกลับมาดูเลย์เอาต์ที่แน่นอนของ advertising payload ของ peripheral หรือขั้นตอน scan-and-subscribe ของ central คือการใช้งานที่ตั้งใจไว้ หน้าอ้างอิง aioble --- Async BLE และ bluetooth --- Bluetooth ระดับต่ำ แสดงทุกเมธอด ค่าสถานะ และค่าคงที่ในที่เดียวเมื่อคำถามคือเพียงแค่ "ชื่อที่แน่นอนของการเรียกนี้คืออะไร"