10.13. Uploading triggered frames to the cloud¶
When motion fires the cam now lights up the dashboard. That’s enough for live use, but the owner also wants a permanent archive of every triggered frame, stored somewhere outside the cam. That’s an outbound HTTP call – the cam acting as a client.
10.13.1. The cam as a client¶
The requests module is the cam’s outbound HTTP client. Its
surface is a deliberate copy of CPython’s requests – the same
verb-named module functions, the same files=, json=,
headers=, auth= keyword arguments. If you’ve made HTTP calls
from CPython you already know the 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() opens a TCP connection, sends the request, and
returns a Response with status_code,
reason, headers, content, json(), and the rest of
the familiar properties.
files={...} builds a multipart/form-data body. The value is a
(filename, file-like) tuple; requests.post() reads the
file-like in chunks so the whole JPEG doesn’t have to be re-buffered
into a string first. io.BytesIO wraps the already-in-memory
JPEG bytes so they expose a read-as-file interface.
headers={...} is a straight dict that gets sent as request
headers – here, a bearer token in the standard Authorization
position. The archive provider documents what token format they want;
the example is the most common form.
10.13.2. Wiring it into the motion detector¶
The motion-detector coroutine introduced earlier already runs on
every new frame and fires when change > state['threshold']. Add
the upload there, but fire it as a background task so the detector
doesn’t stop watching while the upload is in flight:
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() schedules the upload coroutine and
returns immediately. The detector keeps grabbing frames; the upload
runs alongside it; the cam never stalls.
10.13.3. Failure modes¶
Network code fails. The cam might be offline, the archive might be down, the bearer token might have expired. The categories worth catching:
OSError– the TCP connection couldn’t be opened or was closed mid-transfer. DNS failure, no route, connection reset.requestsraises this exact exception.status_code >= 400– the server received the request and rejected it. 401 for an expired token, 403 for a revoked one, 413 for a too-large body, 5xx for the archive being unhealthy.Silent timeout –
requestsuses a default socket timeout (a few seconds); past that it raisesOSErrorwitherrno.ETIMEDOUT.
For an archive that genuinely matters, you’d queue rejected frames to
/sdcard/pending/ and retry on a slower loop – that’s a few more
lines per case, on top of what’s shown.
10.13.4. What requests doesn’t do¶
The MicroPython port is deliberately small. A few things the CPython
requests does that this one doesn’t:
Connection pooling. Every call opens a new TCP connection.
Automatic retries on transient errors. Wrap the call yourself.
Streaming responses.
r.contentis read into RAM in full; there’s nostream=Trueequivalent.Automatic decompression of gzipped responses. Set the
Accept-Encodingheader explicitly only if the server is configured for it.
See requests — HTTP client for the full method list and what’s in / out of scope.
HTTPS works out of the box – the URL scheme drives it, and the default SSL context is created on the fly. For verifying the archive’s cert against a CA bundle you’ve loaded onto the cam, see the as a client section of Verifying a public server (camera as client).
The app is fully shipped: live preview, motion detection, dashboard with login, HTTPS, CORS/CSRF, cloud archive.