10.8. Kétirányú vezérlés WebSocketekkel

A Server-Sent Events csak küldésre képes. Amikor a tulajdonos megérinti a „pillanatkép mentése most” vagy a „kiváltási számláló nullázása” gombot az irányítópulton, az irányítópultnak üzenetet kell küldenie a kamerának. Ez a WebSockets – egyetlen TCP-socket, mindkét irányba áramló keretezett üzenetek.

10.8.1. A /control útvonal

A microdot.websocket.with_websocket() végrehajtja a WebSocket-frissítési kézfogást, és átad a kezelőnek egy WebSocket objektumot. A kezelő végtelen ciklusban fut, parancsokat olvas és nyugtázásokat küld:

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'})

A receive() szöveges keretek esetén stringet, bináris keretek esetén bájtokat ad vissza. A böngészőoldali WebSocket.send(...) alapértelmezetten szöveget küld, így a JSON-kódolt parancsok a természetes választás.

A send() stringeket, bájtokat vagy bármilyen JSON-serializálható objektumot elfogad – egy dict JSON szöveges keretként kerül elküldésre.

A WebSocketError akkor kerül dobásra, amikor a kliens lekapcsolódik (tiszta lezárás, hálózati megszakadás vagy protokollhiba). A kezelő kilép a ciklusból és visszatér; a microdot rendet rak a socket körül.

10.8.2. Az irányítópult parancsokat küld

Két gomb kerül az index.html fájlba a csúszka mellé:

<button id="snap-btn">Save snapshot</button>
<button id="reset-btn">Reset counter</button>

az app.js pedig egyszer megnyitja a WebSocketet, és mindkét gombot a send függvényhez köti:

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));
});

A ws:// és wss:// közötti választás tükrözi a http:// és https:// közöttit – a WebSocket ugyanazt a TLS-kezelést örökli. Ha a HTTPS a helyén van, az irányítópult automatikusan wss:// segítségével kapcsolódik.

10.8.3. Mikor válasszunk SSE-t és mikor WebSocketet

Használj SSE-t, amikor a kamera küld, és a böngésző csak figyel – értesítések, telemetria, állapotváltozások. A vezeték egyszerű HTTP, a kliensoldal egyetlen sor (new EventSource), és az újrakapcsolódás automatikus.

Használj WebSocketeket, amikor a böngészőnek is küldenie kell – gombok, billentyűleütés-sebességű frissítéseket küldő csúszkák, bármi, ahol a következő kérésre várás túl lassú lenne. A kapcsolat kétirányú és keretezett, de az API mindkét oldalon több munkát igényel.

A kamera most már interaktív dolog – figyel, eseményeket küld ki, és parancsokat fogad be.