- Update all submodule URLs from gitea.centraal.wbd-rd.nl to gitea.wbd-rd.nl - Add settler as proper submodule in .gitmodules - Add agent skills, function anchors, decisions, and improvements - Add Docker configuration and scripts - Add manuals and third_party docs - Update .gitignore with secrets and build artifacts - Remove stale .tgz build artifact Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
146 lines
4.5 KiB
JavaScript
146 lines
4.5 KiB
JavaScript
#!/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);
|
|
});
|