10.13. Przesyłanie wyzwolonych ramek do chmury

Gdy ruch wyzwoli zdarzenie, kamera podświetla teraz panel. To wystarcza do użytku na żywo, ale właściciel chce też mieć trwałe archiwum każdej wyzwolonej ramki, przechowywane gdzieś poza kamerą. To wychodzące wywołanie HTTP – kamera działa jako klient.

10.13.1. Kamera jako klient

Moduł requests to wychodzący klient HTTP kamery. Jego interfejs jest celowym odwzorowaniem requests z CPythona – te same funkcje modułu nazwane czasownikami, te same argumenty słowne files=, json=, headers=, auth=. Jeśli wykonywałeś już wywołania HTTP z CPythona, znasz to 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() otwiera połączenie TCP, wysyła żądanie i zwraca Response z status_code, reason, headers, content, json() oraz pozostałymi znanymi właściwościami.

files={...} buduje treść typu multipart/form-data. Wartością jest krotka (filename, file-like); requests.post() odczytuje obiekt plikopodobny w blokach, dzięki czemu cały JPEG nie musi być najpierw ponownie buforowany do łańcucha znaków. io.BytesIO opakowuje obecne już w pamięci bajty JPEG, tak aby udostępniały interfejs odczytu jak z pliku.

headers={...} to zwykły słownik wysyłany jako nagłówki żądania – tutaj token typu bearer na standardowej pozycji Authorization. Dostawca archiwum dokumentuje, jakiego formatu tokenu oczekuje; przykład pokazuje najczęstszą postać.

10.13.2. Wpięcie tego w detektor ruchu

Wprowadzona wcześniej korutyna detektora ruchu działa już na każdej nowej ramce i wyzwala się, gdy change > state['threshold']. Dodaj tam przesyłanie, ale uruchom je jako zadanie w tle, aby detektor nie przestawał obserwować, dopóki trwa przesyłanie:

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() planuje korutynę przesyłania i natychmiast wraca. Detektor dalej pobiera ramki; przesyłanie działa równolegle; kamera nigdy się nie zatrzymuje.

10.13.3. Tryby awarii

Kod sieciowy zawodzi. Kamera może być offline, archiwum może być niedostępne, token bearer mógł wygasnąć. Kategorie warte przechwycenia:

  • OSError – nie udało się otworzyć połączenia TCP lub zostało ono zamknięte w trakcie transferu. Awaria DNS, brak trasy, zresetowanie połączenia. requests zgłasza dokładnie ten wyjątek.

  • status_code >= 400 – serwer otrzymał żądanie i je odrzucił. 401 dla wygasłego tokenu, 403 dla unieważnionego, 413 dla zbyt dużej treści, 5xx gdy archiwum jest niesprawne.

  • Ciche przekroczenie limitu czasu – requests używa domyślnego limitu czasu gniazda (kilka sekund); po jego przekroczeniu zgłasza OSError z errno.ETIMEDOUT.

Dla archiwum, na którym naprawdę zależy, kolejkowałbyś odrzucone ramki do /sdcard/pending/ i ponawiał próby w wolniejszej pętli – to kilka dodatkowych linii na każdy przypadek, ponad to, co pokazano.

10.13.4. Czego requests nie robi

Port dla MicroPythona jest celowo niewielki. Kilka rzeczy, które robi requests z CPythona, a których ten nie robi:

  • Pula połączeń. Każde wywołanie otwiera nowe połączenie TCP.

  • Automatyczne ponawianie przy przejściowych błędach. Opakuj wywołanie samodzielnie.

  • Strumieniowanie odpowiedzi. r.content jest w całości wczytywany do RAM; nie ma odpowiednika stream=True.

  • Automatyczna dekompresja odpowiedzi spakowanych gzipem. Ustaw nagłówek Accept-Encoding jawnie tylko wtedy, gdy serwer jest do tego skonfigurowany.

Pełną listę metod oraz to, co jest w zakresie i poza nim, znajdziesz w requests — Klient HTTP.

HTTPS działa od razu – napędza go schemat URL, a domyślny kontekst SSL jest tworzony w locie. Aby zweryfikować certyfikat archiwum względem pakietu CA wczytanego na kamerę, zobacz sekcję jako klient w Weryfikacja publicznego serwera (kamera jako klient).

Aplikacja jest w pełni gotowa: podgląd na żywo, wykrywanie ruchu, panel z logowaniem, HTTPS, CORS/CSRF, archiwum w chmurze.