10.13. Kiváltott képkockák feltöltése a felhőbe

Amikor a mozgás aktiválódik, a kamera most kivilágítja az irányítópultot. Ez elegendő az élő használathoz, de a tulajdonos minden kiváltott képkockáról állandó archívumot is szeretne, valahol a kamerán kívül tárolva. Ez egy kimenő HTTP-hívás – a kamera kliensként viselkedik.

10.13.1. A kamera mint kliens

A requests modul a kamera kimenő HTTP-kliense. Felülete a CPython requests moduljának szándékos másolata – ugyanazok az ige nevű modulfüggvények, ugyanazok a files=, json=, headers=, auth= kulcsszavas argumentumok. Ha már intéztél HTTP-hívásokat a CPythonból, akkor már ismered az API-t:

import requests
import io

ARCHIVE_URL = 'https://api.backyard-cloud.com/frames'
ARCHIVE_TOKEN = load_archive_token()

async def archive_frame(jpeg, ts):
    try:
        r = requests.post(
            ARCHIVE_URL,
            files={'image': (
                'frame-{}.jpg'.format(ts),
                io.BytesIO(jpeg),
            )},
            headers={'Authorization': 'Bearer ' + ARCHIVE_TOKEN},
        )
    except OSError as e:
        print('upload failed:', e)
        return False
    if r.status_code >= 400:
        print('archive rejected:', r.status_code, r.reason)
        return False
    return True

A requests.post() megnyit egy TCP-kapcsolatot, elküldi a kérést, és visszaad egy Response objektumot a status_code, reason, headers, content, json() és a többi ismerős tulajdonsággal.

A files={...} egy multipart/form-data törzset épít fel. Az érték egy (filename, file-like) tuple; a requests.post() darabokban olvassa be a fájlszerű objektumot, így az egész JPEG-et nem kell előbb újrapufferelni egy stringbe. A io.BytesIO becsomagolja a memóriában már jelen lévő JPEG-bájtokat, hogy fájlként olvasható felületet biztosítsanak.

A headers={...} egy egyszerű dict, amely kérésfejlécként kerül elküldésre – itt egy bearer token a szabványos Authorization pozícióban. Az archívumszolgáltató dokumentálja, hogy milyen tokenformátumot vár; a példa a leggyakoribb forma.

10.13.2. Bekötés a mozgásérzékelőbe

A korábban bemutatott mozgásérzékelő korutin már minden új képkockán lefut, és akkor aktiválódik, amikor change > state['threshold']. Add hozzá ott a feltöltést, de háttérfeladatként indítsd el, hogy az érzékelő ne hagyja abba a figyelést, amíg a feltöltés folyamatban van:

async def motion_detector():
    global last_motion
    prev = None
    while True:
        await new_frame.wait()
        change = compute_change(prev, latest_jpeg)
        if change > state['threshold']:
            state['trigger_count'] += 1
            ts = int(time.time())
            last_motion = {'ts': ts,
                           'count': state['trigger_count'],
                           'change': change}
            motion_event.set()
            asyncio.create_task(archive_frame(latest_jpeg, ts))
        prev = latest_jpeg
        await asyncio.sleep_ms(50)

Az asyncio.create_task() ütemezi a feltöltési korutint, és azonnal visszatér. Az érzékelő tovább kapja a képkockákat; a feltöltés mellette fut; a kamera soha nem akad meg.

10.13.3. Hibamódok

A hálózati kód meghibásodik. A kamera offline lehet, az archívum leállhat, a bearer token lejárhat. Az érdemes elkapni kategóriák:

  • Az OSError – a TCP-kapcsolatot nem lehetett megnyitni, vagy az átvitel közepén zárult le. DNS-hiba, nincs útvonal, kapcsolat-visszaállítás. A requests pontosan ezt a kivételt dobja.

  • status_code >= 400 – a szerver megkapta a kérést és elutasította azt. 401 lejárt token esetén, 403 visszavont esetén, 413 túl nagy törzs esetén, 5xx ha az archívum nem egészséges.

  • Csendes időtúllépés – a requests egy alapértelmezett socket-időtúllépést használ (néhány másodperc); ezen túl OSError kivételt dob errno.ETIMEDOUT értékkel.

Egy valóban fontos archívum esetén az elutasított képkockákat a /sdcard/pending/ mappába sorolnád, és egy lassabb ciklusban próbálkoznál újra – ez esetenként néhány sornyi pluszt jelent a bemutatottakon felül.

10.13.4. Amit a requests nem csinál

A MicroPython port szándékosan kicsi. Néhány dolog, amit a CPython requests megtesz, ez viszont nem:

  • Kapcsolatok poolingja. Minden hívás új TCP-kapcsolatot nyit.

  • Automatikus újrapróbálkozás átmeneti hibák esetén. Csomagold be magad a hívást.

  • Streamelt válaszok. Az r.content teljes egészében a RAM-ba kerül beolvasásra; nincs stream=True megfelelő.

  • Gzippelt válaszok automatikus kicsomagolása. Csak akkor állítsd be explicit módon az Accept-Encoding fejlécet, ha a szerver erre van konfigurálva.

A teljes metóduslistáért és arról, hogy mi tartozik a hatókörbe és mi nem, lásd: requests — HTTP-kliens.

A HTTPS azonnal működik – az URL-séma vezérli, és az alapértelmezett SSL-kontextus menet közben jön létre. Az archívum tanúsítványának egy a kamerára betöltött CA-csomag elleni ellenőrzéséről lásd a Nyilvános szerver ellenőrzése (a kamera mint kliens) mint kliens szakaszát.

Az alkalmazás teljesen kész: élő előnézet, mozgásérzékelés, irányítópult bejelentkezéssel, HTTPS, CORS/CSRF, felhőarchívum.