10.2. Renvoyer une capture

Un point de terminaison d’état c’est bien, mais la raison d’être de la caméra, c’est l’objectif. Ajoutons un point de terminaison qui renvoie le JPEG de ce que le capteur regarde en ce moment.

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'},
    )

Accédez à http://<cam-ip>/snapshot.jpg depuis un navigateur et un JPEG de la vue actuelle remplit l’onglet. Actualisez et vous en obtenez un nouveau.

10.2.1. L’objet Response

Un gestionnaire qui renvoie un dictionnaire laisse microdot faire le reste. Les octets JPEG nécessitent la forme longue : un microdot.Response construit explicitement. L’argument body accepte toute valeur de type bytes – le tampon image.Image de la caméra est exposé via bytearray(), de sorte que le tampon même dans lequel le capteur a écrit part directement vers le socket.

Content-Type: image/jpeg est ce qui indique au navigateur de restituer le corps en tant qu’image. Sans cela, le navigateur tenterait d’afficher les octets JPEG comme du texte et vous verriez tout un écran de charabia.

image.Image.compress() effectue l’encodage JPEG sur le tampon d’image existant sur place et renvoie la même image (désormais au format JPEG) afin que ses octets puissent être envoyés tels quels. quality=85 est la valeur par défaut habituelle – assez élevée pour que l’image soit nette, assez basse pour que le fichier passe par une liaison lente.

10.2.2. La capture bloque la boucle

csi.CSI.snapshot() attend que le capteur ait fini d’exposer et de transférer une trame par DMA avant de renvoyer. À l’intérieur d’un gestionnaire asynchrone, cela signifie que la boucle d’événements se fige pendant toute la durée de l’exposition – dix, vingt, cinquante millisecondes selon l’éclairage. Avec un seul client interrogeant une seule route à la fois, c’est invisible ; avec plusieurs clients, ou une coroutine de capture s’exécutant en parallèle, cela bloquerait tout le reste.

Une variante non bloquante de snapshot() existe pour le cas multi-coroutines (blocking=False renvoie la prochaine trame prête ou None). Pour une capture par requête, l’appel bloquant par défaut convient.

Le propriétaire peut désormais solliciter une URL et obtenir une trame fraîche.