#!/usr/bin/env node /** * Capture live process data from Node-RED WebSocket debug sidebar. * Collects samples over a time window and analyzes trends. */ const http = require('http'); const NR_URL = 'http://localhost:1880'; const CAPTURE_SECONDS = 30; // Alternative: poll the Node-RED comms endpoint // But let's use a simpler approach - inject a temporary catch-all debug and read context async function fetchJSON(url) { return new Promise((resolve, reject) => { http.get(url, res => { const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => { try { resolve(JSON.parse(Buffer.concat(chunks))); } catch (e) { reject(new Error('Parse: ' + e.message)); } }); }).on('error', reject); }); } async function postJSON(url, data) { return new Promise((resolve, reject) => { const body = JSON.stringify(data); const parsed = new URL(url); const req = http.request({ hostname: parsed.hostname, port: parsed.port, path: parsed.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), }, }, res => { const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => { const text = Buffer.concat(chunks).toString(); try { resolve(JSON.parse(text)); } catch { resolve(text); } }); }); req.on('error', reject); req.write(body); req.end(); }); } (async () => { console.log('=== Capturing Process Data (' + CAPTURE_SECONDS + 's) ===\n'); // Use Node-RED inject API to trigger debug output // Instead, let's read node context which stores the current state // Get flows to find node IDs const flows = await fetchJSON(NR_URL + '/flows'); const wwtp = flows.filter(n => n.z === 'demo_tab_wwtp'); // Pumping stations store state in node context const pss = wwtp.filter(n => n.type === 'pumpingStation'); const pumps = wwtp.filter(n => n.type === 'rotatingMachine'); const samples = []; const startTime = Date.now(); console.log('Sampling every 3 seconds for ' + CAPTURE_SECONDS + 's...\n'); for (let i = 0; i < Math.ceil(CAPTURE_SECONDS / 3); i++) { const t = Date.now(); const elapsed = ((t - startTime) / 1000).toFixed(1); // Read PS context data via Node-RED context API const sample = { t: elapsed, stations: {} }; for (const ps of pss) { try { const ctx = await fetchJSON(NR_URL + '/context/node/' + ps.id + '?store=default'); sample.stations[ps.id] = ctx; } catch (e) { sample.stations[ps.id] = { error: e.message }; } } for (const pump of pumps) { try { const ctx = await fetchJSON(NR_URL + '/context/node/' + pump.id + '?store=default'); sample.stations[pump.id] = ctx; } catch (e) { sample.stations[pump.id] = { error: e.message }; } } samples.push(sample); // Print summary for this sample console.log('--- Sample at t=' + elapsed + 's ---'); for (const ps of pss) { const ctx = sample.stations[ps.id]; if (ctx && ctx.data) { console.log(ps.name + ':'); // Print all context keys Object.entries(ctx.data).forEach(([key, val]) => { if (typeof val === 'object') { console.log(' ' + key + ': ' + JSON.stringify(val).substring(0, 200)); } else { console.log(' ' + key + ': ' + val); } }); } else { console.log(ps.name + ': ' + JSON.stringify(ctx).substring(0, 200)); } } for (const pump of pumps) { const ctx = sample.stations[pump.id]; if (ctx && ctx.data && Object.keys(ctx.data).length > 0) { console.log(pump.name + ':'); Object.entries(ctx.data).forEach(([key, val]) => { if (typeof val === 'object') { console.log(' ' + key + ': ' + JSON.stringify(val).substring(0, 200)); } else { console.log(' ' + key + ': ' + val); } }); } } console.log(''); if (i < Math.ceil(CAPTURE_SECONDS / 3) - 1) { await new Promise(r => setTimeout(r, 3000)); } } console.log('\n=== Summary ==='); console.log('Collected ' + samples.length + ' samples over ' + CAPTURE_SECONDS + 's'); })().catch(err => { console.error('Capture failed:', err); process.exit(1); });