deploy: fix toFixed crash and Blob WebSocket handling in CSI simulator
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
b76f5230ab
commit
8f3edd9351
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WiFi-DensePose — Dual-Modal Pose Estimation</title>
|
<title>WiFi-DensePose — Dual-Modal Pose Estimation</title>
|
||||||
<link rel="stylesheet" href="pose-fusion/css/style.css?v=12">
|
<link rel="stylesheet" href="pose-fusion/css/style.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -196,6 +196,6 @@
|
||||||
|
|
||||||
</div><!-- /main-grid -->
|
</div><!-- /main-grid -->
|
||||||
|
|
||||||
<script type="module" src="pose-fusion/js/main.js?v=12"></script>
|
<script type="module" src="pose-fusion/js/main.js?v=13"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class CsiSimulator {
|
export class CsiSimulator {
|
||||||
|
static VERSION = 'v4-drift'; // Cache-bust verification
|
||||||
|
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
this.subcarriers = opts.subcarriers || 52; // 802.11n HT20
|
this.subcarriers = opts.subcarriers || 52; // 802.11n HT20
|
||||||
this.timeWindow = opts.timeWindow || 56; // frames in sliding window
|
this.timeWindow = opts.timeWindow || 56; // frames in sliding window
|
||||||
|
|
@ -32,6 +34,10 @@ export class CsiSimulator {
|
||||||
this._basePhase[i] = (i / this.subcarriers) * Math.PI * 2;
|
this._basePhase[i] = (i / this.subcarriers) * Math.PI * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RSSI tracking
|
||||||
|
this.rssiDbm = -70; // default mid-range
|
||||||
|
this._rssiTarget = -70;
|
||||||
|
|
||||||
// Person influence (updated from video motion)
|
// Person influence (updated from video motion)
|
||||||
this.personPresence = 0;
|
this.personPresence = 0;
|
||||||
this.personX = 0.5;
|
this.personX = 0.5;
|
||||||
|
|
@ -73,6 +79,9 @@ export class CsiSimulator {
|
||||||
* (simulating through-wall sensing capability).
|
* (simulating through-wall sensing capability).
|
||||||
*/
|
*/
|
||||||
updatePersonState(presence, x, y, motion) {
|
updatePersonState(presence, x, y, motion) {
|
||||||
|
// Don't override real CSI sensing with synthetic video-derived state
|
||||||
|
if (this.mode === 'live') return;
|
||||||
|
|
||||||
if (presence > 0.1) {
|
if (presence > 0.1) {
|
||||||
// Person detected in video — update CSI state directly
|
// Person detected in video — update CSI state directly
|
||||||
this.personPresence = presence;
|
this.personPresence = presence;
|
||||||
|
|
@ -126,6 +135,13 @@ export class CsiSimulator {
|
||||||
this.phaseBuffer.shift();
|
this.phaseBuffer.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RSSI: smooth toward target (demo mode generates synthetic RSSI)
|
||||||
|
if (this.mode === 'demo') {
|
||||||
|
// Simulate RSSI based on person presence and slow drift
|
||||||
|
this._rssiTarget = -55 - 25 * (1 - this.personPresence) + Math.sin(elapsed * 0.3) * 3;
|
||||||
|
}
|
||||||
|
this.rssiDbm += (this._rssiTarget - this.rssiDbm) * 0.1;
|
||||||
|
|
||||||
// SNR estimate
|
// SNR estimate
|
||||||
let signalPower = 0, noisePower = 0;
|
let signalPower = 0, noisePower = 0;
|
||||||
for (let i = 0; i < this.subcarriers; i++) {
|
for (let i = 0; i < this.subcarriers; i++) {
|
||||||
|
|
@ -215,6 +231,11 @@ export class CsiSimulator {
|
||||||
this._noiseState[i] = 0.95 * this._noiseState[i] + 0.05 * (rng() * 2 - 1) * 0.03;
|
this._noiseState[i] = 0.95 * this._noiseState[i] + 0.05 * (rng() * 2 - 1) * 0.03;
|
||||||
a += this._noiseState[i];
|
a += this._noiseState[i];
|
||||||
|
|
||||||
|
// Ambient temporal drift (multipath fading even in empty room)
|
||||||
|
a += 0.06 * Math.sin(elapsed * 0.7 + i * 0.25)
|
||||||
|
+ 0.04 * Math.sin(elapsed * 1.3 - i * 0.18)
|
||||||
|
+ 0.03 * Math.cos(elapsed * 2.1 + i * 0.4);
|
||||||
|
|
||||||
// Person-induced CSI perturbation
|
// Person-induced CSI perturbation
|
||||||
if (presence > 0.1) {
|
if (presence > 0.1) {
|
||||||
// Subcarrier-dependent body reflection (Fresnel zone model)
|
// Subcarrier-dependent body reflection (Fresnel zone model)
|
||||||
|
|
@ -237,6 +258,23 @@ export class CsiSimulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleLiveFrame(data) {
|
_handleLiveFrame(data) {
|
||||||
|
// Handle JSON text frames from the sensing server
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
this._handleJsonFrame(msg);
|
||||||
|
} catch (_) { /* ignore malformed JSON */ }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Blob data (convert to ArrayBuffer and re-process)
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
data.arrayBuffer().then(ab => this._handleLiveFrame(ab)).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle binary ArrayBuffer frames (ADR-018 format)
|
||||||
|
if (!(data instanceof ArrayBuffer)) return;
|
||||||
const view = new DataView(data);
|
const view = new DataView(data);
|
||||||
// Check ADR-018 magic: 0xC5110001
|
// Check ADR-018 magic: 0xC5110001
|
||||||
if (data.byteLength < 20) return;
|
if (data.byteLength < 20) return;
|
||||||
|
|
@ -256,6 +294,64 @@ export class CsiSimulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleJsonFrame(msg) {
|
||||||
|
// Sensing server sends: { type: "sensing_update", nodes: [{ amplitude: [...], subcarrier_count }], classification, features }
|
||||||
|
this._liveAmplitude = new Float32Array(this.subcarriers);
|
||||||
|
this._livePhase = new Float32Array(this.subcarriers);
|
||||||
|
|
||||||
|
// Extract amplitude from sensing_update node data
|
||||||
|
const node = (msg.nodes && msg.nodes[0]) || msg;
|
||||||
|
const ampArr = node.amplitude || msg.amplitude;
|
||||||
|
if (ampArr && Array.isArray(ampArr)) {
|
||||||
|
const n = Math.min(ampArr.length, this.subcarriers);
|
||||||
|
// Server sends raw amplitude (already magnitude), normalize to 0-1
|
||||||
|
let maxAmp = 0;
|
||||||
|
for (let i = 0; i < n; i++) maxAmp = Math.max(maxAmp, Math.abs(ampArr[i]));
|
||||||
|
const scale = maxAmp > 0 ? 1.0 / maxAmp : 1.0;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
this._liveAmplitude[i] = Math.abs(ampArr[i]) * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase from node (if available)
|
||||||
|
const phaseArr = node.phase || msg.phase;
|
||||||
|
if (phaseArr && Array.isArray(phaseArr)) {
|
||||||
|
const n = Math.min(phaseArr.length, this.subcarriers);
|
||||||
|
for (let i = 0; i < n; i++) this._livePhase[i] = phaseArr[i];
|
||||||
|
} else if (ampArr) {
|
||||||
|
// Synthesize phase from amplitude variation (Hilbert-like estimate)
|
||||||
|
for (let i = 1; i < this.subcarriers; i++) {
|
||||||
|
this._livePhase[i] = this._livePhase[i - 1] + (this._liveAmplitude[i] - this._liveAmplitude[i - 1]) * Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle raw I/Q pairs
|
||||||
|
const iq = node.iq || msg.iq;
|
||||||
|
if (iq && Array.isArray(iq)) {
|
||||||
|
const n = Math.min(iq.length / 2, this.subcarriers);
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const real = iq[i * 2], imag = iq[i * 2 + 1];
|
||||||
|
this._liveAmplitude[i] = Math.sqrt(real * real + imag * imag) / 2048;
|
||||||
|
this._livePhase[i] = Math.atan2(imag, real);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract RSSI from node data
|
||||||
|
if (typeof node.rssi_dbm === 'number') {
|
||||||
|
this._rssiTarget = node.rssi_dbm;
|
||||||
|
} else if (msg.features && typeof msg.features.mean_rssi === 'number') {
|
||||||
|
this._rssiTarget = msg.features.mean_rssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update presence from server classification
|
||||||
|
const cls = msg.classification;
|
||||||
|
if (cls) {
|
||||||
|
if (typeof cls.confidence === 'number') {
|
||||||
|
this.personPresence = cls.presence ? cls.confidence : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_mulberry32(seed) {
|
_mulberry32(seed) {
|
||||||
return function() {
|
return function() {
|
||||||
let t = (seed += 0x6D2B79F5);
|
let t = (seed += 0x6D2B79F5);
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@ function mainLoop(timestamp) {
|
||||||
// One-time diagnostic
|
// One-time diagnostic
|
||||||
if (!_diagDone) {
|
if (!_diagDone) {
|
||||||
_diagDone = true;
|
_diagDone = true;
|
||||||
console.log(`[PoseFusion] frame 1 OK — mode=${mode}, csi.bufLen=${csiSimulator.amplitudeBuffer.length}, embPts=${embPoints.fused.length}, rssi=${csiSimulator.rssiDbm.toFixed(1)}`);
|
console.log(`[PoseFusion] frame 1 OK — mode=${mode}, csi.bufLen=${csiSimulator.amplitudeBuffer.length}, embPts=${embPoints?.fused?.length ?? 0}, rssi=${(csiSimulator.rssiDbm ?? -99).toFixed(1)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WiFi-DensePose — Dual-Modal Pose Estimation</title>
|
<title>WiFi-DensePose — Dual-Modal Pose Estimation</title>
|
||||||
<link rel="stylesheet" href="pose-fusion/css/style.css?v=12">
|
<link rel="stylesheet" href="pose-fusion/css/style.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -196,6 +196,6 @@
|
||||||
|
|
||||||
</div><!-- /main-grid -->
|
</div><!-- /main-grid -->
|
||||||
|
|
||||||
<script type="module" src="pose-fusion/js/main.js?v=12"></script>
|
<script type="module" src="pose-fusion/js/main.js?v=13"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class CsiSimulator {
|
export class CsiSimulator {
|
||||||
|
static VERSION = 'v4-drift'; // Cache-bust verification
|
||||||
|
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
this.subcarriers = opts.subcarriers || 52; // 802.11n HT20
|
this.subcarriers = opts.subcarriers || 52; // 802.11n HT20
|
||||||
this.timeWindow = opts.timeWindow || 56; // frames in sliding window
|
this.timeWindow = opts.timeWindow || 56; // frames in sliding window
|
||||||
|
|
@ -32,6 +34,10 @@ export class CsiSimulator {
|
||||||
this._basePhase[i] = (i / this.subcarriers) * Math.PI * 2;
|
this._basePhase[i] = (i / this.subcarriers) * Math.PI * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RSSI tracking
|
||||||
|
this.rssiDbm = -70; // default mid-range
|
||||||
|
this._rssiTarget = -70;
|
||||||
|
|
||||||
// Person influence (updated from video motion)
|
// Person influence (updated from video motion)
|
||||||
this.personPresence = 0;
|
this.personPresence = 0;
|
||||||
this.personX = 0.5;
|
this.personX = 0.5;
|
||||||
|
|
@ -73,6 +79,9 @@ export class CsiSimulator {
|
||||||
* (simulating through-wall sensing capability).
|
* (simulating through-wall sensing capability).
|
||||||
*/
|
*/
|
||||||
updatePersonState(presence, x, y, motion) {
|
updatePersonState(presence, x, y, motion) {
|
||||||
|
// Don't override real CSI sensing with synthetic video-derived state
|
||||||
|
if (this.mode === 'live') return;
|
||||||
|
|
||||||
if (presence > 0.1) {
|
if (presence > 0.1) {
|
||||||
// Person detected in video — update CSI state directly
|
// Person detected in video — update CSI state directly
|
||||||
this.personPresence = presence;
|
this.personPresence = presence;
|
||||||
|
|
@ -126,6 +135,13 @@ export class CsiSimulator {
|
||||||
this.phaseBuffer.shift();
|
this.phaseBuffer.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RSSI: smooth toward target (demo mode generates synthetic RSSI)
|
||||||
|
if (this.mode === 'demo') {
|
||||||
|
// Simulate RSSI based on person presence and slow drift
|
||||||
|
this._rssiTarget = -55 - 25 * (1 - this.personPresence) + Math.sin(elapsed * 0.3) * 3;
|
||||||
|
}
|
||||||
|
this.rssiDbm += (this._rssiTarget - this.rssiDbm) * 0.1;
|
||||||
|
|
||||||
// SNR estimate
|
// SNR estimate
|
||||||
let signalPower = 0, noisePower = 0;
|
let signalPower = 0, noisePower = 0;
|
||||||
for (let i = 0; i < this.subcarriers; i++) {
|
for (let i = 0; i < this.subcarriers; i++) {
|
||||||
|
|
@ -215,6 +231,11 @@ export class CsiSimulator {
|
||||||
this._noiseState[i] = 0.95 * this._noiseState[i] + 0.05 * (rng() * 2 - 1) * 0.03;
|
this._noiseState[i] = 0.95 * this._noiseState[i] + 0.05 * (rng() * 2 - 1) * 0.03;
|
||||||
a += this._noiseState[i];
|
a += this._noiseState[i];
|
||||||
|
|
||||||
|
// Ambient temporal drift (multipath fading even in empty room)
|
||||||
|
a += 0.06 * Math.sin(elapsed * 0.7 + i * 0.25)
|
||||||
|
+ 0.04 * Math.sin(elapsed * 1.3 - i * 0.18)
|
||||||
|
+ 0.03 * Math.cos(elapsed * 2.1 + i * 0.4);
|
||||||
|
|
||||||
// Person-induced CSI perturbation
|
// Person-induced CSI perturbation
|
||||||
if (presence > 0.1) {
|
if (presence > 0.1) {
|
||||||
// Subcarrier-dependent body reflection (Fresnel zone model)
|
// Subcarrier-dependent body reflection (Fresnel zone model)
|
||||||
|
|
@ -237,6 +258,23 @@ export class CsiSimulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleLiveFrame(data) {
|
_handleLiveFrame(data) {
|
||||||
|
// Handle JSON text frames from the sensing server
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
this._handleJsonFrame(msg);
|
||||||
|
} catch (_) { /* ignore malformed JSON */ }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Blob data (convert to ArrayBuffer and re-process)
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
data.arrayBuffer().then(ab => this._handleLiveFrame(ab)).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle binary ArrayBuffer frames (ADR-018 format)
|
||||||
|
if (!(data instanceof ArrayBuffer)) return;
|
||||||
const view = new DataView(data);
|
const view = new DataView(data);
|
||||||
// Check ADR-018 magic: 0xC5110001
|
// Check ADR-018 magic: 0xC5110001
|
||||||
if (data.byteLength < 20) return;
|
if (data.byteLength < 20) return;
|
||||||
|
|
@ -256,6 +294,64 @@ export class CsiSimulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleJsonFrame(msg) {
|
||||||
|
// Sensing server sends: { type: "sensing_update", nodes: [{ amplitude: [...], subcarrier_count }], classification, features }
|
||||||
|
this._liveAmplitude = new Float32Array(this.subcarriers);
|
||||||
|
this._livePhase = new Float32Array(this.subcarriers);
|
||||||
|
|
||||||
|
// Extract amplitude from sensing_update node data
|
||||||
|
const node = (msg.nodes && msg.nodes[0]) || msg;
|
||||||
|
const ampArr = node.amplitude || msg.amplitude;
|
||||||
|
if (ampArr && Array.isArray(ampArr)) {
|
||||||
|
const n = Math.min(ampArr.length, this.subcarriers);
|
||||||
|
// Server sends raw amplitude (already magnitude), normalize to 0-1
|
||||||
|
let maxAmp = 0;
|
||||||
|
for (let i = 0; i < n; i++) maxAmp = Math.max(maxAmp, Math.abs(ampArr[i]));
|
||||||
|
const scale = maxAmp > 0 ? 1.0 / maxAmp : 1.0;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
this._liveAmplitude[i] = Math.abs(ampArr[i]) * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase from node (if available)
|
||||||
|
const phaseArr = node.phase || msg.phase;
|
||||||
|
if (phaseArr && Array.isArray(phaseArr)) {
|
||||||
|
const n = Math.min(phaseArr.length, this.subcarriers);
|
||||||
|
for (let i = 0; i < n; i++) this._livePhase[i] = phaseArr[i];
|
||||||
|
} else if (ampArr) {
|
||||||
|
// Synthesize phase from amplitude variation (Hilbert-like estimate)
|
||||||
|
for (let i = 1; i < this.subcarriers; i++) {
|
||||||
|
this._livePhase[i] = this._livePhase[i - 1] + (this._liveAmplitude[i] - this._liveAmplitude[i - 1]) * Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle raw I/Q pairs
|
||||||
|
const iq = node.iq || msg.iq;
|
||||||
|
if (iq && Array.isArray(iq)) {
|
||||||
|
const n = Math.min(iq.length / 2, this.subcarriers);
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const real = iq[i * 2], imag = iq[i * 2 + 1];
|
||||||
|
this._liveAmplitude[i] = Math.sqrt(real * real + imag * imag) / 2048;
|
||||||
|
this._livePhase[i] = Math.atan2(imag, real);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract RSSI from node data
|
||||||
|
if (typeof node.rssi_dbm === 'number') {
|
||||||
|
this._rssiTarget = node.rssi_dbm;
|
||||||
|
} else if (msg.features && typeof msg.features.mean_rssi === 'number') {
|
||||||
|
this._rssiTarget = msg.features.mean_rssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update presence from server classification
|
||||||
|
const cls = msg.classification;
|
||||||
|
if (cls) {
|
||||||
|
if (typeof cls.confidence === 'number') {
|
||||||
|
this.personPresence = cls.presence ? cls.confidence : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_mulberry32(seed) {
|
_mulberry32(seed) {
|
||||||
return function() {
|
return function() {
|
||||||
let t = (seed += 0x6D2B79F5);
|
let t = (seed += 0x6D2B79F5);
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@ function mainLoop(timestamp) {
|
||||||
// One-time diagnostic
|
// One-time diagnostic
|
||||||
if (!_diagDone) {
|
if (!_diagDone) {
|
||||||
_diagDone = true;
|
_diagDone = true;
|
||||||
console.log(`[PoseFusion] frame 1 OK — mode=${mode}, csi.bufLen=${csiSimulator.amplitudeBuffer.length}, embPts=${embPoints.fused.length}, rssi=${csiSimulator.rssiDbm.toFixed(1)}`);
|
console.log(`[PoseFusion] frame 1 OK — mode=${mode}, csi.bufLen=${csiSimulator.amplitudeBuffer.length}, embPts=${embPoints?.fused?.length ?? 0}, rssi=${(csiSimulator.rssiDbm ?? -99).toFixed(1)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue