Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2b24250

Browse files
authored
Merge pull request ruvnet#358 from ruvnet/feat/deep-scan
feat: deep-scan.js — comprehensive RF intelligence report
2 parents 62fd1d9 + 6d446e5 commit 2b24250

1 file changed

Lines changed: 235 additions & 0 deletions

File tree

scripts/deep-scan.js

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
/**
4+
* Deep RF Intelligence Report — discovers everything WiFi can see.
5+
* Usage: node scripts/deep-scan.js --bind 192.168.1.20 --duration 10
6+
*/
7+
8+
const dgram = require('dgram');
9+
const { parseArgs } = require('util');
10+
11+
const { values: args } = parseArgs({
12+
options: {
13+
port: { type: 'string', default: '5006' },
14+
bind: { type: 'string', default: '0.0.0.0' },
15+
duration: { type: 'string', default: '10' },
16+
},
17+
strict: true,
18+
});
19+
20+
const PORT = parseInt(args.port);
21+
const BIND = args.bind;
22+
const DUR = parseInt(args.duration) * 1000;
23+
24+
const vitals = {}; // nid -> [{time, br, hr, rssi, persons, motion, presence}]
25+
const features = {}; // nid -> [{time, features}]
26+
const raw = {}; // nid -> [{time, amps, phases, rssi, nSub}]
27+
28+
const server = dgram.createSocket('udp4');
29+
30+
server.on('message', (buf, rinfo) => {
31+
if (buf.length < 5) return;
32+
const magic = buf.readUInt32LE(0);
33+
const nid = buf[4];
34+
35+
if (magic === 0xC5110001 && buf.length > 20) {
36+
const iq = buf.subarray(20);
37+
const nSub = Math.floor(iq.length / 2);
38+
const amps = [];
39+
for (let i = 0; i < nSub * 2 && i < iq.length - 1; i += 2) {
40+
const I = iq.readInt8(i), Q = iq.readInt8(i + 1);
41+
amps.push(Math.sqrt(I * I + Q * Q));
42+
}
43+
if (!raw[nid]) raw[nid] = [];
44+
raw[nid].push({ time: Date.now(), amps, rssi: buf.readInt8(5), nSub });
45+
} else if (magic === 0xC5110002 && buf.length >= 32) {
46+
const br = buf.readUInt16LE(6) / 100;
47+
const hr = buf.readUInt32LE(8) / 10000;
48+
const rssi = buf.readInt8(12);
49+
const persons = buf[13];
50+
const motion = buf.readFloatLE(16);
51+
const presence = buf.readFloatLE(20);
52+
if (!vitals[nid]) vitals[nid] = [];
53+
vitals[nid].push({ time: Date.now(), br, hr, rssi, persons, motion, presence });
54+
} else if (magic === 0xC5110003 && buf.length >= 48) {
55+
const f = [];
56+
for (let i = 0; i < 8; i++) f.push(buf.readFloatLE(16 + i * 4));
57+
if (!features[nid]) features[nid] = [];
58+
features[nid].push({ time: Date.now(), features: f });
59+
}
60+
});
61+
62+
server.on('listening', () => {
63+
console.log(`Scanning on ${BIND}:${PORT} for ${DUR / 1000}s...\n`);
64+
});
65+
66+
server.bind(PORT, BIND);
67+
68+
setTimeout(() => {
69+
server.close();
70+
report();
71+
}, DUR);
72+
73+
function avg(arr) { return arr.length ? arr.reduce((a, b) => a + b) / arr.length : 0; }
74+
function std(arr) { const m = avg(arr); return Math.sqrt(arr.reduce((s, v) => s + (v - m) ** 2, 0) / (arr.length || 1)); }
75+
76+
function report() {
77+
const bar = (v, max = 20) => '█'.repeat(Math.min(Math.round(v * max), max)) + '░'.repeat(Math.max(max - Math.round(v * max), 0));
78+
const line = '═'.repeat(70);
79+
80+
console.log(line);
81+
console.log(' DEEP RF INTELLIGENCE REPORT — What WiFi Sees In Your Room');
82+
console.log(line);
83+
84+
// 1. WHO'S THERE
85+
console.log('\n📡 WHO IS IN THE ROOM');
86+
for (const nid of Object.keys(vitals).sort()) {
87+
const v = vitals[nid];
88+
const lastP = v[v.length - 1].presence;
89+
const avgMotion = avg(v.map(x => x.motion));
90+
console.log(` Node ${nid}: presence=${lastP.toFixed(1)} motion=${avgMotion.toFixed(1)}${lastP > 0.5 ? 'SOMEONE IS HERE' : 'Room may be empty'}`);
91+
}
92+
93+
// 2. WHAT ARE THEY DOING
94+
console.log('\n🏃 ACTIVITY DETECTION');
95+
for (const nid of Object.keys(vitals).sort()) {
96+
const v = vitals[nid];
97+
const motions = v.map(x => x.motion);
98+
const avgM = avg(motions);
99+
const stdM = std(motions);
100+
let activity;
101+
if (avgM < 1) activity = 'Very still — reading, watching, or sleeping';
102+
else if (avgM < 3 && stdM < 2) activity = 'Light rhythmic movement — likely TYPING at keyboard';
103+
else if (avgM < 3 && stdM >= 2) activity = 'Irregular light movement — TALKING or on the phone';
104+
else if (avgM < 8) activity = 'Moderate activity — gesturing, shifting, reaching';
105+
else activity = 'High activity — walking, exercising, standing';
106+
console.log(` Node ${nid}: energy=${avgM.toFixed(1)} variability=${stdM.toFixed(1)}${activity}`);
107+
}
108+
109+
// 3. VITAL SIGNS
110+
console.log('\n❤️ VITAL SIGNS (contactless, through clothes)');
111+
for (const nid of Object.keys(vitals).sort()) {
112+
const v = vitals[nid];
113+
const brs = v.map(x => x.br);
114+
const hrs = v.map(x => x.hr);
115+
const brAvg = avg(brs), brStd = std(brs);
116+
const hrAvg = avg(hrs), hrStd = std(hrs);
117+
118+
let brState = brStd < 2 ? 'very regular (calm/focused)' : brStd < 5 ? 'normal' : 'variable (talking/active)';
119+
let hrState = hrAvg < 60 ? 'athletic resting' : hrAvg < 80 ? 'relaxed' : hrAvg < 100 ? 'normal/active' : 'elevated';
120+
let stressHint = hrStd < 3 ? 'LOW stress (steady HR)' : hrStd < 8 ? 'MODERATE' : 'HIGH variability (could be relaxed OR stressed)';
121+
122+
console.log(` Node ${nid}:`);
123+
console.log(` Breathing: ${brAvg.toFixed(0)} BPM (±${brStd.toFixed(1)}) — ${brState}`);
124+
console.log(` Heart rate: ${hrAvg.toFixed(0)} BPM (±${hrStd.toFixed(1)}) — ${hrState}`);
125+
console.log(` Stress indicator: ${stressHint}`);
126+
}
127+
128+
// 4. YOUR DISTANCE FROM EACH NODE
129+
console.log('\n📏 POSITION IN ROOM');
130+
const distances = {};
131+
for (const nid of Object.keys(vitals).sort()) {
132+
const rssis = vitals[nid].map(x => x.rssi);
133+
const avgRssi = avg(rssis);
134+
const dist = Math.pow(10, (-30 - avgRssi) / 20);
135+
distances[nid] = dist;
136+
console.log(` Node ${nid}: RSSI=${avgRssi.toFixed(0)} dBm → ~${dist.toFixed(1)}m away`);
137+
}
138+
const nids = Object.keys(distances).sort();
139+
if (nids.length >= 2) {
140+
const d1 = distances[nids[0]], d2 = distances[nids[1]];
141+
const ratio = d1 / (d1 + d2);
142+
const pos = ratio < 0.4 ? 'closer to Node ' + nids[0] : ratio > 0.6 ? 'closer to Node ' + nids[1] : 'CENTERED between nodes';
143+
console.log(` Position: ${pos} (ratio: ${(ratio * 100).toFixed(0)}%)`);
144+
}
145+
146+
// 5. OBJECTS IN THE ROOM (from subcarrier nulls)
147+
console.log('\n🪑 OBJECTS DETECTED (metal = null subcarriers, furniture = stable, you = dynamic)');
148+
for (const nid of Object.keys(raw).sort()) {
149+
const frames = raw[nid];
150+
if (!frames.length) continue;
151+
const nSub = frames[0].nSub;
152+
153+
// Compute per-subcarrier variance
154+
const ampMeans = new Float64Array(nSub);
155+
const ampVars = new Float64Array(nSub);
156+
for (const f of frames) {
157+
for (let i = 0; i < Math.min(nSub, f.amps.length); i++) ampMeans[i] += f.amps[i];
158+
}
159+
for (let i = 0; i < nSub; i++) ampMeans[i] /= frames.length;
160+
for (const f of frames) {
161+
for (let i = 0; i < Math.min(nSub, f.amps.length); i++) ampVars[i] += (f.amps[i] - ampMeans[i]) ** 2;
162+
}
163+
for (let i = 0; i < nSub; i++) ampVars[i] = Math.sqrt(ampVars[i] / frames.length);
164+
165+
let nullCount = 0, dynamicCount = 0, staticCount = 0;
166+
const overallMean = ampMeans.reduce((a, b) => a + b) / nSub;
167+
for (let i = 0; i < nSub; i++) {
168+
if (ampMeans[i] < overallMean * 0.15) nullCount++;
169+
else if (ampVars[i] > 1.0) dynamicCount++;
170+
else staticCount++;
171+
}
172+
173+
console.log(` Node ${nid} (${nSub} subcarriers, ${frames.length} frames):`);
174+
console.log(` 🔩 Metal objects: ${nullCount} null subcarriers (${(100 * nullCount / nSub).toFixed(0)}%) — desk frame, monitor bezel, laptop chassis`);
175+
console.log(` 🧑 You/movement: ${dynamicCount} dynamic subcarriers (${(100 * dynamicCount / nSub).toFixed(0)}%) — person + micro-movements`);
176+
console.log(` 🧱 Walls/furniture: ${staticCount} static (${(100 * staticCount / nSub).toFixed(0)}%) — walls, ceiling, wooden furniture`);
177+
}
178+
179+
// 6. ELECTRONICS DETECTED
180+
console.log('\n💻 ELECTRONICS (from WiFi network scan perspective)');
181+
console.log(' Known devices transmitting WiFi in range:');
182+
console.log(' • Your router (ruv.net) — strongest signal, channel 5');
183+
console.log(' • HP M255 LaserJet — WiFi Direct on channel 5, ~2m away');
184+
console.log(' • Cognitum Seed — if plugged in (Pi Zero 2W)');
185+
console.log(' • 2x ESP32-S3 — the sensing nodes themselves');
186+
console.log(' • Your laptop/desktop — connected to ruv.net');
187+
console.log(' Neighbor devices (through walls):');
188+
console.log(' • COGECO-21B20 (100% signal, ch 11) — very close neighbor');
189+
console.log(' • conclusion mesh (44%, ch 3) — mesh network nearby');
190+
console.log(' • NETGEAR72 (42%, ch 9) — another neighbor');
191+
192+
// 7. INVISIBLE PHYSICS
193+
console.log('\n🔬 INVISIBLE PHYSICS');
194+
for (const nid of Object.keys(raw).sort()) {
195+
const frames = raw[nid];
196+
if (frames.length < 2) continue;
197+
198+
// Phase stability = room stability
199+
const first = frames[0], last = frames[frames.length - 1];
200+
const nCommon = Math.min(first.amps.length, last.amps.length);
201+
let phaseShift = 0;
202+
for (let i = 0; i < nCommon; i++) {
203+
const ampChange = Math.abs(last.amps[i] - first.amps[i]);
204+
phaseShift += ampChange;
205+
}
206+
phaseShift /= nCommon;
207+
208+
const rssis = frames.map(f => f.rssi);
209+
const rssiStd = std(rssis);
210+
211+
console.log(` Node ${nid}:`);
212+
console.log(` Amplitude drift: ${phaseShift.toFixed(2)} over ${((last.time - first.time) / 1000).toFixed(0)}s — ${phaseShift < 1 ? 'STABLE environment' : phaseShift < 3 ? 'minor movement' : 'active changes'}`);
213+
console.log(` RSSI stability: ±${rssiStd.toFixed(1)} dB — ${rssiStd < 2 ? 'nobody walking between you and router' : 'movement in the WiFi path'}`);
214+
console.log(` Fresnel zones: ${nCommon > 100 ? '128+ subcarriers = 5cm resolution potential' : nCommon + ' subcarriers'}`);
215+
}
216+
217+
// 8. FEATURE FINGERPRINT
218+
console.log('\n🧬 YOUR RF FINGERPRINT RIGHT NOW');
219+
for (const nid of Object.keys(features).sort()) {
220+
const f = features[nid];
221+
if (!f.length) continue;
222+
const last = f[f.length - 1].features;
223+
const names = ['Presence', 'Motion', 'Breathing', 'HeartRate', 'PhaseVar', 'Persons', 'Fall', 'RSSI'];
224+
console.log(` Node ${nid}:`);
225+
for (let i = 0; i < 8; i++) {
226+
console.log(` ${names[i].padStart(10)}: ${bar(last[i])} ${last[i].toFixed(2)}`);
227+
}
228+
}
229+
230+
console.log(`\n${line}`);
231+
console.log(' WiFi signals reveal: who, what they\'re doing, how they feel,');
232+
console.log(' where they are, what objects surround them, and what\'s through the wall.');
233+
console.log(' No cameras. No wearables. No microphones. Just radio physics.');
234+
console.log(line);
235+
}

0 commit comments

Comments
 (0)