10.2. Restituire uno snapshot

Un endpoint di stato va bene, ma la ragione per cui la camera esiste è l’obiettivo. Aggiungi un endpoint che restituisca il JPEG di qualunque cosa il sensore stia inquadrando in questo momento.

import csi
from microdot import Response

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

@app.get('/snapshot.jpg')
async def snapshot(request):
    img = csi0.snapshot().compress(quality=85)
    return Response(
        body=img.bytearray(),
        headers={'Content-Type': 'image/jpeg'},
    )

Visita http://<cam-ip>/snapshot.jpg da un browser e un JPEG della vista corrente riempie la scheda. Aggiorna e ne ottieni uno nuovo.

10.2.1. L’oggetto Response

Un handler che restituisce un dict lascia che sia microdot a fare il resto. I byte JPEG richiedono la forma estesa: una microdot.Response costruita esplicitamente. L’argomento body accetta qualsiasi valore di tipo bytes: il buffer image.Image della camera è esposto tramite bytearray(), così lo stesso buffer in cui il sensore ha scritto va dritto al socket.

Content-Type: image/jpeg è ciò che dice al browser di renderizzare il corpo come immagine. Senza, il browser proverebbe a visualizzare i byte JPEG come testo e vedresti uno schermo pieno di caratteri illeggibili.

image.Image.compress() esegue la codifica JPEG sul buffer dell’immagine esistente in place e restituisce la stessa immagine (ora in formato JPEG) così che i suoi byte possano essere inviati così come sono. quality=85 è il valore predefinito abituale: abbastanza alto perché l’immagine sia nitida, abbastanza basso perché il file passi attraverso un collegamento lento.

10.2.2. La cattura blocca il loop

csi.CSI.snapshot() attende che il sensore finisca di esporre e trasferire in DMA un frame prima di restituire. All’interno di un handler asincrono ciò significa che l’event loop si ferma per la durata dell’esposizione: dieci, venti, cinquanta millisecondi a seconda dell’illuminazione. Con un solo client che richiede una sola rotta alla volta tutto questo è invisibile; con più client, o con una coroutine di cattura in esecuzione in parallelo, bloccherebbe tutto il resto.

Esiste una variante non bloccante di snapshot() per il caso a più coroutine (blocking=False restituisce il prossimo frame pronto oppure None). Per uno snapshot per richiesta, la chiamata bloccante predefinita va bene.

Il proprietario può ora richiamare un URL e ottenere un frame nuovo.