10.8. Tweerichtingsbesturing met WebSockets¶
Server-Sent Events zijn alleen-pushen. Wanneer de eigenaar op het dashboard op “sla nu een snapshot op” of “reset de trigger-teller” tikt, moet het dashboard een bericht naar de cam sturen. Dat zijn WebSockets – één TCP-socket, met geframede berichten die beide kanten op stromen.
10.8.1. De /control-route¶
microdot.websocket.with_websocket() voert de WebSocket-upgrade-handshake uit en geeft de handler een WebSocket-object. De handler loopt eindeloos door, leest commando’s en stuurt bevestigingen:
from microdot.websocket import with_websocket
from microdot.websocket import WebSocketError
import json
@app.get('/control')
@with_websocket
async def control(request, ws):
while True:
try:
msg = await ws.receive()
except WebSocketError:
break
try:
cmd = json.loads(msg)
except ValueError:
await ws.send({'error': 'bad json'})
continue
if cmd.get('cmd') == 'snapshot_now':
if latest_jpeg:
path = '/sdcard/snaps/manual-{}.jpg'.format(
int(time.time()))
with open(path, 'wb') as f:
f.write(latest_jpeg)
await ws.send({'ok': True, 'saved': path})
else:
await ws.send({'ok': False, 'reason': 'no frame yet'})
elif cmd.get('cmd') == 'reset':
state['trigger_count'] = 0
await ws.send({'ok': True, 'counters': 'reset'})
else:
await ws.send({'error': 'unknown command'})
receive() geeft een string terug voor tekstframes en bytes voor binaire frames. De WebSocket.send(...) aan de browserzijde stuurt standaard tekst, dus JSON-gecodeerde commando’s zijn de natuurlijke keuze.
send() accepteert strings, bytes of alles wat JSON-serialiseerbaar is – een dict wordt verstuurd als een JSON-tekstframe.
WebSocketError wordt opgeworpen wanneer de client de verbinding verbreekt (nette afsluiting, netwerkonderbreking of protocolfout). De handler verlaat de lus en keert terug; microdot ruimt de socket op.
10.8.2. Het dashboard verstuurt commando’s¶
Er komen twee knoppen in index.html naast de schuifregelaar:
<button id="snap-btn">Save snapshot</button>
<button id="reset-btn">Reset counter</button>
en app.js opent de WebSocket eenmaal en koppelt beide knoppen aan send:
const proto = location.protocol === 'https:' ? 'wss://' : 'ws://';
const ws = new WebSocket(proto + location.host + '/control');
document.getElementById('snap-btn').addEventListener('click', () =>
ws.send(JSON.stringify({cmd: 'snapshot_now'})));
document.getElementById('reset-btn').addEventListener('click', () =>
ws.send(JSON.stringify({cmd: 'reset'})));
ws.addEventListener('message', (e) => {
console.log('cam:', JSON.parse(e.data));
});
De keuze tussen ws:// en wss:// weerspiegelt die tussen http:// en https:// – de WebSocket erft dezelfde TLS-afhandeling. Met HTTPS op zijn plaats maakt het dashboard automatisch verbinding via wss://.
10.8.3. Wanneer kies je SSE versus WebSockets¶
Gebruik SSE wanneer de cam pusht en de browser alleen luistert – meldingen, telemetrie, statuswijzigingen. De verbinding is gewone HTTP, de clientzijde is één regel (new EventSource), en herverbinden gaat automatisch.
Gebruik WebSockets wanneer de browser ook moet pushen – knoppen, schuifregelaars die updates met toetsaanslag-snelheid versturen, alles waarbij wachten op het volgende verzoek te traag zou zijn. De verbinding is bidirectioneel en geframed, maar de API is aan beide kanten ingewikkelder.
De cam is nu een interactief geheel – kijken, gebeurtenissen naar buiten pushen en commando’s binnen accepteren.