10.13. Завантаження спрацьованих кадрів у хмару

Коли рух спрацьовує, камера тепер підсвічує панель моніторингу. Цього достатньо для використання в реальному часі, але власник також хоче постійний архів кожного спрацьованого кадру, що зберігається десь поза камерою. Це вихідний HTTP-виклик – камера діє як клієнт.

10.13.1. Камера як клієнт

Модуль requests є вихідним HTTP-клієнтом камери. Його інтерфейс є навмисною копією requests CPython – ті самі функції модуля з іменами дієслів, ті самі ключові аргументи files=, json=, headers=, auth=. Якщо ви вже робили HTTP-виклики з CPython, ви вже знаєте цей 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() відкриває TCP-з’єднання, надсилає запит і повертає Response з status_code, reason, headers, content, json() та іншими звичними властивостями.

files={...} формує тіло multipart/form-data. Значення є кортежем (filename, file-like); requests.post() читає file-like блоками, тому весь JPEG не потрібно повторно буферизувати в рядок. io.BytesIO обгортає байти JPEG, що вже знаходяться в пам’яті, щоб вони надавали інтерфейс читання файлу.

headers={...} – це простий словник, що надсилається як заголовки запиту – тут, токен носія у стандартній позиції Authorization. Постачальник архіву документує, який формат токена він очікує; наведений приклад є найпоширенішою формою.

10.13.2. Підключення до детектора руху

Корутина детектора руху, введена раніше, вже запускається для кожного нового кадру і спрацьовує, коли change > state['threshold']. Додайте завантаження там, але запустіть його як фонове завдання, щоб детектор не переставав спостерігати під час завантаження:

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() планує корутину завантаження і одразу повертає керування. Детектор продовжує захоплювати кадри; завантаження виконується паралельно; камера ніколи не зависає.

10.13.3. Режими відмов

Мережевий код відмовляє. Камера може бути офлайн, архів може не працювати, токен носія міг закінчитися. Категорії, які варто перехоплювати:

  • OSError – TCP-з’єднання не вдалося відкрити або воно було закрите під час передачі. Помилка DNS, немає маршруту, скидання з’єднання. requests генерує саме це виключення.

  • status_code >= 400 – сервер отримав запит і відхилив його. 401 для простроченого токена, 403 для відкликаного, 413 для надто великого тіла, 5xx, якщо архів несправний.

  • Тихий тайм-аут – requests використовує тайм-аут сокета за замовчуванням (кілька секунд); після цього він генерує OSError з errno.ETIMEDOUT.

Для архіву, який дійсно важливий, ви б ставили відхилені кадри в чергу до /sdcard/pending/ і повторювали спробу в повільнішому циклі – це кілька додаткових рядків на кожен випадок, понад те, що показано.

10.13.4. Що requests не робить

Порт MicroPython навмисно невеликий. Деякі речі, які requests CPython робить, а цей – ні:

  • Пул з’єднань. Кожен виклик відкриває нове TCP-з’єднання.

  • Автоматичні повторні спроби при тимчасових помилках. Обгорніть виклик самостійно.

  • Потокові відповіді. r.content читається в RAM повністю; немає еквіваленту stream=True.

  • Автоматична декомпресія gzip-відповідей. Встановлюйте заголовок Accept-Encoding явно лише якщо сервер налаштований для цього.

Перегляньте requests — HTTP-клієнт для повного списку методів і того, що входить / виходить за рамки.

HTTPS працює з коробки – схема URL керує ним, і контекст SSL за замовчуванням створюється на льоту. Щоб перевірити сертифікат архіву за пакетом CA, завантаженим на камеру, перегляньте розділ як клієнт у Перевірка публічного сервера (камера як клієнт).

Застосунок повністю готовий: живий перегляд, виявлення руху, панель моніторингу з входом, HTTPS, CORS/CSRF, хмарний архів.