10.8. Controllo bidirezionale con i WebSocket¶
I Server-Sent Events sono solo in push. Quando il proprietario tocca «salva subito uno snapshot» o «azzera il contatore degli attivamenti» sulla dashboard, la dashboard deve inviare un messaggio alla camera. Questo è ciò che fanno i WebSocket: un unico socket TCP, con messaggi incapsulati che scorrono in entrambe le direzioni.
10.8.1. La route /control¶
microdot.websocket.with_websocket() esegue l’handshake di upgrade del WebSocket e passa all’handler un oggetto WebSocket. L’handler entra in un ciclo infinito, leggendo i comandi e inviando conferme:
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() restituisce una stringa per i frame di testo e byte per i frame binari. Il WebSocket.send(...) lato browser invia testo per impostazione predefinita, quindi i comandi codificati in JSON sono la scelta naturale.
send() accetta stringhe, byte o qualsiasi oggetto serializzabile in JSON: un dict viene inviato come frame di testo JSON.
WebSocketError viene sollevata quando il client si disconnette (chiusura pulita, caduta della rete o errore di protocollo). L’handler esce dal ciclo e ritorna; microdot si occupa di ripulire il socket.
10.8.2. La dashboard invia comandi¶
Due pulsanti vengono aggiunti in index.html accanto allo slider:
<button id="snap-btn">Save snapshot</button>
<button id="reset-btn">Reset counter</button>
e app.js apre il WebSocket una sola volta e collega entrambi i pulsanti a 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));
});
La scelta tra ws:// e wss:// rispecchia quella tra http:// e https://: il WebSocket eredita la stessa gestione TLS. Con HTTPS attivo, la dashboard si connette automaticamente tramite wss://.
10.8.3. Quando scegliere SSE o WebSocket¶
Usa SSE quando la camera invia in push e il browser si limita ad ascoltare: notifiche, telemetria, cambiamenti di stato. Il canale è semplice HTTP, il lato client è una sola riga (new EventSource) e la riconnessione è automatica.
Usa i WebSocket quando anche il browser deve inviare in push: pulsanti, slider che inviano aggiornamenti alla velocità di battitura, qualunque cosa in cui attendere la richiesta successiva sarebbe troppo lento. La connessione è bidirezionale e incapsulata in frame, ma l’API è più articolata su entrambi i lati.
La camera è ora un dispositivo interattivo: osserva, invia eventi all’esterno e accetta comandi in ingresso.