10.13. Încărcarea cadrelor declanșate în cloud#
Când mișcarea declanșează, camera aprinde acum tabloul de bord. Acest lucru este suficient pentru utilizarea în direct, dar proprietarul dorește și o arhivă permanentă a fiecărui cadru declanșat, stocată undeva în afara camerei. Acesta este un apel HTTP de ieșire – camera acționează ca un client.
10.13.1. Camera ca client#
Modulul requests este clientul HTTP de ieșire al camerei. Suprafața sa este o copie deliberată a modulului requests din CPython – aceleași funcții de modul denumite după verbe, aceleași argumente cu cuvânt-cheie files=, json=, headers=, auth=. Dacă ai făcut apeluri HTTP din CPython, cunoști deja API-ul:
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() deschide o conexiune TCP, trimite cererea și returnează un Response cu status_code, reason, headers, content, json() și restul proprietăților familiare.
files={...} construiește un corp multipart/form-data. Valoarea este un tuplu (filename, file-like); requests.post() citește obiectul de tip fișier în bucăți, astfel încât întregul JPEG să nu trebuiască să fie re-tamponat mai întâi într-un șir de caractere. io.BytesIO încapsulează octeții JPEG deja prezenți în memorie, astfel încât aceștia să expună o interfață de citire ca fișier.
headers={...} este un dicționar simplu care este trimis ca anteturi de cerere – aici, un token bearer în poziția standard Authorization. Furnizorul arhivei documentează formatul de token pe care îl dorește; exemplul este forma cea mai comună.
10.13.2. Conectarea în detectorul de mișcare#
Coroutina detectorului de mișcare introdusă mai devreme rulează deja la fiecare cadru nou și se declanșează când change > state['threshold']. Adaugă încărcarea acolo, dar declanșeaz-o ca sarcină de fundal, astfel încât detectorul să nu se oprească din supraveghere în timp ce încărcarea este în desfășurare:
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() programează coroutina de încărcare și revine imediat. Detectorul continuă să captureze cadre; încărcarea rulează în paralel cu el; camera nu se blochează niciodată.
10.13.3. Moduri de eșec#
Codul de rețea eșuează. Camera ar putea fi offline, arhiva ar putea fi indisponibilă, tokenul bearer ar putea să fi expirat. Categoriile care merită prinse:
OSError– conexiunea TCP nu a putut fi deschisă sau a fost închisă în timpul transferului. Eșec DNS, lipsă de rută, conexiune resetată.requestsridică exact această excepție.status_code >= 400– serverul a primit cererea și a respins-o. 401 pentru un token expirat, 403 pentru unul revocat, 413 pentru un corp prea mare, 5xx pentru o arhivă nesănătoasă.Expirare silențioasă –
requestsfolosește o expirare implicită a socketului (câteva secunde); după aceea ridicăOSErrorcuerrno.ETIMEDOUT.
Pentru o arhivă care contează cu adevărat, ai pune în coadă cadrele respinse în /sdcard/pending/ și ai reîncerca într-o buclă mai lentă – asta înseamnă câteva linii în plus pentru fiecare caz, pe lângă ceea ce este prezentat.
10.13.4. Ce nu face requests#
Portarea pentru MicroPython este în mod deliberat mică. Câteva lucruri pe care requests din CPython le face, dar acesta nu:
Gruparea conexiunilor (connection pooling). Fiecare apel deschide o nouă conexiune TCP.
Reîncercări automate la erori tranzitorii. Învelește apelul tu însuți.
Răspunsuri în flux (streaming).
r.contenteste citit integral în RAM; nu există un echivalentstream=True.Decompresia automată a răspunsurilor comprimate cu gzip. Setează anteturul
Accept-Encodingexplicit doar dacă serverul este configurat pentru asta.
Vezi requests — Client HTTP pentru lista completă de metode și ce intră sau nu în domeniul de aplicare.
HTTPS funcționează imediat – schema URL îl determină, iar contextul SSL implicit este creat din mers. Pentru verificarea certificatului arhivei față de un pachet CA pe care l-ai încărcat pe cameră, vezi secțiunea camera ca client din Verificarea unui server public (camera ca client).
Aplicația este complet livrată: previzualizare în direct, detectare a mișcării, tablou de bord cu autentificare, HTTPS, CORS/CSRF, arhivă în cloud.