10.8. WebSockets ile iki yönlü kontrol

Server-Sent Events yalnızca itme amaçlıdır. Sahibi panoda “hemen şimdi bir anlık görüntü kaydet” veya “tetikleyici sayacını sıfırla” düğmesine dokunduğunda, panonun kameraya bir mesaj göndermesi gerekir. İşte WebSockets bunun içindir; tek bir TCP soketi, her iki yönde de akan çerçeveli mesajlar.

10.8.1. /control rotası

microdot.websocket.with_websocket(), WebSocket yükseltme el sıkışmasını gerçekleştirir ve işleyiciye bir WebSocket nesnesi verir. İşleyici sonsuza kadar döngüye girer, komutları okur ve onaylar gönderir:

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(), metin çerçeveleri için bir dizge ve ikili çerçeveler için bayt döndürür. Tarayıcı tarafındaki WebSocket.send(...) varsayılan olarak metin gönderir, bu nedenle JSON ile kodlanmış komutlar doğal tercihtir.

send(), dizgeleri, baytları veya JSON ile serileştirilebilen herhangi bir şeyi kabul eder; bir sözlük, JSON metin çerçevesi olarak gönderilir.

WebSocketError, istemci bağlantısı kesildiğinde (temiz kapanış, ağ kopması veya protokol hatası) oluşturulur. İşleyici döngüden çıkar ve geri döner; microdot soketi düzenler.

10.8.2. Pano komutları gönderir

Kaydırıcının yanına index.html içine iki düğme eklenir:

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

ve app.js WebSocket’i bir kez açar ve her iki düğmeyi de send ile bağlar:

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

ws:// ve wss:// arasındaki seçim, http:// ve https:// arasındaki seçimi yansıtır; WebSocket aynı TLS işlemesini devralır. HTTPS yürürlükteyken pano otomatik olarak wss:// üzerinden bağlanır.

10.8.3. SSE veya WebSockets ne zaman seçilmeli

Kamera ittiğinde ve tarayıcı yalnızca dinlediğinde SSE kullanın; bildirimler, telemetri, durum değişiklikleri. Hat düz HTTP’dir, istemci tarafı tek satırdır (new EventSource) ve yeniden bağlanma otomatiktir.

Tarayıcının da itmesi gerektiğinde WebSockets kullanın; düğmeler, tuş vuruşu hızında güncelleme gönderen kaydırıcılar, bir sonraki isteği beklemenin çok yavaş olacağı her şey. Bağlantı çift yönlü ve çerçevelidir, ancak API her iki tarafta da daha karmaşıktır.

Kamera artık etkileşimli bir şeydir; izleyin, olayları dışarı itin, komutları içeri alın.