Node-RED - How would I pass a random colour to light.turn_on?

Hello,

Very new to Node-RED, so trying to learn.

I’ve got a flow that turns a light on & off depending on a motion sensor state. That’s great.

What I would like to do is pass a random RGB value to light.turn_on so every time the light turns on, it’s a random colour.

I installed this node which generates a random RGB value as a payload:

How would I then integrate that value into the light.turn_on node? What I’ve tried so far is producing JSONataErrors:

Many thanks.

There are a couple of things here that are wrong. When passing a variable there are 2 methods. jsonata (Jexpression) and JSON {}.

With JSON you use double mustache with the path of the variable {{payload}}.

When using jsonata it is just the name of the path with no quotes or brackets, payload

You can find more information in the web socket guide under jsonata and mustache templates.

RGB color expects the values to be in an array [233, 45, 221] You are sending a string "(233, 45, 221)". I do not use that node, you can check to see if it lets specify the syntax of the output. If it doesn’t then it will need to be converted.

Yes indeed, as Mikefila says, the important thing with passing parameters to Action nodes in the Data field is to use either JSONata or mustache templates. and with the correct format. In general terms, using JSONata is to be preferred as templates do not always work when constructing JSON objects as we are doing here.

In Node-RED there is a node for (almost) everything, however adding lots of custom nodes can bring increased overhead in palette maintenance. JSONata is the preferred language to use in Node-RED, and there is helpfully a random function that can do the work for you.

The following JSONata code should generate the correct Data field JSON object required.

(
    $rp:=function(){$round($random()*255,0)};
    $randcolor:=[$rp(), $rp(), $rp()];
    {"rgb_color": $randcolor, "brightness": 100}
)

giving

{
  "rgb_color": [
    96,
    122,
    158
  ],
  "brightness": 100
}

where the colour values are randomly generated from 0 to 255 (ish).

The JSONata code can be placed directly into the Action node Data field, using J: expression option as you have it. Then there is no need for either the extra node, or for any issues with passing parameters into the node, since the JSONata code will be executed there.

JSONata is not easy to learn, being up there with Lisp and Forth for incomprehensibility, but being based on JavaScript most of the underlying expression syntax is very similar. The secret is to open a code block and to use statements as in ( statement; statement; statement ).

Easier I think than trying to reformat "(233, 45, 221)" given as a string into [233, 45, 221] as a JSON array. Although, if you do want to use the random-node, then something like the following should work:

(
    $randcolor:=($replace(payload, /[()\s]/, "")~>$split(",")).$number();
    {"rgb_color": $randcolor, "brightness": 100};
)

again JSONata code entered into the Data field with the J: expression option.

1 Like

I was trying to figure this out, how to generate 3 random numbers without 3 separate variables. I’m realizing now with your example, it’s calling the function 3 separate times. I completely missed that approach.

I came up with {$floor($random()*256) to generate the numbers since random should not output 1 and then $floor should round down?

I think every computer language (well, most) has a random function, which usually returns a random-ish number between 0 and 1 as an inclusive interval.

To get this to a random number between say 0 and 255, we usually multiply by 255. However, this will generate a number that randomly spans between 0 and 255 exactly. Turning this into an integer becomes a problem, since we can round or floor or ceiling. Floor will return anything between 0 and 0.9999999 as 0. Anything between 1.0 and 1.9999999 as 1. OK so far.

The problem is 255, since the original random number goes from 0 to 1 inclusive, we end up with 255 as a (slim) possibility, but this will only be 255 when the base random function returns 1, hence 0 is generated from 0 … <1, and 1 from 1 … <2 and so on, but 255 occurs only for the miniscule span of 255 exactly. In practical terms, 255 will never occur. Hence we probably should multiply by 256 and trap 256, if it ever occurs, and throw it away (since 256 would be an error in this case) or multiply by 255.9999999999999999999999 and floor. Computers are crap at floating point computation, so this is unlikely to generate the correct interval.

You are entirely correct that floor is a better option than round, since round moves 0-0.5 to 0, and 0.5-1.5 to 1, which means integer 0 is half-represented. At the top end though integer 255 results from 254.5-255, which means that 1…254 is correctly represented by a full span, but 0, and 255 only get half a span each. The balance comes from 0 being half a span and 255 being half a span rather than almost no span at all. In practical terms I was lazy and opted for round as I would get 0 half as much as I should but 255 more often that it would be otherwise.

I think that all random number generators should have an option to return in the interval 0-0.999999999999999999 and not 0 to 1. My other approach is to generate 0-1 and throw the 1 away, or fold it back into the interval… which requires another random number to randomly fold ‘1’ back into the interval 0 … <1. Tough.

I am getting more experienced with coding and, on a good day, I refactor as I go along. Writing JSONata functions is easier than it may appear. Again the trick is to open a code block, which permits multiple statement lines as well as practical use of variables. Functions are defined and allocated to a ‘variable’ and can be called as required.

JSONata functions are closures, and it is possible to pass a function as a parameter to a function, as well as to call functions recursively. At that point I have to write it all out long-hand, and refactor one step at a time.

Anyway, hopefully this gives the OP some options!

Edit:
I checked the JSONata docs, and apparently the random is 0 <= n < 1 so excludes 1 (which does make it not a true random number but does make using it easier). So yes you are correct - should be

(
    $rp:=function(){$floor($random()*256)};
    $randcolor:=[$rp(), $rp(), $rp()];
    {"rgb_color": $randcolor, "brightness": 100}
)

Edit 2:

Not using the Lodash functions myself, I had missed Kermit’s $randomNumber, which of course solves the problem very neatly and entirely [see below]

This function can only be used inside one of the HA WebSocket nodes, but with a bit of refactoring, the following code works as a single statement line.

{"rgb_color": [1..3].$randomNumber(0,255), "brightness": 100}
2 Likes

Hello. Oh thank you so much for these replies. I didn’t realise there was an expression editor on the action node so asking for help gave me that bit of new knowledge as well! I tried pasting your code, however it complained that $floor was expecting only one argument, so I changed it to this:

Seems to be working!

As a side note, the Home Assistant nodes include a $randomNumber function for JSONata. You could use it like this:

{
  "rgb_color": [
    $randomNumber(0,255),
    $randomNumber(0,255),
    $randomNumber(0,255)
  ]
}

https://zachowj.github.io/node-red-contrib-home-assistant-websocket/guide/jsonata/#exposed-lodash-functions

2 Likes