Anlegen vom Raum-Scanner
This commit is contained in:
217
server.js
Normal file
217
server.js
Normal file
@@ -0,0 +1,217 @@
|
||||
/* eslint-disable */
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const morgan = require('morgan');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
// --- Load config ---
|
||||
const CONFIG_PATH = path.join(__dirname, 'room.config');
|
||||
function loadConfig() {
|
||||
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
let cfg = loadConfig();
|
||||
|
||||
// --- Express HTTP server ---
|
||||
const app = express();
|
||||
app.use(express.json({ limit: '256kb' }));
|
||||
app.use(cors({ origin: true }));
|
||||
app.use(morgan('dev'));
|
||||
|
||||
// Serve three.js from node_modules (import-map)
|
||||
app.use('/vendor/three', express.static(path.join(__dirname, 'node_modules/three')));
|
||||
|
||||
// Serve static viewer from ./public
|
||||
const publicDir = path.join(__dirname, 'public');
|
||||
app.use('/', express.static(publicDir, { etag: false, lastModified: false, cacheControl: false }));
|
||||
|
||||
// Expose render-relevant config to the browser
|
||||
app.get('/config', (req, res) => {
|
||||
const { sensor, pose } = cfg;
|
||||
res.json({ sensor, pose });
|
||||
});
|
||||
|
||||
// Optional: pose updates
|
||||
app.post('/pose', (req, res) => {
|
||||
const { position, rotationEulerDeg, eulerOrder } = req.body || {};
|
||||
if (Array.isArray(position) && position.length === 3) cfg.pose.position = position.map(Number);
|
||||
if (Array.isArray(rotationEulerDeg) && rotationEulerDeg.length === 3) cfg.pose.rotationEulerDeg = rotationEulerDeg.map(Number);
|
||||
if (typeof eulerOrder === 'string') cfg.pose.eulerOrder = eulerOrder;
|
||||
broadcastState();
|
||||
res.json({ ok: true, pose: cfg.pose });
|
||||
});
|
||||
|
||||
const server = http.createServer(app);
|
||||
server.on('connection', (socket) => socket.setNoDelay(true));
|
||||
|
||||
// --- WS servers for browsers ---
|
||||
const wssFrames = new WebSocket.Server({ noServer: true });
|
||||
const wssState = new WebSocket.Server({ noServer: true });
|
||||
|
||||
function upgradePath(req) {
|
||||
try {
|
||||
const u = new URL(req.url, `http://${req.headers.host}`);
|
||||
return u.pathname;
|
||||
} catch (_) {
|
||||
return req.url;
|
||||
}
|
||||
}
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
const pathname = upgradePath(req);
|
||||
console.log('[HTTP] upgrade request', pathname, 'from', req.socket.remoteAddress);
|
||||
if (pathname === '/ws/frames') {
|
||||
wssFrames.handleUpgrade(req, socket, head, (ws) => {
|
||||
ws._isViewer = true;
|
||||
ws._name = `viewer:${Math.random().toString(16).slice(2, 8)}`;
|
||||
ws._drops = 0;
|
||||
ws._socket?.setNoDelay?.(true);
|
||||
console.log('[WS frames] connected', ws._name);
|
||||
ws.on('close', (code, reason) => {
|
||||
console.log('[WS frames] closed', ws._name, code, reason?.toString());
|
||||
});
|
||||
wssFrames.emit('connection', ws, req);
|
||||
});
|
||||
} else if (pathname === '/ws/state') {
|
||||
wssState.handleUpgrade(req, socket, head, (ws) => {
|
||||
ws._name = `state:${Math.random().toString(16).slice(2, 8)}`;
|
||||
ws._socket?.setNoDelay?.(true);
|
||||
console.log('[WS state] connected', ws._name);
|
||||
ws.on('close', (code, reason) => {
|
||||
console.log('[WS state] closed', ws._name, code, reason?.toString());
|
||||
});
|
||||
wssState.emit('connection', ws, req);
|
||||
try { ws.send(JSON.stringify({ type: 'pose', pose: cfg.pose })); } catch (_) {}
|
||||
});
|
||||
} else {
|
||||
console.warn('[HTTP] upgrade unknown path:', pathname);
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
wssFrames.on('connection', (ws) => { /* frames -> viewers */ });
|
||||
wssState.on('connection', (ws) => { ws.on('message', () => {/*reserved*/}); });
|
||||
|
||||
function broadcastState() {
|
||||
const msg = JSON.stringify({ type: 'pose', pose: cfg.pose });
|
||||
for (const client of wssState.clients) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
try { client.send(msg); } catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Bridge: WS client to ESP32 ---
|
||||
let espWS = null;
|
||||
let reconnectTimer = null;
|
||||
|
||||
// Counters
|
||||
let espFramesTotal = 0;
|
||||
let espFrames10s = 0;
|
||||
let sent10s = 0;
|
||||
let dropped10s = 0;
|
||||
|
||||
function connectESP() {
|
||||
if (espWS && (espWS.readyState === WebSocket.OPEN || espWS.readyState === WebSocket.CONNECTING)) return;
|
||||
const url = cfg.esp.wsUrl; // e.g. "ws://10.0.0.55:81/frames"
|
||||
console.log(`[ESP] Connecting to ${url} ...`);
|
||||
espWS = new WebSocket(url, { perMessageDeflate: false, handshakeTimeout: 4000 });
|
||||
|
||||
espWS.on('open', () => {
|
||||
console.log('[ESP] connected');
|
||||
try { espWS.send('csv'); } catch (_) {}
|
||||
});
|
||||
|
||||
espWS.on('message', (data) => {
|
||||
const frame = (typeof data === 'string') ? data : data.toString('utf8');
|
||||
espFramesTotal++;
|
||||
espFrames10s++;
|
||||
broadcastFrame(frame);
|
||||
});
|
||||
|
||||
espWS.on('close', (code, reason) => {
|
||||
console.log(`[ESP] closed ${code} ${reason}`);
|
||||
scheduleReconnect();
|
||||
});
|
||||
|
||||
espWS.on('error', (err) => {
|
||||
console.warn('[ESP] error', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (reconnectTimer) return;
|
||||
reconnectTimer = setTimeout(() => { reconnectTimer = null; connectESP(); }, cfg.reconnectMs || 1500);
|
||||
}
|
||||
|
||||
function broadcastFrame(frame) {
|
||||
let sentThisFrame = 0;
|
||||
for (const client of wssFrames.clients) {
|
||||
if (client.readyState !== WebSocket.OPEN) continue;
|
||||
const threshold = (cfg.forwarding && cfg.forwarding.clientBufferedThreshold) || 128 * 1024;
|
||||
if (client.bufferedAmount > threshold) { client._drops = (client._drops || 0) + 1; continue; }
|
||||
try { client.send(frame, { binary: false }); sentThisFrame++; } catch (_) {}
|
||||
}
|
||||
if (sentThisFrame > 0) {
|
||||
sent10s += sentThisFrame; // sum over all viewers for this frame
|
||||
} else {
|
||||
dropped10s++;
|
||||
}
|
||||
}
|
||||
|
||||
// === TESTGENERATOR via Config ===
|
||||
if (cfg.testGenerator?.enabled) {
|
||||
console.log("[TESTGEN] Aktiv – künstliche Testdaten werden erzeugt!");
|
||||
|
||||
const W = cfg.sensor.gridW;
|
||||
const H = cfg.sensor.gridH;
|
||||
|
||||
const fps = cfg.testGenerator.fps || 10;
|
||||
const minD = cfg.testGenerator.minDistMm || 2000;
|
||||
const maxD = cfg.testGenerator.maxDistMm || 2500;
|
||||
const noise = cfg.testGenerator.noiseMm || 20;
|
||||
|
||||
let ts = 0;
|
||||
const intervalMs = 1000 / fps;
|
||||
|
||||
setInterval(() => {
|
||||
const distances = [];
|
||||
|
||||
for (let i = 0; i < W * H; i++) {
|
||||
let mm = minD + Math.random() * (maxD - minD);
|
||||
mm += (Math.random() - 0.5) * noise * 2;
|
||||
distances.push(Math.round(mm));
|
||||
}
|
||||
|
||||
const csv = [ts++, W, H, ...distances].join(",");
|
||||
broadcastFrame(csv); // <- exakt gleiche Pipeline wie echte ESP‑Daten
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- 10s stats ----
|
||||
setInterval(() => {
|
||||
console.log(`[STAT 10s] fromESP:${espFrames10s} ->toViewers:${sent10s} dropped:${dropped10s} viewers:${wssFrames.clients.size}`);
|
||||
espFrames10s = 0;
|
||||
sent10s = 0;
|
||||
dropped10s = 0;
|
||||
}, 10_000);
|
||||
|
||||
const PORT = Number(process.env.PORT || cfg.server.port || 8060);
|
||||
const HOST = cfg.server.host || '0.0.0.0';
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`[HTTP] listening on http://${HOST}:${PORT}`);
|
||||
|
||||
if (cfg.testGenerator?.enabled) {
|
||||
console.log("[TESTGEN] ESP-Verbindung deaktiviert (Testmodus aktiv).");
|
||||
} else {
|
||||
connectESP(); // Nur verbinden, wenn Testgenerator ausgeschaltet ist
|
||||
}
|
||||
});
|
||||
process.on('SIGHUP', () => {
|
||||
try { cfg = loadConfig(); console.log('[CFG] reloaded'); } catch (e) { console.warn('[CFG] reload failed', e.message); }
|
||||
});
|
||||
Reference in New Issue
Block a user