I'm using node-red 3 to simulate processes.
I find myself distributing a setup
and a tick
signal to almost all nodes. That makes the flows visually cluttered.
Are there alternatives to handle global signals that many nodes use ?
Having events available (+ the possibility to handle events in function) nodes would be really nice. I've read the comments that this would break the wiring paradigm
of node-red, but in this case that would actually be a relief. The wires would still be used for 'normal' information-carrying signals.
I've been searching the docs for event-handling but without success so far.
When I have done this in the past I have used the message itself to be the tick. So every second (or whatever the simulation period) a message is injected with the latest process values and it flows through the simulation and produces a new value at the output.
Or even just let the messages flow round, with a delay in the loop to simulate the passing of time. For instance, here is a subflow that simulates a process delay and a couple of RC constants. The technique may not be appropriate for more complex processes though.
[{"id":"1156e9cbb379d09c","type":"subflow","name":"Process Simulation (2)","info":"","in":[{"x":37,"y":103,"wires":[{"id":"5f7bb53fd5e8503c"}]}],"out":[{"x":728.5,"y":294,"wires":[{"id":"f423d62b0901b0c3","port":0}]}]},{"id":"a78eda7549e10a97","type":"function","z":"1156e9cbb379d09c","name":"30 sec RC + 20","func":"// Applies a simple RC low pass filter to incoming payload values\nvar tc = 30*1000; // time constant in milliseconds\n\nvar lastValue = context.get('lastValue');\nif (typeof lastValue == \"undefined\") lastValue = msg.payload;\nvar lastTime = context.get('lastTime') || null;\nvar now = new Date();\nvar currentValue = msg.payload;\nif (lastTime === null) {\n // first time through\n newValue = currentValue;\n} else {\n var dt = now - lastTime;\n var newValue;\n \n if (dt > 0) {\n var dtotc = dt / tc;\n newValue = lastValue * (1 - dtotc) + currentValue * dtotc;\n } else {\n // no time has elapsed leave output the same as last time\n newValue = lastValue;\n }\n}\ncontext.set('lastValue', newValue);\ncontext.set('lastTime', now);\n\nmsg.payload = newValue + 20;\nreturn msg;","outputs":1,"noerr":0,"x":626.5,"y":207,"wires":[["f423d62b0901b0c3"]]},{"id":"87d30eb526f1da83","type":"inject","z":"1156e9cbb379d09c","name":"Inject -0.2 at start","repeat":"","crontab":"","once":true,"topic":"","payload":"-0.2","payloadType":"num","x":134.5,"y":30,"wires":[["5f7bb53fd5e8503c"]]},{"id":"4d29fb5c58ae7a0d","type":"function","z":"1156e9cbb379d09c","name":"10 sec RC","func":"// Applies a simple RC low pass filter to incoming payload values\nvar tc = 10*1000; // time constant in milliseconds\n\nvar lastValue = context.get('lastValue');\nif (typeof lastValue == \"undefined\") lastValue = msg.payload;\nvar lastTime = context.get('lastTime') || null;\nvar now = new Date();\nvar currentValue = msg.payload;\nif (lastTime === null) {\n // first time through\n newValue = currentValue;\n} else {\n var dt = now - lastTime;\n var newValue;\n \n if (dt > 0) {\n var dtotc = dt / tc;\n newValue = lastValue * (1 - dtotc) + currentValue * dtotc;\n } else {\n // no time has elapsed leave output the same as last time\n newValue = lastValue;\n }\n}\ncontext.set('lastValue', newValue);\ncontext.set('lastTime', now);\n\nmsg.payload = newValue;\nreturn msg;","outputs":1,"noerr":0,"x":451,"y":207,"wires":[["a78eda7549e10a97"]]},{"id":"5f7bb53fd5e8503c","type":"delay","z":"1156e9cbb379d09c","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":268,"y":104,"wires":[["1d73e8daa47ff9a2"]]},{"id":"e3afd7e9a2a0baa0","type":"function","z":"1156e9cbb379d09c","name":"2 msg transport delay","func":"// stores messages in a fifo until the specified number have been received, \n// then releases them as new messages are received.\n// during the filling phase the earliest message is passed on each time \n// a message is received, but it is also left in the fifo\nvar fifoMaxLength = 2;\nvar fifo = context.get('fifo') || [];\n// push the new message onto the top of the array, messages are shifted down and\n// drop off the front\nvar length = fifo.push(msg); // returns new length\nif (length > fifoMaxLength) {\n newMsg = fifo.shift();\n} else {\n // not full yet, make a copy of the msg and pass it on\n var newMsg = JSON.parse(JSON.stringify(fifo[0]));\n}\ncontext.set('fifo', fifo);\nreturn newMsg;","outputs":1,"noerr":0,"x":258,"y":208,"wires":[["4d29fb5c58ae7a0d"]]},{"id":"f423d62b0901b0c3","type":"function","z":"1156e9cbb379d09c","name":"Clear all except payload","func":"msg2 = {payload: msg.payload};\nreturn msg2;","outputs":1,"noerr":0,"x":545,"y":293,"wires":[[]]},{"id":"1d73e8daa47ff9a2","type":"range","z":"1156e9cbb379d09c","minin":"0","maxin":"1","minout":"0","maxout":"100","action":"scale","round":false,"name":"","x":87,"y":208,"wires":[["e3afd7e9a2a0baa0"]]}]
@Colin
thank for the tip, I had actually tried that but it gives me two problems
- I still need to disperse the
tick
andreset
housekeeping signals
to all the first nodes in every chain which clutters the flows. - In my energy management simulation, when linking two or more (let's say
n
)consumer
nodes - which can have different power ratings - to astorage
node, the energy consumption messages do arrive atn
times the usual speed, but thetick
signal itself also arrives atn
times the normal rate so I lose the capability to discern how much total consumption is linked to the storage.
If you think about it, the global context is obviously a way to distribute information (variables) outside of the node-and-wires paradigm. So it is not that far fetched to have the same option for events
I found this work-around but it is not very pretty
on tick startup
function node with thisstartup code
setInterval(() => {
const timerArray = global.get('timer')
timerArray.forEach(callback);
}, 1000)
This startup code in the regular function nodes (producers, consumers, storage, power meters, ...)
const myCallback = () => { node.send({ topic: 'consumer', payload: 200 })}
global.set('timer', (global.get('timer') ?? []).push(myCallback))
I don't have a clear idea of your problem but especially because it is an event you can use the link nodes to unclutter.
Your ticks and init node can go to a link in node. And you can distribute this with a link out node. 1- n is possible here.
This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.