mirror of
https://github.com/ruvnet/RuView.git
synced 2026-04-28 05:59:32 +00:00
Merge 4d3cd09286 into 79477c17a9
This commit is contained in:
commit
fb4b9a3ae0
3 changed files with 84 additions and 23 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue