feat: add SocketIO server integration

- Initialize SocketIO with threading mode
- Add WebSocket event handlers (connect, disconnect, subscribe_floor)
- Add broadcast_scan_update() function for pushing scan results
- Integrate broadcast with BT scan endpoint
- Update run_server() to use socketio.run()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
User
2026-02-01 04:55:44 +01:00
parent fed08aa6dd
commit 14757f2e57

View File

@@ -7,7 +7,8 @@ import time
from datetime import datetime
from pathlib import Path
from flask import Flask, jsonify, render_template, request
from flask import Flask, current_app, jsonify, render_template, request
from flask_socketio import SocketIO, emit
from ..scanner import RFScanner
from ..distance import estimate_distance
@@ -16,6 +17,29 @@ from ..bluetooth_identify import identify_single_device, identify_device
from ..database import DeviceDatabase, init_database, get_database
from ..homeassistant import HAWebhooks, HAWebhookConfig
# Module-level SocketIO instance
socketio = SocketIO()
def broadcast_scan_update(app: Flask, devices: list[dict], scan_type: str = "bluetooth"):
"""Broadcast scan results to all connected WebSocket clients."""
sio = app.config.get("SOCKETIO")
if not sio:
return
scanner_identity = app.config.get("SCANNER_IDENTITY", {})
sio.emit(
"scan_update",
{
"type": scan_type,
"timestamp": datetime.now().isoformat(),
"scanner_id": scanner_identity.get("id", "unknown"),
"devices": devices,
},
namespace="/ws/scan",
)
class AutoScanner:
"""Background scanner that runs periodic scans"""
@@ -213,6 +237,10 @@ def create_app(config: Config | None = None) -> Flask:
# Store config reference
app.config["RF_CONFIG"] = config
# Initialize SocketIO with threading mode (compatible with existing threads)
socketio.init_app(app, cors_allowed_origins="*", async_mode="threading")
app.config["SOCKETIO"] = socketio
# Data directory from config
app.config["DATA_DIR"] = config.get_data_dir()
app.config["DATA_DIR"].mkdir(parents=True, exist_ok=True)
@@ -320,6 +348,31 @@ def create_app(config: Config | None = None) -> Flask:
location_label=config.auto_scan.location_label
)
# ==================== WebSocket Event Handlers ====================
@socketio.on("connect", namespace="/ws/scan")
def ws_connect():
"""Handle client connection."""
scanner_id = current_app.config.get("SCANNER_IDENTITY", {}).get("id", "unknown")
emit("connected", {"scanner_id": scanner_id})
print(f"[WS] Client connected: {request.sid}")
@socketio.on("disconnect", namespace="/ws/scan")
def ws_disconnect():
"""Handle client disconnection."""
print(f"[WS] Client disconnected: {request.sid}")
@socketio.on("subscribe_floor", namespace="/ws/scan")
def ws_subscribe_floor(data):
"""Subscribe to floor-specific updates."""
from flask_socketio import join_room
floor = data.get("floor", "all")
join_room(f"floor_{floor}")
emit("subscribed", {"floor": floor})
# ==================== HTTP Routes ====================
@app.route("/")
def index():
"""Main dashboard page"""
@@ -1072,6 +1125,9 @@ def create_app(config: Config | None = None) -> Flask:
scan_type="bluetooth"
)
# Broadcast to WebSocket clients
broadcast_scan_update(current_app, response_data["bluetooth_devices"], "bluetooth")
return jsonify(response_data)
# ==================== Historical Data API ====================
@@ -1476,10 +1532,12 @@ def run_server(
if log_requests:
print(f"Request logging: ENABLED")
print(f"Log output: {config.get_data_dir() / 'logs'}")
print(f"WebSocket: ENABLED (namespace /ws/scan)")
print(f"{'='*60}")
print(f"Server running at: http://{host}:{port}")
print(f"Local access: http://localhost:{port}")
print(f"Network access: http://<your-ip>:{port}")
print(f"{'='*60}\n")
app.run(host=host, port=port, debug=debug)
# Use socketio.run() for WebSocket support
socketio.run(app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True)