From 4d3cd092860bc5ecba1c625c6daf5f532cabd6e5 Mon Sep 17 00:00:00 2001 From: Bharani010 Date: Mon, 9 Mar 2026 16:43:34 -0500 Subject: [PATCH] fix: WebSocket connectivity and DensePose initialization in dev mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes 3 bugs preventing UI from connecting to backend when running frontend on :3000 and backend on :8000 (standard dev setup). Bug 1: API config assumed frontend/backend on same origin - ui/config/api.config.js now detects port 3000 and routes to :8000 - buildWsUrl() now uses backend host instead of frontend host Bug 2: Sensing service had wrong WebSocket endpoint and data format - Changed ws://localhost:3000/ws/sensing → ws://localhost:8000/api/v1/stream/pose - Added pose_data → sensing_update format transformation adapter Bug 3: DensePoseHead initialized without required config parameter - Added densepose_config dict with model architecture parameters - Fixes TypeError on backend startup Tested: Windows 11, Python 3.13.2, mock mode Result: Backend starts, WebSocket connects, real-time pose streaming works --- ui/config/api.config.js | 21 +++++----- ui/services/sensing.service.js | 69 +++++++++++++++++++++++++++------ v1/src/services/pose_service.py | 17 +++++++- 3 files changed, 84 insertions(+), 23 deletions(-) diff --git a/ui/config/api.config.js b/ui/config/api.config.js index c4750996..c48a443b 100644 --- a/ui/config/api.config.js +++ b/ui/config/api.config.js @@ -1,13 +1,15 @@ // API Configuration for WiFi-DensePose UI -// Auto-detect the backend URL from the page origin so the UI works whether -// served from Docker (:3000), local dev (:8080), or any other port. -const _origin = (typeof window !== 'undefined' && window.location && window.location.origin) - ? window.location.origin - : 'http://localhost:3000'; +// Backend URL configuration +// When running locally, frontend is on :3000 and backend is on :8000 +const _backendUrl = (typeof window !== 'undefined' && window.location) + ? (window.location.port === '3000' + ? 'http://localhost:8000' // Local dev: frontend on :3000, backend on :8000 + : window.location.origin) // Production/Docker: same origin + : 'http://localhost:8000'; export const API_CONFIG = { - BASE_URL: _origin, + BASE_URL: _backendUrl, API_VERSION: '/api/v1', WS_PREFIX: 'ws://', WSS_PREFIX: 'wss://', @@ -121,9 +123,10 @@ export function buildWsUrl(endpoint, params = {}) { ? API_CONFIG.WSS_PREFIX : API_CONFIG.WS_PREFIX; - // Derive host from the page origin so it works on any port (Docker :3000, dev :8080, etc.) - const host = window.location.host; - let url = `${protocol}${host}${endpoint}`; + // Extract host from BASE_URL (e.g., "http://localhost:8000" → "localhost:8000") + // This ensures WebSocket connects to backend port, not frontend port + const backendHost = API_CONFIG.BASE_URL.replace(/^https?:\/\//, ''); + let url = `${protocol}${backendHost}${endpoint}`; // Add query parameters const queryParams = new URLSearchParams(params); diff --git a/ui/services/sensing.service.js b/ui/services/sensing.service.js index 4931e86e..0f091244 100644 --- a/ui/services/sensing.service.js +++ b/ui/services/sensing.service.js @@ -1,19 +1,19 @@ /** * Sensing WebSocket Service * - * Manages the connection to the Python sensing WebSocket server - * (ws://localhost:8765) and provides a callback-based API for the UI. + * Manages the connection to the Python backend WebSocket server + * (ws://localhost:8000/api/v1/stream/pose) and provides a callback-based API for the UI. * * Falls back to simulated data only after MAX_RECONNECT_ATTEMPTS exhausted. * While reconnecting the service stays in "reconnecting" state and does NOT * emit simulated frames so the UI can clearly distinguish live vs. fallback data. */ -// Derive WebSocket URL from the page origin so it works on any port. -// The /ws/sensing endpoint is available on the same HTTP port (3000). +// Derive WebSocket URL - connect to backend on port 8000 for local dev +const _isLocalDev = (typeof window !== 'undefined' && window.location.port === '3000'); const _wsProto = (typeof window !== 'undefined' && window.location.protocol === 'https:') ? 'wss:' : 'ws:'; -const _wsHost = (typeof window !== 'undefined' && window.location.host) ? window.location.host : 'localhost:3000'; -const SENSING_WS_URL = `${_wsProto}//${_wsHost}/ws/sensing`; +const _wsHost = _isLocalDev ? 'localhost:8000' : window.location.host; +const SENSING_WS_URL = `${_wsProto}//${_wsHost}/api/v1/stream/pose`; const RECONNECT_DELAYS = [1000, 2000, 4000, 8000, 16000]; const MAX_RECONNECT_ATTEMPTS = 20; // Number of failed attempts that must occur before simulation starts. @@ -308,20 +308,65 @@ class SensingService { // ---- Data handling ----------------------------------------------------- _handleData(data) { - this._lastMessage = data; + // Transform backend pose_data format to sensing format for compatibility + let transformedData = data; + + if (data.type === 'pose_data') { + // Backend is sending pose data - transform to sensing format + transformedData = { + type: 'sensing_update', + timestamp: new Date(data.timestamp).getTime() / 1000, + source: 'server-simulated', // Backend is in mock mode + nodes: [{ + node_id: 1, + rssi_dbm: -45, + position: [2, 0, 1.5], + amplitude: [], + subcarrier_count: 0, + }], + features: { + mean_rssi: -45, + variance: data.data?.confidence || 0.5, + std: Math.sqrt(data.data?.confidence || 0.5), + motion_band_power: data.data?.pose?.count > 0 ? 0.15 : 0.05, + breathing_band_power: 0.05, + dominant_freq_hz: 0.3, + change_points: data.data?.pose?.count || 0, + spectral_power: 0.2, + range: 1.5, + iqr: 1.0, + skewness: 0, + kurtosis: 1, + }, + classification: { + motion_level: data.data?.pose?.count > 0 ? 'active' : 'present_still', + presence: data.data?.pose?.count > 0, + confidence: data.data?.confidence || 0.5, + }, + // Store original pose data for components that need it + _pose_data: data.data + }; + } else if (data.type === 'connection_established') { + // Connection confirmation - set data source to server-simulated + console.log('[Sensing] WebSocket connected:', data); + this._setDataSource('server-simulated'); + return; // Don't emit connection message as data + } + + this._lastMessage = transformedData; // Track the server's source field from each frame so the UI // can react if the server switches between esp32 ↔ simulated at runtime. - if (data.source && this._state === 'connected') { - const raw = data.source; + if (transformedData.source && this._state === 'connected') { + const raw = transformedData.source; if (raw !== this._serverSource) { this._applyServerSource(raw); } } // Update RSSI history for sparkline - if (data.features && data.features.mean_rssi != null) { - this._rssiHistory.push(data.features.mean_rssi); + if (transformedData.features && transformedData.features.mean_rssi != null) { + this._rssiHistory.push(transformedData.features.mean_rssi); if (this._rssiHistory.length > this._maxHistory) { this._rssiHistory.shift(); } @@ -330,7 +375,7 @@ class SensingService { // Notify all listeners for (const cb of this._listeners) { try { - cb(data); + cb(transformedData); } catch (e) { console.error('[Sensing] Listener error:', e); } diff --git a/v1/src/services/pose_service.py b/v1/src/services/pose_service.py index f5013c1e..9eeb546c 100644 --- a/v1/src/services/pose_service.py +++ b/v1/src/services/pose_service.py @@ -107,16 +107,29 @@ async def initialize(self): async def _initialize_models(self): """Initialize neural network models.""" try: + # DensePose model configuration + densepose_config = { + 'input_channels': 256, # Feature channels from modality translator + 'num_body_parts': 24, # 24 body parts for DensePose + 'num_uv_coordinates': 2, # U and V coordinates + 'hidden_channels': [256, 128, 64], + 'kernel_size': 3, + 'padding': 1, + 'dropout_rate': 0.1, + 'use_fpn': False, + 'output_stride': 4 + } + # Initialize DensePose model if self.settings.pose_model_path: - self.densepose_model = DensePoseHead() + self.densepose_model = DensePoseHead(densepose_config) # Load model weights if path is provided # model_state = torch.load(self.settings.pose_model_path) # self.densepose_model.load_state_dict(model_state) self.logger.info("DensePose model loaded") else: self.logger.warning("No pose model path provided, using default model") - self.densepose_model = DensePoseHead() + self.densepose_model = DensePoseHead(densepose_config) # Initialize modality translation config = {