This commit is contained in:
Sai Bharani Veerepalli 2026-04-21 02:43:31 +04:00 committed by GitHub
commit fb4b9a3ae0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 84 additions and 23 deletions

View file

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

View file

@ -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.
@ -313,20 +313,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();
}
@ -349,7 +394,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);
}

View file

@ -107,16 +107,29 @@ class PoseService:
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 = {