Watch Global Variable

In node red, how can I watch the value of a global variable and do something with it when the value changes? I'm building an application that has a front-end user interface, and a sort of back end that does a control loop with values set by the user. I would like to keep my flows separate by setting the value of global variables on the front end, and have the back end immediately take action when they are set by front end. Is there any concept of a $watch like I would find in Angular or any other way to do this?

This topic and post may help Lighting controller ideas - #19 by Steve-Mcl

Instead of using globals, you could use Retained Topics in MQTT. Then, on startup and whenever it changes, all connected nodes will be sent a message.

To directly answer your question, you cannot. I've long wanted to write a context handler that did have notifications but though I started a couple of times, I fear neither my Node-fu nor my patience was sufficient. So I'm afraid that it remains a theory.

Your best bet is, as Colin says, to use MQTT instead. Alternatively, make sure some other trigger is fired when updating the variable.

Inside the function node, you can access the global context, which may be considered a global variable for the intentions of what you may be doing. Then, it's just a matter of creating a class that extends event emitter and check when properties of your class instance have a value changed using a setter and emit an event with the details. There probably might be some nodes to do such a thing, but in the meanwhile you can experiment with this.

[{"id":"1b5f86f6ecfa2556","type":"function","z":"e9f5eca971f24054","name":"event emitter","func":"//const changeWatcher = global.get('changeWatcher');\n\n//changeWatcher.fieldOne = msg.payload;\n\n//changeWatcher.fieldTwo = msg.topic;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\nconst { EventEmitter } = events;\n\nclass ChangeWatcher extends EventEmitter {\n  #fieldOne;\n  #fieldTwo;\n\n  constructor() {\n    super();\n  }\n\n  get fieldOne() {\n    return this.#fieldOne;\n  }\n\n  set fieldOne(val) {\n    if (val !== this.#fieldOne) {\n      this.emit('change', { fieldName: 'fieldOne', oldValue: this.#fieldOne, newValue: val });\n      this.#fieldOne = val;\n    }\n  }\n\n  get fieldTwo() {\n    return this.#fieldTwo;\n  }\n\n  set fieldTwo(val) {\n    if (val !== this.#fieldTwo) {\n      this.emit('change', { fieldName: 'fieldTwo', oldValue: this.#fieldTwo, newValue: val });\n      this.#fieldTwo = val;\n    }\n  }\n}\n\nconst changeWatcher = new ChangeWatcher();\n\nchangeWatcher.on('change', data => {\n\n  node.send({ payload: data });\n  \n});\n\nglobal.set('changeWatcher', changeWatcher);","finalize":"// Code added here will be run when the\n// node is being stopped or re-deployed.\nconst changeWatcher = global.get('changeWatcher');\n\nif (changeWatcher) {\n  changeWatcher.removeAllListeners('change');\n\n  global.set('changeWatcher', undefined);\n}","libs":[{"var":"events","module":"events"}],"x":1330,"y":280,"wires":[["5650a5cf2bd8195d"]]},{"id":"98443450c14f6769","type":"inject","z":"e9f5eca971f24054","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"world","payload":"hello","payloadType":"str","x":1100,"y":240,"wires":[["13010b8b25a3a4fb"]]},{"id":"5650a5cf2bd8195d","type":"debug","z":"e9f5eca971f24054","name":"debug 20","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1560,"y":280,"wires":[]},{"id":"58ae53579b43437f","type":"inject","z":"e9f5eca971f24054","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"world","payload":"bye","payloadType":"str","x":1100,"y":320,"wires":[["13010b8b25a3a4fb"]]},{"id":"13010b8b25a3a4fb","type":"function","z":"e9f5eca971f24054","name":"change field","func":"const changeWatcher = global.get('changeWatcher');\n\nif (changeWatcher) {\n  changeWatcher.fieldOne = msg.payload;\n\n  changeWatcher.fieldTwo = msg.topic;\n}","outputs":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1330,"y":440,"wires":[]}]

You would not be limited to just the two functions nodes. You could have multiple of each that update the ChangeWatcher instance or listen to its changes. If you are not skilled with javascript, then you may not want to experiment with this as it may seem complicated. Consider this to be a quick mockup test until you develop a real node that is tailored to your exact needs.

1 Like

Very smart, like it!!

1 Like

Alternatively, of course, you can just use a link node from where the value is changed, to send a message to the node that needs to know.

1 Like

Here is my method. This will take a minute to explain, so bear with me. I have four possible presence states that dictate what mode my house is in. I'm using Hubitat, iPhones and Withings sleep sensors to set those states. They are home/away or asleep/awake for me and both states again for my wife. This results in nine possible states. (It's nine because asleep assumes I'm home and away assumes I'm awake. So I can discard states like Away/Asleep.) For clarity the states are:

Both Away
I'm Asleep (Home Alone)
I'm Awake & She's Asleep (Both Home)
I'm Awake (Home Alone)
Both Awake (Both Home)
She's Awake (Home Alone)
She's Awake & I'm Asleep (Both Home)
She's Asleep (Home Alone)
Both Asleep (Both Home)

Now each of those states has between three and four possible reasons for those states. For example, if I'm awake and home alone, it could be because I just woke up and she's gone, or it could be that we were both gone and I just got home, or it could be that we were both home and she's left.

When any of the four main states change, the time is set in a global variable named after the state. At the same time, I also set a global variable called mostCurrentGlobalStatusChangeTime. All states are then run through a state machine to make sure they conform to my scheme. From there, a switch node sends the overall state to the correct one of nine routing switch nodes; which compares the mostCurrentGlobalStatusChangeTime to the timestamps of the relevant global variables timestamps.

I've attached some pictures just for clarity (or at least to illustrate the complexity of it all). If you'd like more info, I'd be happy to post some flows.

Picture #1 shows the presence flows that run parallel to each other and eventually into state machines.

Picture #2 shows the state machine output being routed to the nine possible state switches. Those switches then compare the times and, as you can see from the dotted lines, route the payload based on the relationship of when something happens in any given state.

Picture #3 is a close up of picture #2 showing an example of me waking up in the morning and the routing as a result.

Picture #4 is just a screen shot of how I compare the timestamps of each global variable to the mostCurrentGlobalStatusChangeTime global variable. In this case it's the Both Awake/Both Home scenario.
Screenshot 2023-05-19 at 12.47.53 AM

It's certainly way more complex than it needs to be and it may or may not help you, but it does work very well. Hopefully, this makes sense, but it's late and I'm not sure any of this is coherent. :joy:

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.