8.14. AsyncCSI

Ett typiskt OpenMV Cam-skript slutar med while True: img = csi0.snapshot() – en blockerande stillbildsloop som inte alls behöver asyncio. I samma ögonblick som applikationen måste göra något annat parallellt med tagningarna – lyssna efter en knapp, skicka data till en motpart, köra en bakgrundsuppgift – står det blockerande anropet i vägen. Medan snapshot väntar på nästa bildruta körs inte händelseloopen, så varje annan korutin i programmet är frusen tills bildrutan kommer.

Den här sidan bygger ett litet omslag kring CSI som förvandlar snapshot till en await-vänlig korutin. Resultatet är en direkt ersättning som låter en tagningsloop samexistera med resten av ett asyncio-program.

8.14.1. Delarna

En del av CSI-API:et gör det mesta av jobbet – snapshot() i sitt icke-blockerande läge. Att anropa snapshot(blocking=False) returnerar antingen nästa bildruta (om en är klar) eller None (om inte). Det första icke-blockerande anropet startar också kamerans DMA-tagning om den inte redan kördes, så omslaget behöver inte göra något särskilt för att komma igång.

Den andra delen är asyncio.sleep_ms(). Omslaget pollar icke-blockerande stillbilder i en loop och lämnar över till händelseloopen med await asyncio.sleep_ms(0) mellan kontrollerna, så att varje annan redo korutin får en chans att köra innan nästa pollning.

8.14.2. Omslaget

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)

Konstruktorn omsluter en CSI-instans. __getattr__ vidarebefordrar varje attribut som omslaget inte självt definierar – reset, pixformat, framesize, alla sensorreglage – till den underliggande CSI, så omslaget ser identiskt ut med det oomslutna objektet förutom den enda metod som spelar roll.

async def snapshot är den nya delen. Den anropar snapshot(blocking=False); om anropet returnerar en bild returnerar korutinen den. Annars lämnar den tillbaka till händelseloopen med await asyncio.sleep_ms(0) så att andra korutiner får en chans att köra, och loopar sedan tillbaka och försöker igen. Den första iterationen startar DMA:n; efterföljande iterationer plockar upp bildrutor allteftersom de blir tillgängliga.

8.14.3. En stillbildsloop med sällskap

Med omslaget på plats passar en stillbildsloop in i ett större asyncio-program på samma sätt som vilken annan korutin som helst. Exemplet nedan kör tre korutiner samtidigt: tagningsloopen, en LED-blinkare och ett hjärtslag som skriver ut hello en gång i sekunden:

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())

Alla tre korutinerna gör framsteg på samma händelseloop. Medan capture_loop lämnar över mellan icke-blockerande stillbildspollningar växlar blinker LED:en och hello skriver ut. Medan blinker och hello sover pollar capture_loop kameran. Pollningsintervallet är kort – ett enda händelseloops-tick – så det lägger till försumbar latens till när applikationen ser en ny bildruta.

Tagningsloopen blockerar inte händelseloopen. Att lägga till mer samtidigt arbete – till exempel en UART-klient – är bara ytterligare ett create_task()-anrop inuti main.

Anteckning

Inställningen framebuffers har fortfarande betydelse i den här formen. Enbuffertläge gör att snapshot(blocking=False) returnerar None tills nästa bildruta fångas; dubbel- eller trippelbuffring jämnar ut det så att omslaget vanligtvis hittar en buffrad bildruta som väntar vid den första pollningen efter att den föregående bildrutan har bearbetats.