10.13. Caricamento dei frame attivati sul cloud¶
Quando viene rilevato del movimento, la camera ora illumina la dashboard. Per l’uso dal vivo questo basta, ma il proprietario desidera anche un archivio permanente di ogni frame attivato, conservato da qualche parte all’esterno della camera. Si tratta di una chiamata HTTP in uscita, con la camera che agisce da client.
10.13.1. La camera come client¶
Il modulo requests è il client HTTP in uscita della camera. La sua interfaccia è una copia deliberata di requests di CPython: le stesse funzioni del modulo che prendono il nome dai verbi, gli stessi argomenti keyword files=, json=, headers=, auth=. Se hai già effettuato chiamate HTTP da CPython, conosci già l’API:
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() apre una connessione TCP, invia la richiesta e restituisce un oggetto Response con status_code, reason, headers, content, json() e le altre proprietà familiari.
files={...} costruisce un corpo multipart/form-data. Il valore è una tupla (filename, file-like); requests.post() legge l’oggetto file-like a blocchi, in modo che l’intero JPEG non debba essere prima ribufferizzato in una stringa. io.BytesIO avvolge i byte JPEG già presenti in memoria, così da esporli con un’interfaccia di lettura come file.
headers={...} è un semplice dict che viene inviato come header della richiesta: qui, un bearer token nella posizione standard Authorization. Il fornitore dell’archivio documenta quale formato di token si aspetta; l’esempio mostra la forma più comune.
10.13.2. Integrazione nel rilevatore di movimento¶
La coroutine del rilevatore di movimento introdotta in precedenza viene già eseguita a ogni nuovo frame e si attiva quando change > state['threshold']. Aggiungi qui il caricamento, ma avvialo come task in background in modo che il rilevatore non smetta di osservare mentre il caricamento è in corso:
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() pianifica la coroutine di caricamento e restituisce immediatamente. Il rilevatore continua ad acquisire frame; il caricamento procede in parallelo; la camera non si blocca mai.
10.13.3. Modalità di errore¶
Il codice di rete può fallire. La camera potrebbe essere offline, l’archivio potrebbe non essere disponibile, il bearer token potrebbe essere scaduto. Le categorie che vale la pena intercettare:
OSError– la connessione TCP non ha potuto essere aperta oppure è stata chiusa durante il trasferimento. Errore DNS, nessuna route, connessione resettata.requestssolleva esattamente questa eccezione.status_code >= 400– il server ha ricevuto la richiesta e l’ha rifiutata. 401 per un token scaduto, 403 per uno revocato, 413 per un corpo troppo grande, 5xx per l’archivio non integro.Timeout silenzioso –
requestsutilizza un timeout di socket predefinito (pochi secondi); superato questo, sollevaOSErrorconerrno.ETIMEDOUT.
Per un archivio che conta davvero, accoderesti i frame rifiutati in /sdcard/pending/ e riproveresti con un ciclo più lento: si tratta di poche righe in più per ogni caso, oltre a quanto mostrato.
10.13.4. Cosa requests non fa¶
Il port per MicroPython è volutamente ridotto. Alcune cose che requests di CPython fa e che questo non fa:
Pooling delle connessioni. Ogni chiamata apre una nuova connessione TCP.
Tentativi automatici in caso di errori transitori. Devi avvolgere tu stesso la chiamata.
Risposte in streaming.
r.contentviene letto interamente in RAM; non esiste un equivalente distream=True.Decompressione automatica delle risposte compresse con gzip. Imposta l’header
Accept-Encodingesplicitamente solo se il server è configurato per gestirla.
Consulta requests — Client HTTP per l’elenco completo dei metodi e per ciò che rientra o meno nell’ambito supportato.
Lo HTTPS funziona subito senza configurazione: è lo schema dell’URL a determinarlo e il contesto SSL predefinito viene creato al volo. Per verificare il certificato dell’archivio rispetto a un bundle di CA che hai caricato sulla camera, consulta la sezione as a client di Verificare un server pubblico (camera come client).
L’applicazione è ora completa: anteprima dal vivo, rilevamento del movimento, dashboard con login, HTTPS, CORS/CSRF, archivio sul cloud.