11.12. Samtidiga roller och flera anslutningar¶
Sidorna om peripheral och central visar var och en en enda roll som betjänar en enda anslutning åt gången. Verkliga tillämpningar är sällan så enkla. En kamera kan publicera en sensortjänst till en telefon samtidigt som den läser värden från ett pulsband, eller acceptera anslutningar från två samtidigt parkopplade telefoner. aioble-API:et stöder båda mönstren eftersom radion multiplexar i botten och varje operation redan är en coroutine – kör fler coroutiner, så sker arbetet parallellt på en enda händelseloop.
Den här sidan samlar de mönster som dyker upp.
11.12.1. Flera klienter ansluter till en peripheral¶
Den enkla peripheral-loopen på Att agera som kringutrustning betjänar en ansluten central åt gången:
async def serve():
while True:
connection = await aioble.advertise(...)
async with connection:
await connection.disconnected()
Mönstret som låter den acceptera fler än en klient är att starta en uppgift per anslutning och omedelbart loopa tillbaka till aioble.advertise() så att nästa klient också kan ansluta:
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))
Varje anslutning körs i sin egen uppgift. GATT-databasen är delad – alla klienter ser samma tjänster och egenskaper – men tillstånd per anslutning lever inuti uppgiften. Notifieringar går till varje prenumererande klient när write() anropas med send_update=True; riktade pushar som bara ska nå en klient använder notify() / indicate() med det specifika DeviceConnection-argumentet.
Håll utfläkten liten. Varje vidmakthållen anslutning kostar radiotid, RAM och en plats i styrenhetens anslutningstabell, och kameran är inte konstruerad för att vara en nav för dussintals klienter. Två eller tre centraler (en telefon, en surfplatta, en mikrokontroller som följeslagare) ligger väl inom räckhåll; konstruktioner som behöver fler hör hemma på en riktig BLE-gateway snarare än på kameran.
11.12.2. Peripheral och central samtidigt¶
En kamera kan annonsera sin egen tjänst till en telefon samtidigt som den agerar central gentemot en bärbar enhet. aioble har ingen ”läges”-omkopplare – annonseringsloopen och loopen för skanna-och-ansluta är bara oberoende coroutiner:
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())
Radion tidsdelar mellan de två rollerna – ett skanningsfönster här, en annonseringsskur där, en anslutningshändelse när någon av sidornas anslutningar är aktiv. Genomströmningen på varje roll sjunker när båda är aktiva eftersom radion bokstavligen inte kan göra två saker samtidigt, men för de lågbandbreddskonversationer som BLE konstruerades för är kostnaden vanligen osynlig.
Två praktiska saker att hålla i minnet:
Båda rollerna måste vara i sin egen coroutine. Att anropa
aioble.scan()inifrån uppgiften per klient som hanterar en ansluten central fungerar, men blockerar den klientens notifieringar tills skanningen är klar – kör skanningen i sin egen uppgift istället.Bara en skanning körs åt gången. Om du behöver skanna från två olika ställen, dela skanningsiteratorn eller samordna åtkomsten; gå inte in i två
aioble.scan()-kontexthanterare parallellt.
11.12.3. Samordna flera anslutningar från en uppgift¶
När flera anslutningar behöver kombineras till en logisk operation – till exempel pratar kameran med två sensorer samtidigt och rapporterar resultatet först när båda har svarat – gäller standardprimitiverna i asyncio direkt. asyncio.gather() kör coroutinerna per anslutning samtidigt och återvänder när alla har slutfört; asyncio.wait_for() lägger till en tidsgräns.
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
Samma mönster som asyncio-kapitlet (Asyncio) använder för nätverk – BLE-coroutiner kopplas in i gather / wait_for / Event / Lock på samma sätt som TCP-coroutiner gör.
11.12.4. När en roll slutförs per cykel och den andra inte gör det¶
En cykel i en batteridriven kamera kan se ut så här:
Vakna.
Läs som central färska värden från ett parkopplat sensorband.
Annonsera som peripheral så att en telefon kan ladda ner dagens mätningar.
När båda är overksamma, anropa
aioble.stop()och sov.
Sekvenseringen är rättfram med två uppgifter och en 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