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 ระดับต่ำ แสดงทุกเมธอด ค่าสถานะ และค่าคงที่ในที่เดียวเมื่อคำถามคือเพียงแค่ "ชื่อที่แน่นอนของการเรียกนี้คืออะไร"