Error-circular - where is this coming from?

tgud1

(Sorry, totally inappropriate.)

2 Likes

@knolleary @dceejay, I am still not clear whether there is any difference (from the users point of view) between using context.myvar and context.get/set() when used with the memory store. Can we have a definitive answer on this?

When used with in-memory context store, I can't immediately think of a difference.

Until the user decides they want to start persisting their data and find they have to change all their code because they used the 'wrong' style of api.

10 seconds later... I can think of a difference when you look at flow context.

flow.myvar and flow.set('myvar',123) are accessing two different myvar objects - you cannot inter-mix the two styles.

If you need to access that context value from any other non-Function node (such as Change node), then you must use flow.get/set in your Function nodes.

A value set by flow.myvar will not be visible to other nodes that access context using the get/set functions.

That is worth remembering, but the reason I am asking is that when I asked this earlier (post #21) Dave replied saying they were not the same, as recursive objects could not be saved using context.get/set even with memory store. Unless he misread my question.

We are just trying to give clear guidance of how context can/should be used.

It is much easier to say context.get/set should not be used for those sorts of objects rather than adding in all sorts of qualifiers of "except if you use this type of context store".

Yes, technically, the memory store can be used for anything. But the worry is it leads a developer to start using it for things they don't realise is not portable to other context stores.

OK. Not sure you have completely succeeded :slight_smile:
Considering how unusual recursive objects actually are, I would have thought possibly it might be better to recommend always using get/set but just point out that persistent storage cannot be used for recursive objects so, of the stores provided by node-RED, only the memory store can be used for such objects.

I've found some spare time to build a general solution.

You don't need to understand the code, just install node-red-contrib-dsm from the Manage palette menu and import the flow from the dsm Wiki Fade in out.

1 Like

Hi, I really like this. Two possible improvements, would you consider them?

1/ Currently you need to set msg.topic to start. Why is this required? It's useful to retain the msg.topic (passed into the input) for e.g. controlling lights on the correct circuit

2/ Is there a good way to set a "pre-delay"? e.g. wait x seconds after receiving the input before starting the fade? This is great for sleep timers. For example, "Alexa, set bedroom to sleep", this would give you 10 minutes of low light, then start to fade gently.

can't you use the default delay node for this pre-delay?

It's possible, although this would delay the first value from being sent (i.e. the start value). You could wire a change node in parallel, but then

a) the fader reset (stop) command wouldn't work. (unless you piped that through the delay as well, which is messy)
b) you'd have to manually keep the change node's value in sync with the fader start value.

msg.topic is the default to trigger the dsm, so you can send start or stop. However the default can be overwritten for example:

    "triggerInput": "cmd",
    "stateOutput": "cmd",

Now you can trigger the dsm by:

msg.cmd = "start"
msg.cmd = "stop"

For your use case I've added an additional parameter:

{
    "initialdelay": 2000,
    "start": 0,
    "end": 1,
    "duration": 3000,
    "step": 100
}

Flow:

[{"id":"b3c0022e.f99bc","type":"dsm","z":"6ff0723.8c6b78c","name":"fade in/out","sm_config":"{\n    \"triggerInput\": \"cmd\",\n    \"stateOutput\": \"cmd\",\n    \"currentState\": \"stopped\",\n    \n    \"states\": {\n        \"stopped\": {\n            \"start\": \"started\",\n            \"stop\": \"stopped\"\n        },\n        \"started\": {\n            \"fade_in\": \"fade_in\",\n            \"fade_out\": \"fade_out\",\n            \"stop\": \"stopped\"\n        },\n        \"fade_in\": {\n            \"fade_in\": \"fade_in\",\n            \"stop\": \"stopped\"\n        },\n        \"fade_out\": {\n            \"fade_out\": \"fade_out\",\n            \"stop\": \"stopped\"\n        }\n    },\n    \"data\": {\n        \"initialdelay\": 2000,\n        \"start\": 0,\n        \"end\": 1,\n        \"duration\": 3000,\n        \"step\": 100\n    },\n    \"methods\": {\n        \"init\": [\n            \"sm.tick = function(tran, msg) {\",\n            \"   timeout.id = setTimeout(function() {\",\n            \"       sm.text = 'fading ' + sm.data.duration + ' - ' + sm.data.actual;\",\n            \"       msg.payload = sm.data.actual;\",\n            \"       node.send(msg);\",\n            \"       resume(tran, msg);\",\n            \"   }, sm.data.step);\",\n            \"};\",\n            \"sta.fill = 'grey';\"\n        ],\n        \"onBeforeTransition\": [\n            \"if (typeof msg.payload.initialdelay !== 'undefined') sm.data.initialdelay = msg.payload.initialdelay;\",\n            \"if (typeof msg.payload.start !== 'undefined') sm.data.start = msg.payload.start;\",\n            \"if (typeof msg.payload.end !== 'undefined') sm.data.end = msg.payload.end;\",\n            \"if (typeof msg.payload.duration !== 'undefined') sm.data.duration = msg.payload.duration;\",\n            \"if (typeof msg.payload.step !== 'undefined') sm.data.step = msg.payload.step;\"\n        ],\n        \"start\": [\n            \"clearTimeout(timeout.id);\",\n            \"timeout.id = setTimeout(function() {\",\n            \"   sm.data.delta = (sm.data.step / sm.data.duration) * Math.abs(sm.data.start - sm.data.end);\",\n            \"   sm.data.actual = sm.data.start;\",\n            \n            \"   if (sm.data.end > sm.data.start) {\",\n            \"       sm.text = 'fade_in';\",\n            \"       resume('fade_in', msg);\",\n            \"   } else {\",\n            \"       sm.text = 'fade_out';\",\n            \"       resume('fade_out', msg);\",\n            \"   };\",\n            \"}, sm.data.initialdelay);\",\n            \"sm.text = 'initial delay ' + sm.data.initialdelay;\",\n            \"msg.payload = sm.data.start;\",\n            \"node.send(msg);\"\n        ],\n        \"fade_in\": [\n            \"if (sm.data.actual < sm.data.end) {\",\n            \"   sm.data.actual = Math.round((sm.data.actual + sm.data.delta) * 100) / 100;\",\n            \"   if (sm.data.actual > sm.data.end) {sm.data.actual = sm.data.end;};\",\n            \"   sm.tick('fade_in', msg);\",\n            \"} else {\",\n            \"   resume('stop', msg);\",\n            \"};\"\n        ],\n        \"fade_out\": [\n            \"if (sm.data.actual > sm.data.end) {\",\n            \"   sm.data.actual = Math.round((sm.data.actual - sm.data.delta) * 100) / 100;\",\n            \"   if (sm.data.actual < sm.data.end) {sm.data.actual = sm.data.end;};\",\n            \"   sm.tick('fade_out', msg);\",\n            \"} else {\",\n            \"   resume('stop', msg);\",\n            \"};\"\n        ],\n        \"stop\": [\n            \"sm.text = sm.currentState;\",\n            \"clearTimeout(timeout.id);\"\n        ],\n        \"onAfterTransition\": \"output = false;\",\n        \"status\": {\n            \"fill\": {\n                \"get\": \"sm.currentState === 'stopped' ? 'grey': 'green';\"\n            },\n            \"shape\": \"dot\",\n            \"text\": {\n                \"get\": \"sm.text || sm.currentState;\"\n            }\n        }\n    }\n}","x":490,"y":2200,"wires":[["4e43e7f0.d1eee8"]]},{"id":"5226bacb.b02654","type":"inject","z":"6ff0723.8c6b78c","name":"fade in","topic":"mytopic","payload":"{\"initialdelay\":2000,\"start\":0,\"end\":1,\"duration\":3000,\"step\":100}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":2200,"wires":[["a027285c.4bf7a8"]]},{"id":"de736c65.b889a","type":"inject","z":"6ff0723.8c6b78c","name":"fade out","topic":"mytopic","payload":"{\"initialdelay\":2000,\"start\":1,\"end\":0,\"duration\":5000,\"step\":500}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":2240,"wires":[["a027285c.4bf7a8"]]},{"id":"6820c9c9.829228","type":"inject","z":"6ff0723.8c6b78c","name":"stop","topic":"mytopic","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":2280,"wires":[["4ec1aed8.ae5"]]},{"id":"4e43e7f0.d1eee8","type":"debug","z":"6ff0723.8c6b78c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":650,"y":2200,"wires":[]},{"id":"a027285c.4bf7a8","type":"change","z":"6ff0723.8c6b78c","name":"cmd","rules":[{"t":"set","p":"cmd","pt":"msg","to":"start","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":2200,"wires":[["b3c0022e.f99bc"]]},{"id":"4ec1aed8.ae5","type":"change","z":"6ff0723.8c6b78c","name":"cmd","rules":[{"t":"set","p":"cmd","pt":"msg","to":"stop","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":2280,"wires":[["b3c0022e.f99bc"]]}]

@cflurin Hi, just implemented the fades using your latest code snippet. It's so much tidier and works like a charm. Thank you so much for your help!

@hazymat: You are welcome.

The latest version in the Wiki: https://github.com/cflurin/node-red-contrib-dsm/wiki/Fade-in--out
has an additional property unit so you can set milliseconds, seconds or minutes.

Remember to change the triggerInput from topic to cmd for your use case.

1 Like