This code NEARLY works. Help needed. (Function node JS code)

I have 8 (0-7) Neopixel leds in a strip.

At a given time I want to walk a 3 led mark along them from 0 to 7.

This is the code I have now.

if (msg.delay_time == undefined)
    msg.delay_time = 400;
var delay_time = msg.delay_time;
if (msg.sunset == undefined)
    msg.sunset = "196,10,0";
var sunset = msg.sunset;

sendLater(0, msg, "rgb,0," + sunset);
sendLater(delay_time * 1, msg, "rgb,1," + sunset);
sendLater(delay_time * 2, msg, "rgb,1," + sunset);
sendLater(delay_time * 3, msg, "rgb,2," + sunset);
if (msg.existing[0] == undefined) {
    msg.existing[0] = "0,0,0";
}
sendLater(delay_time * 4, msg, "rgb,0," + msg.existing[0]);
sendLater(delay_time * 5, msg, "rgb,3," + sunset);
if (msg.existing[1] == undefined) {
    msg.existing[1] = "0,0,0";
}
sendLater(delay_time * 6, msg, "rgb,1," + msg.existing[1]);
sendLater(delay_time * 7, msg, "rgb,4," + sunset);
if (msg.existing[2] == undefined) {
    msg.existing[2] = "0,0,0";
}
sendLater(delay_time * 8, msg, "rgb,2," + msg.existing[2]);
sendLater(delay_time * 9, msg, "rgb,5," + sunset);
if (msg.existing[3] == undefined) {
    msg.existing[3] = "0,0,0";
}
sendLater(delay_time * 10, msg, "rgb,3," + msg.existing[3]);
sendLater(delay_time * 11, msg, "rgb,6," + sunset);
if (msg.existing[4] == undefined) {
    msg.existing[4] = "0,0,0";
}
sendLater(delay_time * 12, msg, "rgb,4," + msg.existing[4]);
sendLater(delay_time * 13, msg, "rgb,7," + sunset);

if (msg.existing[5] == undefined) {
    msg.existing[5] = "0,0,0";
}
sendLater(delay_time * 14, msg, "rgb,5," + msg.existing[5]);
if (msg.existing[6] == undefined) {
    msg.existing[6] = "0,0,0";
}
sendLater(delay_time * 15, msg, "rgb,6," + msg.existing[6]);

sendLater(delay_time * 16, msg, "rgb,7," + msg.existing[7]);
if (msg.existing[7] == undefined) {
    msg.existing[7] = "0,0,0";
}

/**
* @param {number} delayMS
* @param {NodeMessage} msg
* @param {string} payload
*/
function sendLater(delayMS, msg, payload) {
//    node.warn(`In sendLater(${delayMS},${msg},${payload}) - setting up a timer to send payload '${payload}' in ${(delayMS/1000).toFixed(2)} seconds time`)
    const callback = function (msg, payload) {
//        node.warn("Sending delayed payload " + payload);
        msg.payload = payload;
        node.send(msg);
    }
    setTimeout(callback, delayMS, RED.util.cloneMessage(msg), payload)
}

I don't know how I missed it for so long but there is a bug in the code.

The 3 red LED's walk along the strip but when it gets to number 6, there is a problem.

My best effort to explain what happens:
LED 0 is set to RED
LED 1 is set to RED
LED 2 is set to RED and 0 is turned off.
LED 3 is set to RED and 1 is turned off.
LED 4 is set to RED and 2 is turned off.
(repeat)
LED 6 is set to RED and 4 is tuned off.
LED 7 is set to RED and 5 is turned off - and also 6.

I think (hope) if I look at it more I may see the mistake, but.....

Sod's law says if I don't ask, I won't be able to see the mistake.

So I'm asking.

Thanks in advance.

here is one stand out issue. Surely that should be:

if (msg.existing[7] == undefined) {
    msg.existing[7] = "0,0,0";
}
sendLater(delay_time * 16, msg, "rgb,7," + msg.existing[7]);
1 Like

Yeah, ok, I'll cop that.

But I suspect it is something to do with how the code works.

If there is a next LED things are good, but code for 6 looks different somehow and I think that is the problem.

Some improvements to your code:


const delay_time = msg.delay_time | 400;
const sunset = msg.sunset || "196,10,0";

// Ensure msg.existing is an array
let existing = msg.existing
if (!Array.isArray(existing)) {
    existing = []
}

// Ensure elements 0 ~ 7 have a _some value_
for (let index = 0; index < 8; index++) {
    existing[index] = existing[index] || "0,0,0"
}

sendLater(0, msg, "rgb,0," + sunset);
sendLater(delay_time * 1, msg, "rgb,1," + sunset);
sendLater(delay_time * 2, msg, "rgb,1," + sunset);
sendLater(delay_time * 3, msg, "rgb,2," + sunset);
sendLater(delay_time * 4, msg, "rgb,0," + existing[0]);
sendLater(delay_time * 5, msg, "rgb,3," + sunset);
sendLater(delay_time * 6, msg, "rgb,1," + existing[1]);
sendLater(delay_time * 7, msg, "rgb,4," + sunset);
sendLater(delay_time * 8, msg, "rgb,2," + existing[2]);
sendLater(delay_time * 9, msg, "rgb,5," + sunset);
sendLater(delay_time * 10, msg, "rgb,3," + existing[3]);
sendLater(delay_time * 11, msg, "rgb,6," + sunset);
sendLater(delay_time * 12, msg, "rgb,4," + existing[4]);
sendLater(delay_time * 13, msg, "rgb,7," + sunset);
sendLater(delay_time * 14, msg, "rgb,5," + existing[5]);
sendLater(delay_time * 15, msg, "rgb,6," + existing[6]);
sendLater(delay_time * 16, msg, "rgb,7," + existing[7]);

/**
* @param {number} delayMS
* @param {NodeMessage} msg
* @param {string} payload
*/
function sendLater(delayMS, msg, payload) {
    //    node.warn(`In sendLater(${delayMS},${msg},${payload}) - setting up a timer to send payload '${payload}' in ${(delayMS/1000).toFixed(2)} seconds time`)
    const callback = function (msg, payload) {
        //        node.warn("Sending delayed payload " + payload);
        msg.payload = payload;
        node.send(msg);
    }
    setTimeout(callback, delayMS, RED.util.cloneMessage(msg), payload)
}

It makes it more readable - perhaps you can spot the issue with this version?

And lastly, an even better (more node-red way)

Code:


const delay_time = msg.delay_time | 400;
const sunset = msg.sunset || "196,10,0";

// Ensure msg.existing is an array
let existing = msg.existing
if (!Array.isArray(existing)) {
    existing = []
}

// Ensure elements 0 ~ 7 have a _some value_
for (let index = 0; index < 8; index++) {
    existing[index] = existing[index] || "0,0,0"
}

sendMessage(0, msg, "rgb,0," + sunset);
sendMessage(delay_time * 1, msg, "rgb,1," + sunset);
sendMessage(delay_time * 2, msg, "rgb,1," + sunset);
sendMessage(delay_time * 3, msg, "rgb,2," + sunset);
sendMessage(delay_time * 4, msg, "rgb,0," + existing[0]);
sendMessage(delay_time * 5, msg, "rgb,3," + sunset);
sendMessage(delay_time * 6, msg, "rgb,1," + existing[1]);
sendMessage(delay_time * 7, msg, "rgb,4," + sunset);
sendMessage(delay_time * 8, msg, "rgb,2," + existing[2]);
sendMessage(delay_time * 9, msg, "rgb,5," + sunset);
sendMessage(delay_time * 10, msg, "rgb,3," + existing[3]);
sendMessage(delay_time * 11, msg, "rgb,6," + sunset);
sendMessage(delay_time * 12, msg, "rgb,4," + existing[4]);
sendMessage(delay_time * 13, msg, "rgb,7," + sunset);
sendMessage(delay_time * 14, msg, "rgb,5," + existing[5]);
sendMessage(delay_time * 15, msg, "rgb,6," + existing[6]);
sendMessage(delay_time * 16, msg, "rgb,7," + existing[7]);

/**
* @param {number} delay
* @param {NodeMessage} msg
* @param {string} payload
*/
function sendMessage(delay, msg, payload) {
    const m = RED.util.cloneMessage(msg)
    m.payload = payload
    m.delay = delay
    node.send(m);
}

Just feed that to a delay node set to have variable delay (instead of set timeouts, callbacks etc)

example:

[{"id":"16518bb781195249","type":"debug","z":"90e2df527c725ffb","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":560,"y":280,"wires":[]},{"id":"5e2e7772c79d63e0","type":"function","z":"90e2df527c725ffb","name":"function 2","func":"\nconst delay_time = msg.delay_time | 400;\nconst sunset = msg.sunset || \"196,10,0\";\n\n// Ensure msg.existing is an array\nlet existing = msg.existing\nif (!Array.isArray(existing)) {\n    existing = []\n}\n\n// Ensure elements 0 ~ 7 have a _some value_\nfor (let index = 0; index < 8; index++) {\n    existing[index] = existing[index] || \"0,0,0\"\n}\n\nsendMessage(0, msg, \"rgb,0,\" + sunset);\nsendMessage(delay_time * 1, msg, \"rgb,1,\" + sunset);\nsendMessage(delay_time * 2, msg, \"rgb,1,\" + sunset);\nsendMessage(delay_time * 3, msg, \"rgb,2,\" + sunset);\nsendMessage(delay_time * 4, msg, \"rgb,0,\" + existing[0]);\nsendMessage(delay_time * 5, msg, \"rgb,3,\" + sunset);\nsendMessage(delay_time * 6, msg, \"rgb,1,\" + existing[1]);\nsendMessage(delay_time * 7, msg, \"rgb,4,\" + sunset);\nsendMessage(delay_time * 8, msg, \"rgb,2,\" + existing[2]);\nsendMessage(delay_time * 9, msg, \"rgb,5,\" + sunset);\nsendMessage(delay_time * 10, msg, \"rgb,3,\" + existing[3]);\nsendMessage(delay_time * 11, msg, \"rgb,6,\" + sunset);\nsendMessage(delay_time * 12, msg, \"rgb,4,\" + existing[4]);\nsendMessage(delay_time * 13, msg, \"rgb,7,\" + sunset);\nsendMessage(delay_time * 14, msg, \"rgb,5,\" + existing[5]);\nsendMessage(delay_time * 15, msg, \"rgb,6,\" + existing[6]);\nsendMessage(delay_time * 16, msg, \"rgb,7,\" + existing[7]);\n\n/**\n* @param {number} delay\n* @param {NodeMessage} msg\n* @param {string} payload\n*/\nfunction sendMessage(delay, msg, payload) {\n    const m = RED.util.cloneMessage(msg)\n    m.payload = payload\n    m.delay = delay\n    node.send(m);\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":280,"wires":[["2b60646066e7c095"]]},{"id":"c2918b5f5e94fbc0","type":"inject","z":"90e2df527c725ffb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":280,"wires":[["5e2e7772c79d63e0"]]},{"id":"2b60646066e7c095","type":"delay","z":"90e2df527c725ffb","name":"","pauseType":"delayv","timeout":"1","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":420,"y":280,"wires":[["16518bb781195249"]]}]
1 Like

Ah, that's nice!

I like how you use the delay node to do the timing rather than how I did it.

Good news, bad news.... (Sorry.)

LED 6 is still not playing the game.

What's happening - just to be clear:
Normally - other LED's - do this:

Get turned on.
Stay on. (as the bar goes past them) then go off.

LED 6 isn't doing that.

Dunno if I can make a video of the LED's....

I'll try to (poorly) explain it graphically (ascii art)

(start - all LEDs off)

. . . . . . . .
. . . . . . . R
. . . . . . R R
. . . . . R R R
. . . . R R R .
. . . R R R . .
. . R R R . . .
. R R R . . . .
R . R . . . . .

That isn't a perfect depiction.
Alas there are subtle things happening between the actual things shown.

But I hope it shows you what I am seeing and maybe you can see where the code is failing.

I'll try, but do you know if I can post videos to the forum?

Here's a few screenshots of the problem.

In time order.

So top is first, going down the screen is going forward in time.

EDITED

Better version with more information seen.

I hope that helps you see what is happening better.

Stepping back and looking at this, I am confused about something else new now.

The msg.delay value.

Originally I had to increase the message_delay value to control the speed at which the function node spat out the messages - I think. (Never really understood it. It just kinda worked.)

This new way all the messages are sent through the delay node set to rate limit and that value is determined by msg.delay to control the speed at which they are sent.

So why does that need to increase?

It gets message 1 (ok, no delay) then gets message 2 with a delay. I'm taking it that the delay is applied to the time BEFORE sending the next message.
Ok, so ITMT, Message 3 arrives with a msg.delay value.
That is queued and sent it's value of msg.delay time after it has sent message 2.
So that would be the same value as the time between message 1 and message 2 - yes?

Sorry, I seem to be missing something there too.

Because all messages are sent in sequence immediately (but in order). If you set all delay values the same, they will all be delayed the same amount of time and all release at the same time. That is how the delay node is designed to work - UNLESS - you set the mode to "rate limit" where it will space them out.


As for your issue, I really dont know what your issue is.

Perhaps if you list out EXACTLY what you expect to be sent, i can better help.

e.g.

Seq Time (ms) Payload Expected LED Status
1 0 rgb,0,196,10,0 0 0 0 0 0 0 0 R
2 400 rgb,1,196,10,0 0 0 0 0 0 0 R R
3 800 rgb,1,196,10,0 0 0 0 0 0 R R 0
... ... ... ...
16 6400 rgb,7,0,0,0 0 0 0 0 0 0 0 0

I recommend using the ScreenToGif app. Captures what's on screen into a video that you can save out as an animated gif which you can post here.

2 Likes

Thanks.
I'll look for it and ....

Is it for Ubuntu?
(Sorry, just checking.)

SORRY ALL!

I am stupid - you don't have to agree right away. :wink:

Alas I had written some other code and not really checked it 100%.
It was running when it shouldn't have been.

Which was causing the problem with LED 6 being turned off.

Found the problem, fixed the code, now all working fine.

Windows.

There are probably equivalents for Linux.

:frowning:
I'm Ubuntu.

1 Like

I am just curious. Do you need just an animation? Why are you not saving all your animation steps in an array like:
animation array = [
{led1:'off',led2:'off',led3:'off',led4:'off',led5:'off',led6:'off'.led7:'off'},
{led1:'off',led2:'off',led3:'off',led4:'off',led5:'off',led6:'off'.led7:'on},
{led1:'off',led2:'off',led3:'off',led4:'off',led5:'off',led6:'on'.led7:'on},
{led1:'off',led2:'off',led3:'off',led4:'off',led5:'on',led6:'on'.led7:'on'},
{led1:'off',led2:'off',led3:'off',led4:'on',led5:'on',led6:'on'.led7:'off'},
... etc.
]

and just cycle through the array with a set timeout When a message appears. Or is there more logic you need?

And after drawing one part of the array you just set the timeout to draw the next => until it is done.

Did I miss something in your logic?

1 Like

Thanks.

Alas I am stuck between keeping it simple and trying to explain the complicated story on how the LED's are controlled.

Their conditions are controlled by MANY factors. :frowning: (Well, that's life for them)

I really stuffed up with some other code controlling LED 6 which was causing it to go blank as it was.