10.13. Загрузка кадров по триггеру в облако¶
Теперь при срабатывании движения камера подсвечивает панель управления. Этого достаточно для живого использования, но владельцу также нужен постоянный архив каждого кадра, снятого по триггеру, который хранится где-то за пределами камеры. Это исходящий HTTP-вызов – камера выступает в роли клиента.
10.13.1. Камера как клиент¶
Модуль requests – это исходящий HTTP-клиент камеры. Его интерфейс намеренно повторяет requests из CPython – те же функции модуля, названные по HTTP-глаголам, те же именованные аргументы 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() читает файлоподобный объект порциями, так что весь JPEG не приходится заранее перезагружать в строку. io.BytesIO оборачивает уже находящиеся в памяти байты JPEG, чтобы они предоставляли интерфейс чтения как у файла.
headers={...} – это обычный словарь, который отправляется как заголовки запроса; здесь это bearer-токен в стандартной позиции 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. Режимы отказа¶
Сетевой код даёт сбои. Камера может быть офлайн, архив может быть недоступен, bearer-токен может истечь. Категории, которые стоит перехватывать:
OSError– TCP-соединение не удалось открыть или оно закрылось в середине передачи. Сбой DNS, нет маршрута, сброс соединения.requestsвозбуждает именно это исключение.status_code >= 400– сервер получил запрос и отклонил его. 401 для истёкшего токена, 403 для отозванного, 413 для слишком большого тела, 5xx если архив нездоров.Тихий тайм-аут –
requestsиспользует тайм-аут сокета по умолчанию (несколько секунд); по его истечении возбуждаетсяOSErrorсerrno.ETIMEDOUT.
Для архива, который действительно важен, вы поставили бы отклонённые кадры в очередь в /sdcard/pending/ и повторяли бы попытки в более медленном цикле – это ещё несколько строк на каждый случай, помимо показанного.
10.13.4. Чего requests не делает¶
Порт для MicroPython намеренно сделан небольшим. Несколько вещей, которые CPython-овский requests делает, а этот – нет:
Пул соединений. Каждый вызов открывает новое TCP-соединение.
Автоматические повторы при временных ошибках. Оберните вызов самостоятельно.
Потоковые ответы.
r.contentцеликом читается в ОЗУ; эквивалентаstream=Trueнет.Автоматическая распаковка ответов, сжатых gzip. Устанавливайте заголовок
Accept-Encodingявно только если сервер настроен на это.
Полный список методов и того, что входит и не входит в область применения, см. в requests — HTTP-клиент.
HTTPS работает из коробки – его задаёт схема URL, а SSL-контекст по умолчанию создаётся на лету. О проверке сертификата архива по набору CA, загруженному на камеру, см. раздел как клиент в Проверка публичного сервера (камера в роли клиента).
Приложение полностью готово: живой предпросмотр, обнаружение движения, панель управления с авторизацией, HTTPS, CORS/CSRF, облачный архив.