Error-circular - where is this coming from?

or just use context.myname like I suggested originally in the other thread...
BUT you need to get rid of that circular reference as there is no need for it - and I suppose this is one good way to find it :slight_smile:

1 Like

I thought context.myname was deprecated.
The circular reference isn't necessarily an issue. Perhaps timers have circular references.
However there is a better pattern for achieving what @hazymat is trying to achieve, I just can't find an example of it immediately.

This was also my understanding :slightly_smiling_face: but it seems to be valid.

1 Like

It is deprecated because anything stored that way will not get persisted.

However, it still has its uses - such as this precise scenario where it is meaningless to persist a timer id object.

1 Like

Hi Colin, you have been enormously helpful so far, do let me know if it comes to mind. Would appreciate your input.

I don't profess to be a superb architect of code (! lol - I'm pretty crap) and was surprised nobody else made a really simple delay based number fader like this hence resorting to giving it a go myself. So any input is greatly appreciated.

Indeed, I could not find a node or flow in Node-RED library that address this need.

The subject of dimming light has already been discussed once in our forum and people provided a lot of insights and suggestions.

This smoothing problem resembles somewhat the capabilities that CSS give us to "ease" transitions.

It is feasible to build a solution without using timer functions (setTimeout , setInterval) since the smoothing factor depends only on the time elapsed since the beginning of the fading (only subtracting current time from the start time). Plenty of smoothing functions can be created based on easing functions or bezier curves.

1 Like

I knew I had done something very similar, and @Andrei reminded me. In the flow he linked to I posted a function node that does something very similar that almost does what you want.

[{"id":"5f8de1b.3f7c8a","type":"function","z":"6dc690a3.1abc88","name":"Slew rate limit","func":"// Limits the slew rate incoming payload values\nvar maxRate = 60  ;         // max slew rate units/minute\nvar period = 1000;          // period in millisecs to send new values, 1000 is 1 per second\n\nvar newValue = Number(msg.payload);\nvar timer = context.get('timer') || 0;\n// check the value is  a number\nif (!isNaN(newValue) && isFinite(newValue)) {\n    var target = msg.payload;\n    context.set('target', target);\n    // set last value to new one if first time through\n    var lastValue = context.get('lastValue');\n    if (typeof lastValue == \"undefined\" || lastValue === null) {\n        lastValue = newValue;\n        context.set('lastValue', newValue)\n    }\n    // calc new value\n    msg.payload = calcOutput();\n    // stop and restart the timer\n    if (timer) {\n        clearTimeout(timer);\n        context.set('timer', null);\n    }\n    timer = setInterval(function(){\n        // the timer has run down calculate next value and send it\n        var newValue = calcOutput();\n        if (newValue != context.get('lastValueSent')) {\n            context.set('lastValueSent', newValue)\n            node.send({payload: newValue});\n        }\n    },period);\n    context.set('timer', timer);\n    context.set('lastValueSent', msg.payload);\n} else {\n    // payload is not a number so ignore it\n    msg = null;\n}\nreturn msg;\n\n// determines the required output value\nfunction calcOutput() {\n    var lastValue = context.get('lastValue');\n    var target = context.get('target');\n    // set to current value if first time through\n    if (typeof lastValue == \"undefined\" || lastValue === null) lastValue = newValue;\n    var now = new Date();\n    var lastTime = context.get('lastTime') || now;\n    // limit value to last value +- rate * time\n    var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);\n    if (target > lastValue) {\n        newValue = Math.min( lastValue + maxDelta, target);\n    } else {\n        newValue = Math.max( lastValue - maxDelta, target);\n    }\n    context.set('lastValue', newValue);\n    context.set('lastTime', now);   \n    return newValue;\n}","outputs":1,"noerr":0,"x":433,"y":232,"wires":[["fb203846.5494b"]]},{"id":"fb203846.5494b","type":"debug","z":"6dc690a3.1abc88","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":650.5,"y":231,"wires":[]},{"id":"df5dd6ab.d3bdd","type":"inject","z":"6dc690a3.1abc88","name":"","topic":"","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":159.5,"y":176,"wires":[["5f8de1b.3f7c8a"]]},{"id":"2a7e98bc.cd7f18","type":"inject","z":"6dc690a3.1abc88","name":"","topic":"","payload":"10","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":158,"y":237,"wires":[["5f8de1b.3f7c8a"]]},{"id":"381ece91.b0c4a2","type":"ui_slider","z":"6dc690a3.1abc88","name":"","label":"slider","group":"41737f1.c5e588","order":0,"width":0,"height":0,"passthru":false,"topic":"","min":0,"max":"10","step":1,"x":172.5,"y":304,"wires":[["5f8de1b.3f7c8a"]]},{"id":"41737f1.c5e588","type":"ui_group","z":"","name":"Default","tab":"bb9b58bd.29621","disp":true,"width":"6"},{"id":"bb9b58bd.29621","type":"ui_tab","z":"","name":"Home","icon":"dashboard"}]

Again, though, I suggest not saving the timer id in persistent store. Make sure that if you are using the local file store for context that you also provide a memory store and use that.

About using context.myname @knolleary said

I thought that deprecated meant that at some point in the future it will be removed and therefore it should not be used.
A further question, am I right in thinking that if one asks for a local file store and a memory store, that when using the memory store the operation using context.set() and specifying the memory store is identical to using context.myname?

@Andrei
Right! the example is in the Wiki (Fading)

@hazymat
The Fadingexample above can do more than you need, so I didn't mention it.
But it can be simplified for your use case.

@Colin
I'm surprised that you don't use the contextStorage property.
This is one of the features I was waiting since I'm using Node-RED. I guess 80% or more running v0.19 or greater use it with the default localfilesystem.

@coliin

is identical to using context.myname ?

no - that is why it is "deprecated". As this thread has shown using .set works by serialising the data. This is necessary if you want to store the data "offboard" eg in a file / database / over the network. Whereas context.myname can handle non-serialisable data like objects containing functions or circular references - and is ONLY held in memory.
So in most cases it will appear the same to the user... but is NOT identical.

Has that always been the case? If so then the circular error seen is not inherent in storing timer id using context.set but must be something about this specific use case.
Back on the deprecation of context.myname. If it has a useful purpose that is not covered by context.set then I would have thought it cannot be deprecated. Unless my understanding of deprecated is wrong (that it should not be used as it will be removed from node-RED at some point).

I use MQTT with retained topics for this. It has a number of advantages:

  1. Any subscribers to the topic get notified when the value changes and can take action. This does not happen with persistent context variables.
  2. The persistent values are available across systems. For example I have similar dashboards in a number of different node-red systems. They can all subscribe to the same topics on the same server (where appropriate) so that, for example, if I have a switch on the dashboard and I switch it on one system then that is immediately echoed on the dashboards on the other systems.
  3. Automatic initialisation of dependent objects on re-deploy. If I redeploy node-RED then the switch mentioned above automatically gets initialised. To achieve that with context variables requires something like using inject nodes configured to inject the value at startup.

I am not saying there are no uses for persistent context, just that I have not found the need so far.

BTW assuming you have defined contextStorage like:

    contextStorage: {
        default: {
	          module:"localfilesystem"
        },
        memoryOnly: {
            module: "memory"
        }
    },

You can do it this way if you want to avoid Error-circular:

context.set('id', id, 'memoryOnly');
and
context.get('id', 'memoryOnly');

Yes. There is nothing in setTimeout that is circular - but the way he is calling his function from inside his function makes it so.

context is a javascript object - we can't remove it or stop people using it in normal javascript ways (such as adding a property) - but we can advise against using it if they want to be able to persist values when restarting Node-RED.

Does that fix it for the users case? If so that appears to conflict with @dceejay's statement that context.set with memory store will still do the serialisation.

I've tried several times and it seems to work.

Deprecated means you shouldn't use it as there's no guarantee it will be there in the future. But nor is it a guarantee that it'll be removed.

In this instance, the only result of preventing a user from setting properties on the context object directly would be to break a lot of people's flows needlessly. There would be no benefit in doing that.

It was deprecated very early on in favour of context.set() because we did not want users to assume their flows would magically start persisting those values. That was long before we got into the detailed design and implementation work around persistent context.

During that work, it became clear there was still a role to play for setting context.foo directly - for volatile values that cannot be persisted, but have no need to be persisted. We just haven't spelt that out in the documentation.

@dceejay, This seems to suggest that context.set of circular object with memory store is ok
image

[{"id":"e5ef82be.d668c8","type":"function","z":"514a90a5.c7bae8","name":"Circular context set","func":"let a = { b: null };\nlet b = { a: a };\na.b = b;\ncontext.set(\"a\", a);\nmsg.payload = context.get(\"a\");\nreturn msg;","outputs":1,"noerr":0,"x":321.5,"y":358,"wires":[["3e4a4081.cbe08"]]},{"id":"70f2ea5b.d5a6e4","type":"inject","z":"514a90a5.c7bae8","name":"Tryit","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":129.5,"y":359,"wires":[["e5ef82be.d668c8"]]},{"id":"3e4a4081.cbe08","type":"debug","z":"514a90a5.c7bae8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":534,"y":357,"wires":[]}]

The in-memory store does not do any serialisation - it keeps the objects in memory, as-is. So you can throw any junk you want in there.

As soon as you switch the in-memory store for a file-based store, that junk will either get lost or cause errors.