8.14. AsyncCSI¶
Egy tipikus OpenMV Cam szkript a while True: img = csi0.snapshot() sorral ér véget – ez egy blokkoló pillanatkép-hurok, amelynek egyáltalán nincs szüksége asyncio-ra. Abban a pillanatban, amikor az alkalmazásnak a felvételek mellett valami mást is tennie kell – figyelni egy gombot, adatot küldeni egy társeszköznek, háttérfeladatot futtatni –, a blokkoló hívás útban van. Amíg a snapshot a következő képkockára vár, az eseményhurok nem fut, így a program minden más korutinja befagy, amíg a képkocka meg nem érkezik.
Ez az oldal egy kis burkolót épít a CSI köré, amely a snapshot hívást egy await-barát korutinná alakítja. Az eredmény egy közvetlenül cserélhető helyettesítő, amely lehetővé teszi, hogy egy felvételi hurok együtt létezzen egy asyncio program többi részével.
8.14.1. Az alkotóelemek¶
A CSI API egyetlen darabja végzi a munka nagy részét – a snapshot() a nem blokkoló módjában. A snapshot(blocking=False) hívás vagy a következő képkockát adja vissza (ha kész van), vagy None értéket (ha nincs). Az első nem blokkoló hívás egyúttal elindítja a kamera DMA-felvételét, ha az még nem futott, így a burkolónak nem kell semmi különöset tennie a beindításhoz.
A másik darab az asyncio.sleep_ms(). A burkoló egy hurokban lekérdezi a nem blokkoló pillanatképeket, és az ellenőrzések között az await asyncio.sleep_ms(0) hívással átadja a vezérlést az eseményhuroknak, hogy minden más futásra kész korutin lehetőséget kapjon a következő lekérdezés előtt.
8.14.2. A burkoló¶
import asyncio
import csi
class AsyncCSI:
def __init__(self, *args, **kwargs):
self._csi = csi.CSI(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._csi, name)
async def snapshot(self):
while True:
img = self._csi.snapshot(blocking=False)
if img is not None:
return img
await asyncio.sleep_ms(0)
A konstruktor egy CSI példányt burkol be. A __getattr__ minden olyan attribútumot, amelyet maga a burkoló nem definiál – reset, pixformat, framesize, az összes érzékelő-beállítás –, továbbít az alatta lévő CSI felé, így a burkoló a beburkolatlan objektummal teljesen azonosnak tűnik, kivéve azt az egy metódust, amely számít.
Az async def snapshot az új darab. Meghívja a snapshot(blocking=False) hívást; ha a hívás visszaad egy képet, a korutin visszaadja azt. Ellenkező esetben az await asyncio.sleep_ms(0) hívással visszaadja a vezérlést az eseményhuroknak, hogy más korutinok futási lehetőséget kapjanak, majd visszaugrik a hurokba, és újra próbálkozik. Az első iteráció elindítja a DMA-t; a további iterációk felveszik a képkockákat, amint elérhetővé válnak.
8.14.3. Felvételi hurok társaságban¶
A burkolóval a helyén egy felvételi hurok ugyanúgy illeszkedik egy nagyobb asyncio programba, mint bármely más korutin. Az alábbi példa három korutint futtat egyidejűleg: a felvételi hurkot, egy LED-villogtatót, és egy szívverést, amely másodpercenként egyszer kiírja a hello szót:
import asyncio
import csi
from machine import LED
async def capture_loop(cam):
while True:
img = await cam.snapshot()
# process img here
async def blinker(led, period_ms):
while True:
led.on()
await asyncio.sleep_ms(period_ms)
led.off()
await asyncio.sleep_ms(period_ms)
async def hello(period_s):
while True:
print("hello")
await asyncio.sleep(period_s)
async def main():
cam = AsyncCSI()
cam.reset()
cam.pixformat(csi.RGB565)
cam.framesize(csi.QVGA)
asyncio.create_task(blinker(LED("LED_BLUE"), 200))
asyncio.create_task(hello(1))
await capture_loop(cam)
asyncio.run(main())
Mindhárom korutin ugyanazon az eseményhurkon halad előre. Amíg a capture_loop a nem blokkoló pillanatkép-lekérdezések között átadja a vezérlést, a blinker kapcsolgatja a LED-et, és a hello kiír. Amíg a blinker és a hello alszik, a capture_loop lekérdezi a kamerát. A lekérdezési időköz rövid – egyetlen eseményhurok-ütés –, így elhanyagolható késleltetést ad ahhoz, hogy az alkalmazás mikor lát egy új képkockát.
A felvételi hurok nem blokkolja az eseményhurkot. További egyidejű munka hozzáadása – például egy UART kliens – csupán egy újabb create_task() hívás a main belsejében.
Megjegyzés
A framebuffers beállítás ebben a formában is számít. Az egypufferes mód azt eredményezi, hogy a snapshot(blocking=False) None értéket ad vissza, amíg a következő képkocka nincs felvéve; a dupla vagy hármas pufferelés ezt elsimítja, így a burkoló általában már az előző képkocka feldolgozása utáni első lekérdezéskor talál egy pufferelt képkockát.