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.