feat: Rust sensing server with full DensePose-compatible API

Replace Python FastAPI + WebSocket servers with a single 2.1MB Rust binary
(wifi-densepose-sensing-server) that serves all UI endpoints:

- REST: /health/*, /api/v1/info, /api/v1/pose/current, /api/v1/pose/stats,
  /api/v1/pose/zones/summary, /api/v1/stream/status
- WebSocket: /api/v1/stream/pose (pose_data with 17 COCO keypoints),
  /ws/sensing (raw sensing_update stream on port 8765)
- Static: /ui/* with no-cache headers

WiFi-derived pose estimation: derive_pose_from_sensing() generates 17 COCO
keypoints from CSI/WiFi signal data with motion-driven animation.

Data sources: ESP32 CSI via UDP :5005, Windows WiFi via netsh, simulation
fallback. Auto-detection probes each in order.

UI changes:
- Point all endpoints to Rust server on :8080 (was Python :8000)
- Fix WebSocket sensing URL to include /ws/sensing path
- Remove sensingOnlyMode gating — all tabs init normally
- Remove api.service.js sensing-only short-circuit
- Fix clearPingInterval bug in websocket.service.js

Also removes obsolete k8s/ template manifests.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-02-28 21:29:45 -05:00
parent cd5943df23
commit d956c30f9e
16 changed files with 1285 additions and 1913 deletions

View File

@@ -65,18 +65,15 @@ class WiFiDensePoseApp {
// Show notification to user
this.showBackendStatus('Mock server active - testing mode', 'warning');
} else {
console.log('🔌 Initializing with real backend');
console.log('🔌 Connecting to backend...');
// Verify backend is actually working
try {
const health = await healthService.checkLiveness();
console.log('✅ Backend is available and responding:', health);
this.showBackendStatus('Connected to real backend', 'success');
console.log('✅ Backend responding:', health);
this.showBackendStatus('Connected to Rust sensing server', 'success');
} catch (error) {
// DensePose API backend not running — sensing-only mode
backendDetector.sensingOnlyMode = true;
console.log(' DensePose API not running — sensing-only mode via WebSocket on :8765');
this.showBackendStatus('Sensing mode — live WiFi data via WebSocket', 'success');
console.warn('⚠️ Backend not available:', error.message);
this.showBackendStatus('Backend unavailable — start sensing-server', 'warning');
}
}
}
@@ -99,36 +96,32 @@ class WiFiDensePoseApp {
this.components.tabManager.onTabChange((newTab, oldTab) => {
this.handleTabChange(newTab, oldTab);
});
}
// Initialize individual tab components
initializeTabComponents() {
// Skip DensePose-dependent tabs in sensing-only mode
const sensingOnly = backendDetector.sensingOnlyMode;
// Dashboard tab
const dashboardContainer = document.getElementById('dashboard');
if (dashboardContainer) {
this.components.dashboard = new DashboardTab(dashboardContainer);
if (!sensingOnly) {
this.components.dashboard.init().catch(error => {
console.error('Failed to initialize dashboard:', error);
});
}
this.components.dashboard.init().catch(error => {
console.error('Failed to initialize dashboard:', error);
});
}
// Hardware tab
const hardwareContainer = document.getElementById('hardware');
if (hardwareContainer) {
this.components.hardware = new HardwareTab(hardwareContainer);
if (!sensingOnly) this.components.hardware.init();
this.components.hardware.init();
}
// Live demo tab
const demoContainer = document.getElementById('demo');
if (demoContainer) {
this.components.demo = new LiveDemoTab(demoContainer);
if (!sensingOnly) this.components.demo.init();
this.components.demo.init();
}
// Sensing tab

View File

@@ -1,7 +1,7 @@
// API Configuration for WiFi-DensePose UI
export const API_CONFIG = {
BASE_URL: 'http://localhost:8000', // FastAPI backend port
BASE_URL: 'http://localhost:8080', // Rust sensing server port
API_VERSION: '/api/v1',
WS_PREFIX: 'ws://',
WSS_PREFIX: 'wss://',
@@ -111,8 +111,8 @@ export function buildWsUrl(endpoint, params = {}) {
? API_CONFIG.WSS_PREFIX
: API_CONFIG.WS_PREFIX;
// Use localhost:8000 for WebSocket connections to match FastAPI backend
const host = 'localhost:8000';
// Match Rust sensing server port
const host = 'localhost:8080';
let url = `${protocol}${host}${endpoint}`;
// Add query parameters

View File

@@ -67,11 +67,6 @@ export class ApiService {
// Generic request method
async request(url, options = {}) {
try {
// In sensing-only mode, skip all DensePose API calls
if (backendDetector.sensingOnlyMode) {
throw new Error('DensePose API unavailable (sensing-only mode)');
}
// Process request through interceptors
const processed = await this.processRequest(url, options);

View File

@@ -8,7 +8,7 @@
* always shows something.
*/
const SENSING_WS_URL = 'ws://localhost:8765';
const SENSING_WS_URL = 'ws://localhost:8765/ws/sensing';
const RECONNECT_DELAYS = [1000, 2000, 4000, 8000, 16000];
const MAX_RECONNECT_ATTEMPTS = 10;
const SIMULATION_INTERVAL = 500; // ms

View File

@@ -309,8 +309,11 @@ export class WebSocketService {
clearTimeout(connection.reconnectTimer);
}
// Clear ping interval
this.clearPingInterval(connection.url);
// Clear heartbeat timer
if (connection.heartbeatTimer) {
clearInterval(connection.heartbeatTimer);
connection.heartbeatTimer = null;
}
// Close WebSocket
if (connection.ws.readyState === WebSocket.OPEN) {