10.8. בקרה דו-כיוונית עם WebSockets

Server-Sent Events הם דחיפה בלבד. כאשר הבעלים מקיש על ”save a snapshot right now“ או ”reset the trigger counter“ בלוח הבקרה, על לוח הבקרה לשלוח הודעה אל המצלמה. זה WebSockets – socket אחד של TCP, הודעות ממוסגרות הזורמות בשני הכיוונים.

10.8.1. מסלול /control

microdot.websocket.with_websocket() מבצע את לחיצת היד של שדרוג ה-WebSocket ומוסר למטפל אובייקט WebSocket. המטפל רץ בלולאה לנצח, קורא פקודות ושולח אישורים:

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() מחזיר מחרוזת עבור פריימי טקסט ובייטים עבור פריימים בינאריים. ה-WebSocket.send(...) בצד הדפדפן שולח טקסט כברירת מחדל, ולכן פקודות מקודדות-JSON הן הבחירה הטבעית.

send() מקבל מחרוזות, בייטים, או כל דבר הניתן לסריאליזציה ל-JSON – dict נשלח כפריים טקסט של JSON.

WebSocketError מועלה כשהלקוח מתנתק (סגירה נקייה, ניתוק רשת, או שגיאת פרוטוקול). המטפל יוצא מהלולאה ומחזיר; microdot מסדר את ה-socket.

10.8.2. לוח הבקרה שולח פקודות

שני כפתורים נכנסים אל index.html ליד המחוון:

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

ו-app.js פותח את ה-WebSocket פעם אחת ומחבר את שני הכפתורים אל 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));
});

הבחירה בין ws:// לבין wss:// משקפת את http:// מול https:// – ה-WebSocket יורש את אותו טיפול ב-TLS. כאשר HTTPS קיים, לוח הבקרה מתחבר אוטומטית דרך wss://.

10.8.3. מתי לבחור SSE ומתי WebSockets

השתמש ב-SSE כאשר המצלמה דוחפת והדפדפן רק מאזין – התראות, טלמטריה, שינויי סטטוס. החיווט הוא HTTP פשוט, צד הלקוח הוא שורה אחת (new EventSource), והתחברות מחדש אוטומטית.

השתמש ב-WebSockets כאשר הדפדפן צריך גם לדחוף – כפתורים, מחוונים השולחים עדכונים בקצב הקשות, כל דבר שבו המתנה לבקשה הבאה תהיה איטית מדי. החיבור דו-כיווני וממוסגר, אך ה-API מורכב יותר בשני הצדדים.

המצלמה היא כעת ישות אינטראקטיבית – צפה, דחוף אירועים החוצה, קבל פקודות פנימה.