10.13. Ausgelöste Einzelbilder in die Cloud hochladen¶
Wenn Bewegung erkannt wird, leuchtet das Dashboard der Kamera jetzt auf. Für den Live-Einsatz reicht das aus, aber der Besitzer möchte zusätzlich ein dauerhaftes Archiv jedes ausgelösten Einzelbildes, das irgendwo außerhalb der Kamera gespeichert wird. Das ist ein ausgehender HTTP-Aufruf – die Kamera tritt als Client auf.
10.13.1. Die Kamera als Client¶
Das Modul requests ist der ausgehende HTTP-Client der Kamera. Seine Schnittstelle ist eine bewusste Nachbildung von CPythons requests – dieselben nach HTTP-Verben benannten Modulfunktionen, dieselben Schlüsselwortargumente files=, json=, headers=, auth=. Wenn Sie schon einmal HTTP-Aufrufe aus CPython gemacht haben, kennen Sie die API bereits:
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() öffnet eine TCP-Verbindung, sendet die Anfrage und gibt ein Response mit status_code, reason, headers, content, json() und den übrigen vertrauten Eigenschaften zurück.
files={...} erstellt einen multipart/form-data-Body. Der Wert ist ein (filename, file-like)-Tupel; requests.post() liest das dateiähnliche Objekt in Blöcken, sodass das gesamte JPEG nicht erst wieder in einen String gepuffert werden muss. io.BytesIO umhüllt die bereits im Speicher befindlichen JPEG-Bytes, sodass sie eine als-Datei-lesbare Schnittstelle bereitstellen.
headers={...} ist ein einfaches Dict, das als Anfrage-Header gesendet wird – hier ein Bearer-Token an der üblichen Authorization-Position. Der Archivanbieter dokumentiert, welches Token-Format er erwartet; das Beispiel zeigt die häufigste Form.
10.13.2. Einbindung in den Bewegungsmelder¶
Die zuvor eingeführte Koroutine des Bewegungsmelders läuft bereits bei jedem neuen Einzelbild und löst aus, wenn change > state['threshold']. Fügen Sie den Upload dort hinzu, starten Sie ihn aber als Hintergrund-Task, damit der Melder während des laufenden Uploads nicht aufhört zu beobachten:
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() plant die Upload-Koroutine ein und kehrt sofort zurück. Der Melder erfasst weiterhin Einzelbilder; der Upload läuft daneben; die Kamera bleibt nie hängen.
10.13.3. Fehlerfälle¶
Netzwerkcode schlägt fehl. Die Kamera könnte offline sein, das Archiv könnte ausgefallen sein, das Bearer-Token könnte abgelaufen sein. Die abzufangenden Kategorien:
OSError– die TCP-Verbindung konnte nicht geöffnet werden oder wurde mitten in der Übertragung geschlossen. DNS-Fehler, keine Route, Verbindungsabbruch.requestslöst genau diese Ausnahme aus.status_code >= 400– der Server hat die Anfrage empfangen und abgelehnt. 401 für ein abgelaufenes Token, 403 für ein widerrufenes, 413 für einen zu großen Body, 5xx, wenn das Archiv nicht gesund ist.Stille Zeitüberschreitung –
requestsverwendet eine standardmäßige Socket-Zeitüberschreitung (einige Sekunden); darüber hinaus löst esOSErrormiterrno.ETIMEDOUTaus.
Für ein Archiv, auf das es wirklich ankommt, würden Sie abgelehnte Einzelbilder in /sdcard/pending/ einreihen und in einer langsameren Schleife erneut versuchen – das sind je nach Fall ein paar Zeilen mehr, zusätzlich zum Gezeigten.
10.13.4. Was requests nicht tut¶
Der MicroPython-Port ist bewusst klein gehalten. Einige Dinge, die CPythons requests tut, dieser hier aber nicht:
Connection-Pooling. Jeder Aufruf öffnet eine neue TCP-Verbindung.
Automatische Wiederholungen bei vorübergehenden Fehlern. Umhüllen Sie den Aufruf selbst.
Streaming von Antworten.
r.contentwird vollständig in den RAM gelesen; es gibt kein Äquivalent zustream=True.Automatische Dekomprimierung von gzip-komprimierten Antworten. Setzen Sie den
Accept-Encoding-Header nur dann explizit, wenn der Server entsprechend konfiguriert ist.
Die vollständige Liste der Methoden und was innerhalb / außerhalb des Geltungsbereichs liegt, finden Sie unter requests — HTTP-Client.
HTTPS funktioniert ohne weiteres Zutun – das URL-Schema steuert es, und der standardmäßige SSL-Kontext wird spontan erstellt. Um das Zertifikat des Archivs gegen ein CA-Bundle zu überprüfen, das Sie auf die Kamera geladen haben, lesen Sie den Abschnitt als Client von Einen öffentlichen Server verifizieren (Kamera als Client).
Die App ist vollständig ausgeliefert: Live-Vorschau, Bewegungserkennung, Dashboard mit Login, HTTPS, CORS/CSRF, Cloud-Archiv.