11.12. บทบาทพร้อมกันและการเชื่อมต่อหลายรายการ¶
หน้า peripheral และ central แต่ละหน้าแสดงบทบาทเดียวที่ให้บริการการเชื่อมต่อเดียวในแต่ละครั้ง แอปพลิเคชันจริงแทบไม่เคยง่ายอย่างนั้น กล้องอาจเผยแพร่บริการ sensor ไปยังโทรศัพท์ พร้อมกับ อ่านค่าจาก heart-rate strap หรือรับการเชื่อมต่อจากโทรศัพท์ที่จับคู่พร้อมกันสองเครื่อง API ของ aioble รองรับทั้งสองรูปแบบเพราะวิทยุ multiplex อยู่เบื้องล่างและทุกการดำเนินการเป็น coroutine อยู่แล้ว -- รัน coroutine เพิ่มขึ้น และงานจะดำเนินการแบบขนานบน event loop เดียว
หน้านี้รวบรวมรูปแบบที่มักพบ
11.12.1. Client หลายรายเชื่อมต่อไปยัง peripheral เดียว¶
ลูป peripheral แบบง่ายบน การทำหน้าที่เป็น peripheral ให้บริการ central ที่เชื่อมต่อทีละตัว:
async def serve():
while True:
connection = await aioble.advertise(...)
async with connection:
await connection.disconnected()
รูปแบบที่ช่วยให้รับ client มากกว่าหนึ่ง คือการเปิดตัว task ต่อการเชื่อมต่อและวนซ้ำกลับไปยัง aioble.advertise() ทันทีเพื่อให้ client ถัดไปสามารถเชื่อมต่อได้เช่นกัน:
async def handle_client(connection):
async with connection:
# ... per-client work: subscribe their CCCDs,
# push notifications, await writes ...
await connection.disconnected()
async def serve():
while True:
connection = await aioble.advertise(
interval_us=250000,
name="openmv-env",
services=[ENV_SERVICE],
)
asyncio.create_task(handle_client(connection))
การเชื่อมต่อแต่ละครั้งรันในงานของตัวเอง ฐานข้อมูล GATT ถูกแชร์ -- client ทั้งหมดเห็นบริการและ characteristic เดียวกัน -- แต่สถานะต่อการเชื่อมต่ออยู่ภายใน task การแจ้งเตือนไปยัง ทุก client ที่สมัครสมาชิกเมื่อเรียก write() ด้วย send_update=True; การ push แบบกำหนดทิศทางที่ควรไปถึง client เดียวเท่านั้นใช้ notify() / indicate() พร้อมอาร์กิวเมนต์ DeviceConnection เฉพาะ
รักษา fan-out ให้เล็ก การเชื่อมต่อที่ถือไว้แต่ละอันใช้เวลาวิทยุ RAM และ slot ในตาราง connection ของ controller และกล้องไม่ได้ออกแบบมาเป็น hub สำหรับ client หลายสิบตัว สอง-สาม central (โทรศัพท์ แท็บเล็ต companion microcontroller) อยู่ในขอบเขตที่เข้าถึงได้; การออกแบบที่ต้องการมากกว่านี้ควรอยู่บน BLE gateway ที่เหมาะสมแทนที่จะอยู่บนกล้อง
11.12.2. Peripheral และ central ในเวลาเดียวกัน¶
กล้องสามารถโฆษณาบริการของตัวเองไปยังโทรศัพท์ พร้อมกับ ทำหน้าที่เป็น central ไปยัง wearable ด้วย aioble ไม่มีสวิตช์ "โหมด" -- ลูปโฆษณาและลูป scan-and-connect เป็นเพียง coroutine อิสระ:
async def be_peripheral():
while True:
connection = await aioble.advertise(
interval_us=250000,
name="openmv-hub",
services=[ENV_SERVICE],
)
asyncio.create_task(handle_client(connection))
async def be_central():
while True:
sensor = await find_sensor()
if sensor is None:
await asyncio.sleep(5)
continue
try:
async with await sensor.connect() as conn:
await stream_from_sensor(conn)
except aioble.DeviceDisconnectedError:
pass
async def main():
await asyncio.gather(be_peripheral(), be_central())
asyncio.run(main())
วิทยุแบ่งเวลาระหว่างสองบทบาท -- scan window ที่นี่ burst โฆษณาที่นั่น connection event เมื่อการเชื่อมต่อจากฝ่ายใดฝ่ายหนึ่งมีชีวิตอยู่ ปริมาณงานที่ผ่านได้ในแต่ละบทบาทลดลงเมื่อทั้งสองทำงานพร้อมกันเพราะวิทยุไม่สามารถทำสองสิ่งพร้อมกันได้ แต่สำหรับการสนทนาแบนด์วิดท์ต่ำที่ BLE ออกแบบมาสำหรับ ค่าใช้จ่ายมักจะมองไม่เห็น
สองสิ่งที่ควรคำนึงในทางปฏิบัติ:
ทั้งสองบทบาทต้องอยู่ใน coroutine ของตัวเอง. การเรียก
aioble.scan()จากภายใน task ต่อ client ที่จัดการ central ที่เชื่อมต่อทำงานได้ แต่จะบล็อกการแจ้งเตือนของ client นั้นจนกว่าการสแกนจะเสร็จสิ้น -- รันการสแกนบน task ของตัวเองแทนมีเพียงการสแกนเดียวที่ทำงานในแต่ละครั้ง. หากคุณต้องการสแกนจากสองที่แตกต่างกัน แชร์ scan iterator หรือประสานการเข้าถึง; อย่าเข้า context manager ของ
aioble.scan()สองตัวแบบขนาน
11.12.3. การประสานการเชื่อมต่อหลายรายการจาก task เดียว¶
เมื่อการเชื่อมต่อหลายรายการต้องรวมกันเป็นการดำเนินการเชิงตรรกะเดียว -- ตัวอย่างเช่น กล้องคุยกับ sensor สองตัวพร้อมกันและรายงานผลลัพธ์หลังจากทั้งคู่ตอบสนองแล้ว -- primitives มาตรฐาน asyncio ใช้ได้โดยตรง asyncio.gather() รัน coroutine ต่อการเชื่อมต่อพร้อมกันและส่งคืนเมื่อทั้งหมดเสร็จสิ้น; asyncio.wait_for() เพิ่ม deadline
async def read_pair():
async with await sensor_a.connect() as a:
async with await sensor_b.connect() as b:
value_a, value_b = await asyncio.gather(
read_value(a, A_SERVICE, A_CHAR),
read_value(b, B_SERVICE, B_CHAR),
)
return value_a, value_b
รูปแบบเดียวกับที่บท asyncio (Asyncio) ใช้สำหรับเครือข่าย -- BLE coroutines เสียบเข้ากับ gather / wait_for / Event / Lock แบบเดียวกับที่ TCP ทำ
11.12.4. เมื่อบทบาทหนึ่งเสร็จสิ้นต่อรอบและอีกบทบาทไม่เสร็จ¶
รอบหนึ่งในกล้องที่ใช้พลังงานจากแบตเตอรี่อาจมีลักษณะดังนี้:
ตื่น
ในฐานะ central อ่านค่าใหม่จาก sensor strap ที่จับคู่
ในฐานะ peripheral โฆษณาให้โทรศัพท์ดาวน์โหลดการวัดของวัน
เมื่อทั้งคู่ไม่ได้ใช้งาน เรียก
aioble.stop()และหลับ
การจัดลำดับเป็นเรื่องตรงไปตรงมาด้วยสอง task และ asyncio.Event
phone_done = asyncio.Event()
async def serve_phone():
connection = await aioble.advertise(
interval_us=250000,
name="openmv-hub",
services=[ENV_SERVICE],
)
async with connection:
await stream_measurements(connection)
phone_done.set()
async def read_strap():
async with await strap.connect() as conn:
await pull_fresh_values(conn)
async def cycle():
await asyncio.gather(read_strap(), serve_phone())
aioble.stop() # radio off until next wake