UNPKG

smithtek-nodered

Version:

NodeRED node to post data on SmithTek using MQTT.

178 lines (141 loc) 5.14 kB
module.exports = function (RED) { "use strict"; function StepTimerNode(config) { RED.nodes.createNode(this, config); const node = this; // ---- helpers ---- function toNumber(v, fallback = 0) { const n = Number(v); return Number.isFinite(n) ? n : fallback; } function clampInt(v, min, max, fallback) { let n = parseInt(v, 10); if (!Number.isFinite(n)) n = fallback; if (n < min) n = min; if (n > max) n = max; return n; } function unitsToMs(value, units, defaultUnits = "seconds") { const v = toNumber(value, 0); const u = units || defaultUnits; switch (u) { case "milliseconds": return v; case "minutes": return v * 60 * 1000; case "hours": return v * 60 * 60 * 1000; case "days": return v * 24 * 60 * 60 * 1000; case "seconds": default: return v * 1000; } } function safeClearTimeout(t) { if (t) clearTimeout(t); return null; } // ---- config ---- node.numOutputs = clampInt(config.outputs, 1, 128, 1); // prevent 0 outputs / silly values node.timeoutVal = Math.max(0, unitsToMs(config.step, config.stepUnits, "seconds")); node.pauseVal = Math.max(0, unitsToMs(config.pauseTime, config.pauseUnits, "seconds")); node.autostart = !!config.autostart; node.startupDelay = node.autostart ? Math.max(0, unitsToMs(config.startupDelay, config.startupUnits, "seconds")) : 0; // ---- state ---- node.nextOutput = 0; node.running = false; node.paused = false; node.started = false; let tickTimer = null; // sequencing loop let pauseTimer = null; // pause delay function setStatus(fill, shape, text) { node.status({ fill, shape, text }); } function buildNextOutputArray() { const outs = new Array(node.numOutputs).fill(null); // fresh message each time (avoid shared reference surprises) const msg = { payload: true }; outs[node.nextOutput] = msg; node.nextOutput = (node.nextOutput + 1) % node.numOutputs; return outs; } function stopSequencing() { tickTimer = safeClearTimeout(tickTimer); } function scheduleNextTick() { // If we’re stopped or paused, don’t schedule. if (!node.running || node.paused) return; tickTimer = safeClearTimeout(tickTimer); // Use setTimeout loop instead of setInterval (clean stop, less drift pain) tickTimer = setTimeout(() => { if (!node.running || node.paused) return; node.send(buildNextOutputArray()); setStatus("blue", "dot", "Running, next output: " + String(node.nextOutput)); scheduleNextTick(); }, node.timeoutVal); } function startNow() { node.running = true; node.paused = false; // fire immediately, then schedule next node.send(buildNextOutputArray()); setStatus("blue", "dot", "Running, next output: " + String(node.nextOutput)); scheduleNextTick(); } function pauseNow() { node.running = false; node.paused = true; stopSequencing(); pauseTimer = safeClearTimeout(pauseTimer); setStatus("red", "dot", "Paused, next output: " + String(node.nextOutput)); pauseTimer = setTimeout(() => { // resume after pause node.paused = false; startNow(); }, node.pauseVal); } // ---- initial status ---- setStatus("red", "ring", "Not Started"); // ---- input behavior ---- // If not running and not paused: start // If running and not paused: pause // If paused: ignore inputs (matches your original intent) node.on("input", function (msg) { // Optional: allow msg.payload commands without breaking old behaviour. // If you don’t want commands, you can delete this whole block. const p = msg && msg.payload; const cmd = (typeof p === "string") ? p.toLowerCase().trim() : null; if (cmd === "stop") { node.running = false; node.paused = false; stopSequencing(); pauseTimer = safeClearTimeout(pauseTimer); setStatus("red", "ring", "Stopped"); return; } if (cmd === "reset") { node.nextOutput = 0; setStatus(node.running ? "blue" : "red", "dot", "Reset, next output: 0"); return; } if (!node.running && !node.paused) { startNow(); } else if (!node.paused) { pauseNow(); } }); // ---- cleanup on redeploy ---- node.on("close", function () { stopSequencing(); pauseTimer = safeClearTimeout(pauseTimer); }); // ---- autostart ---- if (node.autostart && !node.started) { node.started = true; setStatus("yellow", "ring", "Autostart in " + Math.round(node.startupDelay / 1000) + "s"); setTimeout(() => { // Node-RED supported way to inject into self node.receive({ payload: "start" }); }, node.startupDelay); } } RED.nodes.registerType("smithtek_sequencer", StepTimerNode); };