summaryrefslogtreecommitdiff
path: root/scripts/demo-websocket-server.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/demo-websocket-server.mjs')
-rw-r--r--scripts/demo-websocket-server.mjs131
1 files changed, 131 insertions, 0 deletions
diff --git a/scripts/demo-websocket-server.mjs b/scripts/demo-websocket-server.mjs
new file mode 100644
index 0000000..1bee865
--- /dev/null
+++ b/scripts/demo-websocket-server.mjs
@@ -0,0 +1,131 @@
+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);