Anlegen vom Raum-Scanner

This commit is contained in:
chk
2026-06-16 10:06:05 +02:00
commit 792022d1fb
1836 changed files with 773869 additions and 0 deletions

217
server.js Normal file
View 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 ESPDaten
}, 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); }
});