8.14. AsyncCSI

Tyypillinen OpenMV Cam -skripti päättyy lauseeseen while True: img = csi0.snapshot() – estävään tilannekuvasilmukkaan, joka ei tarvitse asynciota lainkaan. Sillä hetkellä, kun sovelluksen on tehtävä jotain muuta kaappausten ohessa – kuunneltava painiketta, lähetettävä dataa vertaiselle tai suoritettava taustatehtävä – estävä kutsu alkaa haitata. Sillä aikaa kun snapshot odottaa seuraavaa kehystä, tapahtumasilmukka ei ole käynnissä, joten kaikki muut ohjelman korutiinit ovat jäädytettyinä, kunnes kehys saapuu.

Tällä sivulla rakennetaan pieni kääre CSI-luokan ympärille, joka muuttaa snapshot-metodin await-ystävälliseksi korutiiniksi. Tuloksena on suora korvaaja, jonka avulla kaappaussilmukka voi toimia rinnakkain muun asyncio-ohjelman kanssa.

8.14.1. Osat

Yksi osa CSI-rajapintaa tekee suurimman osan työstä – snapshot() ei-estävässä tilassaan. Kutsu snapshot(blocking=False) joko palauttaa seuraavan kehyksen (jos sellainen on valmiina) tai None (jos ei ole). Ensimmäinen ei-estävä kutsu myös käynnistää kameran DMA-kaappauksen, ellei se ollut jo käynnissä, joten kääreen ei tarvitse tehdä mitään erityistä alustamista varten.

Toinen osa on asyncio.sleep_ms(). Kääre pollaa ei-estäviä tilannekuvia silmukassa luovuttaen vuoron tapahtumasilmukalle kutsulla await asyncio.sleep_ms(0) tarkistusten välissä, jotta jokainen muu valmis korutiini saa mahdollisuuden suorittua ennen seuraavaa pollausta.

8.14.2. Kääre

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)

Konstruktori käärii CSI-instanssin. __getattr__ välittää jokaisen attribuutin, jota kääre ei itse määrittele – reset, pixformat, framesize ja kaikki sensorin säätimet – taustalla olevalle CSI-objektille, joten kääre näyttää identtiseltä käärimättömän objektin kanssa lukuun ottamatta sitä yhtä metodia, jolla on merkitystä.

async def snapshot on uusi osa. Se kutsuu snapshot(blocking=False); jos kutsu palauttaa kuvan, korutiini palauttaa sen. Muutoin se luovuttaa vuoron takaisin tapahtumasilmukalle kutsulla await asyncio.sleep_ms(0), jotta muut korutiinit saavat mahdollisuuden suorittua, ja palaa sitten silmukkaan ja yrittää uudelleen. Ensimmäinen iteraatio käynnistää DMA:n; seuraavat iteraatiot poimivat kehyksiä sitä mukaa kuin niitä tulee saataville.

8.14.3. Tilannekuvasilmukka seurassa

Kun kääre on paikallaan, tilannekuvasilmukka sopii laajempaan asyncio-ohjelmaan samalla tavalla kuin mikä tahansa muu korutiini. Alla oleva esimerkki suorittaa kolme korutiinia rinnakkain: kaappaussilmukan, LED-vilkuttajan ja sykäyksen, joka tulostaa hello kerran sekunnissa:

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

Kaikki kolme korutiinia etenevät samassa tapahtumasilmukassa. Sillä aikaa kun capture_loop luovuttaa vuoron ei-estävien tilannekuvapollausten välissä, blinker vaihtaa LEDin tilaa ja hello tulostaa. Sillä aikaa kun blinker ja hello nukkuvat, capture_loop pollaa kameraa. Pollausväli on lyhyt – yksi tapahtumasilmukan tikitys – joten se lisää häviävän pienen viiveen siihen, milloin sovellus näkee uuden kehyksen.

Kaappaussilmukka ei estä tapahtumasilmukkaa. Lisää rinnakkaista työtä – esimerkiksi UART-asiakas – on vain yksi ylimääräinen create_task()-kutsu main-funktion sisällä.

Muista

framebuffers-asetuksella on yhä merkitystä tässä muodossa. Yhden puskurin tila saa snapshot(blocking=False)-kutsun palauttamaan None, kunnes seuraava kehys on kaapattu; kaksois- tai kolmoispuskurointi tasoittaa tätä niin, että kääre yleensä löytää puskuroidun kehyksen odottamassa ensimmäisellä pollauksella sen jälkeen, kun edellinen kehys on käsitelty.