10.13. Enviando quadros disparados para a nuvem¶
Quando o movimento dispara, a câmera agora acende o painel. Isso é suficiente para uso ao vivo, mas o proprietário também quer um arquivo permanente de cada quadro disparado, armazenado em algum lugar fora da câmera. Isso é uma chamada HTTP de saída – a câmera atuando como um cliente.
10.13.1. A câmera como cliente¶
O módulo requests é o cliente HTTP de saída da câmera. Sua interface é uma cópia proposital do requests do CPython – as mesmas funções de módulo nomeadas por verbos, os mesmos argumentos nomeados files=, json=, headers=, auth=. Se você já fez chamadas HTTP a partir do CPython, já conhece a 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() abre uma conexão TCP, envia a requisição e retorna uma Response com status_code, reason, headers, content, json() e o restante das propriedades familiares.
files={...} constrói um corpo multipart/form-data. O valor é uma tupla (filename, file-like); requests.post() lê o objeto file-like em blocos para que todo o JPEG não tenha que ser rebufferizado em uma string primeiro. io.BytesIO envolve os bytes JPEG já existentes na memória para que exponham uma interface de leitura como arquivo.
headers={...} é um dict direto que é enviado como cabeçalhos da requisição – aqui, um token bearer na posição Authorization padrão. O provedor de arquivamento documenta o formato de token que deseja; o exemplo mostra a forma mais comum.
10.13.2. Conectando ao detector de movimento¶
A corrotina de detecção de movimento apresentada anteriormente já roda a cada novo quadro e dispara quando change > state['threshold']. Adicione o envio ali, mas dispare-o como uma tarefa em segundo plano para que o detector não pare de observar enquanto o envio está em andamento:
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() agenda a corrotina de envio e retorna imediatamente. O detector continua capturando quadros; o envio roda em paralelo a ele; a câmera nunca trava.
10.13.3. Modos de falha¶
Código de rede falha. A câmera pode estar offline, o arquivo pode estar fora do ar, o token bearer pode ter expirado. As categorias que vale a pena capturar:
OSError– a conexão TCP não pôde ser aberta ou foi fechada no meio da transferência. Falha de DNS, sem rota, conexão resetada.requestslança exatamente essa exceção.status_code >= 400– o servidor recebeu a requisição e a rejeitou. 401 para um token expirado, 403 para um revogado, 413 para um corpo grande demais, 5xx para o arquivo estar com problemas.Timeout silencioso –
requestsusa um timeout de socket padrão (alguns segundos); passado isso, ele lançaOSErrorcomerrno.ETIMEDOUT.
Para um arquivo que realmente importa, você enfileiraria os quadros rejeitados em /sdcard/pending/ e os tentaria novamente em um loop mais lento – isso são mais algumas linhas por caso, além do que está mostrado.
10.13.4. O que o requests não faz¶
A versão para MicroPython é propositalmente pequena. Algumas coisas que o requests do CPython faz e que esta versão não faz:
Pool de conexões. Cada chamada abre uma nova conexão TCP.
Novas tentativas automáticas em erros transitórios. Envolva a chamada você mesmo.
Respostas em streaming.
r.contenté lido inteiramente na RAM; não há equivalente astream=True.Descompressão automática de respostas com gzip. Defina o cabeçalho
Accept-Encodingexplicitamente apenas se o servidor estiver configurado para isso.
Veja requests — Cliente HTTP para a lista completa de métodos e o que está dentro ou fora do escopo.
HTTPS funciona imediatamente – o esquema da URL o aciona, e o contexto SSL padrão é criado dinamicamente. Para verificar o certificado do arquivo contra um pacote de CA que você carregou na câmera, veja a seção como cliente de Verificando um servidor público (câmera como cliente).
O aplicativo está totalmente concluído: pré-visualização ao vivo, detecção de movimento, painel com login, HTTPS, CORS/CSRF, arquivamento na nuvem.