import { WebSocketServer } from 'ws'; const port = Number(process.env.PORT || 8080); const profile = process.env.TIMEPLOT_PROFILE || 'telemetry'; const sendIntervalMs = Number(process.env.TIMEPLOT_INTERVAL_MS || 100); const logEvery = Number(process.env.TIMEPLOT_LOG_EVERY || 10); const wss = new WebSocketServer({ port }); const startedAt = Date.now(); let sampleIndex = 0; let activeClientCount = 0; function log(message, details = '') { const timestamp = new Date().toISOString(); if (details) { console.log(`[timeplot-ws ${timestamp}] ${message} ${details}`); return; } console.log(`[timeplot-ws ${timestamp}] ${message}`); } function sampleTelemetry(seconds) { return Math.sin(seconds * 2.2) + 0.35 * Math.cos(seconds * 6.4 + Math.sin(seconds * 0.8)) + 0.15 * Math.sin(seconds * 0.33); } function sampleChirp(seconds) { return 0.7 * Math.sin(seconds * seconds * 1.4) + 0.3 * Math.sin(seconds * 7.5); } function sampleSteps(seconds) { const phase = Math.floor((seconds % 8) / 1.0); return [0, 0.4, 0.9, 1.2, 0.2, -0.6, -1.0, 0.3][phase] ?? 0; } function sampleBurst(seconds) { const burstPhase = (seconds % 6) - 1.5; const burst = Math.sin(seconds * 9.5) * Math.exp(-(burstPhase ** 2) * 0.8); return 0.45 * Math.sin(seconds * 2.1) + burst; } function sampleValue(seconds) { switch (profile) { case 'chirp': return sampleChirp(seconds); case 'steps': return sampleSteps(seconds); case 'burst': return sampleBurst(seconds); case 'telemetry': default: return sampleTelemetry(seconds); } } function buildMessage() { const timestampMs = Date.now() - startedAt; const seconds = timestampMs / 1000; sampleIndex += 1; return { timestampMs, value: Number(sampleValue(seconds).toFixed(6)), sequence: sampleIndex, profile, }; } const interval = setInterval(() => { const message = buildMessage(); const payload = JSON.stringify(message); let sentCount = 0; for (const client of wss.clients) { if (client.readyState === client.OPEN) { client.send(payload); sentCount += 1; } } if (message.sequence === 1 || (logEvery > 0 && message.sequence % logEvery === 0)) { log( 'broadcast', `seq=${message.sequence} clients=${sentCount} timestampMs=${message.timestampMs} value=${message.value}`, ); } }, sendIntervalMs); wss.on('connection', (socket, request) => { const clientAddress = request.socket.remoteAddress || 'unknown'; activeClientCount += 1; log('client connected', `from=${clientAddress} activeClients=${activeClientCount}`); socket.send(JSON.stringify({ timestampMs: 0, value: 0, sequence: 0, profile, message: 'connected', })); socket.on('error', (error) => { log('client error', `from=${clientAddress} error=${error.message}`); }); socket.on('close', () => { activeClientCount = Math.max(0, activeClientCount - 1); log('client disconnected', `from=${clientAddress} activeClients=${activeClientCount}`); }); }); wss.on('error', (error) => { log('server error', error.message); }); wss.on('listening', () => { log('listening', `url=ws://localhost:${port}`); log('config', `profile=${profile} intervalMs=${sendIntervalMs} logEvery=${logEvery}`); }); function shutdown() { log('shutdown requested', `activeClients=${activeClientCount}`); clearInterval(interval); wss.close(() => { log('server stopped'); process.exit(0); }); } process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown);