10.8. Contrôle bidirectionnel avec les WebSockets¶
Les Server-Sent Events sont en envoi unique (push uniquement). Lorsque le propriétaire appuie sur « enregistrer une capture maintenant » ou « réinitialiser le compteur de déclenchements » sur le tableau de bord, celui-ci doit envoyer un message vers la caméra. C’est le rôle des WebSockets – un seul socket TCP, des messages encadrés circulant dans les deux sens.
10.8.1. La route /control¶
microdot.websocket.with_websocket() effectue la poignée de main de mise à niveau WebSocket et transmet au gestionnaire un objet WebSocket. Le gestionnaire boucle indéfiniment, lisant les commandes et envoyant des accusés de réception :
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() renvoie une chaîne pour les trames texte et des octets pour les trames binaires. Côté navigateur, WebSocket.send(...) envoie du texte par défaut, donc les commandes encodées en JSON sont le choix naturel.
send() accepte des chaînes, des octets ou tout objet sérialisable en JSON – un dictionnaire est envoyé sous forme de trame texte JSON.
WebSocketError est levée lorsque le client se déconnecte (fermeture propre, coupure réseau ou erreur de protocole). Le gestionnaire sort de la boucle et retourne ; microdot nettoie le socket.
10.8.2. Le tableau de bord envoie des commandes¶
Deux boutons sont ajoutés dans index.html à côté du curseur :
<button id="snap-btn">Save snapshot</button>
<button id="reset-btn">Reset counter</button>
et app.js ouvre le WebSocket une seule fois et relie les deux boutons à 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));
});
Le choix entre ws:// et wss:// reflète celui entre http:// et https:// – le WebSocket hérite de la même gestion TLS. Une fois le HTTPS en place, le tableau de bord se connecte automatiquement via wss://.
10.8.3. Quand choisir SSE plutôt que WebSockets¶
Utilisez SSE lorsque la caméra émet et que le navigateur se contente d’écouter – notifications, télémétrie, changements d’état. Le canal est du HTTP simple, le côté client tient en une ligne (new EventSource) et la reconnexion est automatique.
Utilisez les WebSockets lorsque le navigateur doit aussi émettre – boutons, curseurs envoyant des mises à jour au rythme des frappes, tout ce pour quoi attendre la prochaine requête serait trop lent. La connexion est bidirectionnelle et encadrée, mais l’API est plus complexe des deux côtés.
La caméra est désormais un objet interactif – on observe, on émet des événements et on accepte des commandes en retour.