10.8. التحكم ثنائي الاتجاه باستخدام WebSockets¶
إن الأحداث المُرسَلة من الخادم (Server-Sent Events) أحادية الاتجاه (دفع فقط). فعندما ينقر المالك على "احفظ لقطة الآن" أو "أعد ضبط عدّاد التفعيل" في لوحة المعلومات، يجب على لوحة المعلومات إرسال رسالة إلى الكاميرا. وهذا ما تؤديه WebSockets -- مقبس 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 -- إذ يُرسَل القاموس بوصفه إطارًا نصيًا من نوع JSON.
يُطلَق WebSocketError عند انقطاع اتصال العميل (إغلاق نظيف، أو انقطاع الشبكة، أو خطأ في البروتوكول). فيخرج المعالج من الحلقة ويعود؛ ويتولى microdot ترتيب المقبس.
10.8.2. لوحة المعلومات ترسل الأوامر¶
يُضاف زرّان إلى index.html بجوار المنزلقة (slider):
<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 عندما يحتاج المتصفح أيضًا إلى الدفع -- مثل الأزرار، والمنزلقات التي ترسل تحديثات بمعدل ضغط المفاتيح، وأي شيء يكون فيه الانتظار حتى الطلب التالي بطيئًا للغاية. فالاتصال ثنائي الاتجاه ومُؤطَّر، لكن الواجهة أكثر تعقيدًا على كلا الجانبين.
أصبحت الكاميرا الآن كيانًا تفاعليًا -- تراقب، وتدفع الأحداث إلى الخارج، وتقبل الأوامر إلى الداخل.