11.12. Souběžné role a více připojení¶
Stránky o periferii a centrálním zařízení vždy ukazují jedinou roli obsluhující v daném okamžiku jediné připojení. Reálné aplikace bývají jen zřídka tak jednoduché. Kamera může publikovat senzorovou službu do telefonu a zároveň číst hodnoty z hrudního pásu pro měření tepu, nebo přijímat připojení od dvou současně spárovaných telefonů. API aioble podporuje oba vzory, protože rádio se na pozadí multiplexuje a každá operace už je korutina – stačí spustit více korutin a práce probíhá paralelně v jedné smyčce událostí.
Tato stránka shromažďuje vzory, které se v praxi objevují.
11.12.1. Více klientů připojených k jedné periferii¶
Jednoduchá smyčka periferie na Vystupování jako periferie obsluhuje v daném okamžiku jediné připojené centrální zařízení:
async def serve():
while True:
connection = await aioble.advertise(...)
async with connection:
await connection.disconnected()
Vzor, který jí umožňuje přijmout více než jednoho klienta, spočívá v tom, že se pro každé připojení spustí samostatná úloha a okamžitě se vrátí zpět k aioble.advertise(), aby se mohl připojit i další klient:
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))
Každé připojení běží ve své vlastní úloze. Databáze GATT je sdílená – všichni klienti vidí stejné služby a charakteristiky – ale stav specifický pro jednotlivé připojení žije uvnitř úlohy. Oznámení jdou každému přihlášenému klientovi, když je write() voláno s send_update=True; cílené odeslání, které má dosáhnout pouze jednoho klienta, používá notify() / indicate() s konkrétním argumentem DeviceConnection.
Udržujte rozvětvení malé. Každé udržované připojení stojí čas rádia, RAM a slot v tabulce připojení řadiče a kamera není navržena jako rozbočovač pro desítky klientů. Dvě nebo tři centrální zařízení (telefon, tablet, doprovodný mikrokontrolér) jsou snadno na dosah; návrhy, které potřebují více, patří na řádnou BLE bránu, nikoli na kameru.
11.12.2. Periferie a centrální zařízení současně¶
Kamera může inzerovat svoji vlastní službu do telefonu, a zároveň fungovat jako centrální zařízení vůči nositelnému zařízení. aioble nemá přepínač „režimu“ – inzertní smyčka a smyčka skenování a připojování jsou jen nezávislé korutiny:
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())
Rádio se časově sdílí mezi oběma rolemi – skenovací okno zde, inzertní dávka tam, událost připojení, když je některé z připojení na obou stranách aktivní. Propustnost (throughput) každé role klesá, když jsou obě aktivní, protože rádio nemůže doslova dělat dvě věci najednou, ale u nízkokapacitní komunikace, pro kterou byl BLE navržen, je tento náklad obvykle neviditelný.
Dvě praktické věci, které je třeba mít na paměti:
Obě role musí být ve své vlastní korutině. Volání
aioble.scan()zevnitř úlohy pro jednotlivého klienta, která obsluhuje připojené centrální zařízení, funguje, ale blokuje oznámení tohoto klienta, dokud skenování neskončí – skenování raději spusťte v jeho vlastní úloze.Najednou běží pouze jedno skenování. Pokud potřebujete skenovat ze dvou různých míst, sdílejte iterátor skenování nebo koordinujte přístup; nevstupujte do dvou kontextových správců
aioble.scan()paralelně.
11.12.3. Koordinace více připojení z jedné úlohy¶
Když je třeba zkombinovat několik připojení do jedné logické operace – například kamera komunikuje se dvěma senzory najednou a výsledek hlásí teprve poté, co oba odpověděly – uplatní se přímo standardní primitiva asyncio. asyncio.gather() spouští korutiny pro jednotlivá připojení souběžně a vrací se, až všechny dokončí; asyncio.wait_for() přidává časový limit.
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
Stejný vzor, jaký pro síťování používá kapitola o asyncio (Asyncio) – BLE korutiny se zapojují do gather / wait_for / Event / Lock stejným způsobem jako ty pro TCP.
11.12.4. Když jedna role dokončí svůj cyklus a druhá ne¶
Cyklus u kamery napájené z baterie může vypadat takto:
Probuzení.
Jako centrální zařízení přečíst čerstvé hodnoty ze spárovaného senzorového pásu.
Jako periferie inzerovat, aby si telefon mohl stáhnout denní měření.
Když jsou obě nečinné, zavolat
aioble.stop()a uspat se.
Sekvencování je s dvěma úlohami a asyncio.Event přímočaré:
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