10.13. Ladda upp utlösta bildrutor till molnet

När rörelse utlöser tänder kameran nu upp instrumentpanelen. Det räcker för direktanvändning, men ägaren vill också ha ett permanent arkiv av varje utlöst bildruta, lagrat någonstans utanför kameran. Det är ett utgående HTTP-anrop – kameran fungerar som en klient.

10.13.1. Kameran som klient

Modulen requests är kamerans utgående HTTP-klient. Dess gränssnitt är en avsiktlig kopia av CPythons requests – samma verbnamngivna modulfunktioner, samma nyckelordsargument files=, json=, headers=, auth=. Om du har gjort HTTP-anrop från CPython kan du redan API:et:

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

requests.post() öppnar en TCP-anslutning, skickar förfrågan och returnerar ett Response med status_code, reason, headers, content, json() och resten av de bekanta egenskaperna.

files={...} bygger en multipart/form-data-kropp. Värdet är en (filename, file-like)-tupel; requests.post() läser det filliknande objektet i bitar så att hela JPEG-bilden inte behöver buffras om till en sträng först. io.BytesIO omsluter de JPEG-byte som redan finns i minnet så att de exponerar ett läs-som-fil-gränssnitt.

headers={...} är en vanlig dict som skickas som förfrågningshuvuden – här en bearer-token i den standardiserade Authorization-positionen. Arkivleverantören dokumenterar vilket tokenformat de vill ha; exemplet är den vanligaste formen.

10.13.2. Koppla in det i rörelsedetektorn

Rörelsedetektor-koroutinen som introducerades tidigare körs redan vid varje ny bildruta och utlöses när change > state['threshold']. Lägg till uppladdningen där, men kör den som en bakgrundsuppgift så att detektorn inte slutar bevaka medan uppladdningen pågår:

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)

asyncio.create_task() schemalägger uppladdningskoroutinen och returnerar omedelbart. Detektorn fortsätter att hämta bildrutor; uppladdningen körs parallellt med den; kameran låser sig aldrig.

10.13.3. Felscenarier

Nätverkskod misslyckas. Kameran kan vara offline, arkivet kan vara nere, bearer-token kan ha löpt ut. De kategorier som är värda att fånga:

  • OSError – TCP-anslutningen kunde inte öppnas eller stängdes mitt i överföringen. DNS-fel, ingen rutt, anslutning återställd. requests genererar exakt detta undantag.

  • status_code >= 400 – servern tog emot förfrågan och avvisade den. 401 för en utgången token, 403 för en återkallad, 413 för en för stor kropp, 5xx för att arkivet inte mår bra.

  • Tyst timeout – requests använder en standardtimeout för socketen (några sekunder); efter det genererar den OSError med errno.ETIMEDOUT.

För ett arkiv som verkligen är viktigt skulle du köa avvisade bildrutor till /sdcard/pending/ och försöka igen i en långsammare slinga – det är några rader till per fall, utöver det som visas.

10.13.4. Vad requests inte gör

MicroPython-porten är avsiktligt liten. Några saker som CPythons requests gör som den här inte gör:

  • Anslutningspoolning. Varje anrop öppnar en ny TCP-anslutning.

  • Automatiska omförsök vid tillfälliga fel. Linda in anropet själv.

  • Strömmande svar. r.content läses in i sin helhet i RAM; det finns ingen motsvarighet till stream=True.

  • Automatisk dekomprimering av gzip-komprimerade svar. Ange huvudet Accept-Encoding explicit endast om servern är konfigurerad för det.

Se requests — HTTP-klient för den fullständiga metodlistan och vad som ingår respektive inte ingår i omfattningen.

HTTPS fungerar direkt – URL-schemat styr det, och standard-SSL-kontexten skapas i farten. För att verifiera arkivets certifikat mot ett CA-paket som du har laddat upp på kameran, se avsnittet som en klient i Verifiera en publik server (kameran som klient).

Appen är fullt levererad: direktförhandsgranskning, rörelsedetektering, instrumentpanel med inloggning, HTTPS, CORS/CSRF, molnarkiv.