Beginner questions on IF expressions and wildcard usage in function node

What exactly is the ?? ? Kind of an if expression here? Because you are checking for 0 and then initializing. But that should never be true because the value is never 0.

I was trying

if (flow.get(msg.topic + '_Temperature') == undefined ) {
    flow.set(msg.topic + '_Temperature', msg.Temperature)
    return msg
}

when I noticed that the docs do not list the Else If case.

So is it
Else If { ...} or Else { If { }}?

It is like || but safe for where you might be using a boolean value. It is true if the value is undefined or null but not if it is false.

1 Like

So, if undefined it will be set to whatever is behind the ?? ?

In that case I could replace my if expression with that like so?
if (Math.abs(msg.Temperature - flow.get(msg.topic + '_Temperature')) >= flow.get('XiaomiTemperatureDelta1') ?? msg.Temperature) {

But still curious if I need if inside else or if Node-RED supports ElseIf. Else If { seems to work.

EDIT:
No, I think I cannot replace my if. The ??is probably just an alternative and does not initialize the context to that value, correct?

If the expression context.get("something") evaluates to false
(the context variable does not exist, or it does exist but has the value false)
Then use the value 0.

Untitled 1
I think that using your function "Flow variables" to set values before using them elsewhere in the same flow is likely to be problematic.

Not quite, || includes false, ?? does not.

The nullish coalescing (??) operator is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.

1 Like

I think I mentioned it earlier that I disliked my initial idea of attaching it to the mqtt in because it constantly gets re-initialized.

I switched that to an inject node with "run once after 0.1 s". So they should always be available.

But on that topic, kind of, is there a way to delete all flow contexts in one go? The sidebar is quite tideous for this task :smiley:

There are 2 ways.

  1. Store all your context in a single object - then just delete the 1 object.
  2. Use a function node: How to clear context? - #2 by Steve-Mcl
1 Like

Perfect!!!

[{"id":"e64fea4887d59053","type":"function","z":"c36d205f4de85948","name":"Initialize flow variables","func":"// Thresholds tasmota ENERGY Power\nflow.set('Threshold1', 10)\nflow.set('Threshold2', 25)\nflow.set('Threshold3', 50)\nflow.set('Threshold4', 100)\nflow.set('Threshold5', 1000)\n\n// PowerDeltas tasmota ENERGY Power\nflow.set('PowerDelta1', 3)\nflow.set('PowerDelta2', 5)\nflow.set('PowerDelta3', 10)\nflow.set('PowerDelta4', 15)\nflow.set('PowerDelta5', 50)\n\n// TemperatureDeltas Xiaomi\nflow.set('XiaomiTemperatureDelta1', 0.3)\n\n// HumidityDeltas Xiaomi\nflow.set('XiaomiHumidityDelta1', 1.5)","outputs":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":50,"wires":[]},{"id":"65b6414216f9efb6","type":"inject","z":"c36d205f4de85948","name":"Initialize flow variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":50,"wires":[["e64fea4887d59053"]]},{"id":"d35b624de7c1676a","type":"function","z":"c36d205f4de85948","name":"Delete all flow variables","func":"const keys = flow.keys()\nfor (let index = 0; index < keys.length; index++) {\n    const key = keys[index];\n    flow.set(key, undefined)\n}\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":100,"wires":[["e64fea4887d59053"]]},{"id":"b4bf3edb1df92d53","type":"inject","z":"c36d205f4de85948","name":"Delete all flow variables","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":100,"wires":[["d35b624de7c1676a"]]}]

Two more questions:

  • Is there a performance difference between context contexts and flow contexts?
    In theory my "previous" values do not need to be available to other paths in my flow. They could be limited to that one node were they are called and initialized.

  • When I want to subtract two dates to create a time delta, I tried doing
    msg.TimeDelta = (msg.Time.getTime() - msg.topic + '_Time'.getTime()) / 1000;
    but that does not work because I am basically doing a maths operation while joining to strings to a new string.
    I tried this with brackets
    msg.TimeDelta = (msg.Time.getTime() - (msg.topic + '_Time').getTime()) / 1000;
    but that also does not work. So I figure that there is a trick do achiving this.

For context (not the variable, but the english word :smiley: ):
flow.set(msg.topic + '_Time', msg.Time)

No. They are the same. The only difference is where you can access them.

Don't forget that there is a vast world of knowledge out there! :wink:

Yeah, but the problem is not the how to do it but the naming due to my variable names being composed of different parts (see my flow.set I posted).
msg.Time.getTime() - msg.topic + '_Time'.getTime()

I think node-red reads this as

msg.Time minus msg.Topic plus _Time

It should read

msg.Time minus msg.Topic_Time

where topic varies depending on the input data.

EDIT:
And it seems msg.Time.getTime() is also not supported (TypeError: msg.Time.getTime is not a function). Apparently not a function. I would assume again because of the combination. So here it is probably the "." between msg and Time.

No, not Node-RED, JavaScript reads it exactly how you wrote it.

If you want to get a property using a variable name, use JavaScripts square bracket notation

e.g.

const propName = msg.topic + '_Time'
const propVal = msg[propName]



That is likely because msg.Time is a string or number (i.e. not a Date object). Use the debug node to figure that out or call node.warn(['typeof msg.Time', typeof msg.Time) in your function



I know you keep saying you will post your code/flows when done, but I gotta be honest - you look like you are making a HUGE headache for yourself. Your means of encoding topic names, non camelCase variable names, (over) use of context, all function nodes etc etc etc - this is not gonna be fun to debug when you get bugs - and you will.

No, I don't think it is. Perhaps this will help:

((new Date('2023-09-06 11:30:00')) - (new Date('2023-09-06 10:30:00')))/60000

Which returns 60 - the number of minutes between the 2 times - because JavaScript dates are in milliseconds offset from an origin. You only need getTime if you aren't interested in the dates. And if you used getTime and the two dates cross the midnight boundary, you'd likely get the wrong answer anyway.

Not sure what format your msg data is in sorry, I might have missed that bit. And not really sure what you are trying to do with the msg.topic. Don't mix strings and numbers unless you really have to as it can be hard to read and debug as you are discovering.

getTime is a function of a JavaScript date object. You need a date, not a string.

Here is an example:

{"topic":"tele/xiaomi/ATC801437","payload":{"mac":"a4c138801437","Temperature":25.6,"Humidity":73.3,"DewPoint":20.5,"Btn":0,"Battery":50,"RSSI":-95},"Time":"2023-09-06T21:40:52","_msgid":"48b050568f079c21","Temperature":25.6,"PowerDelta":0.40000000000000213,"TimeDelta":null,"CheckTime":"2023-09-06T21:40:52"}

So I would assume that msg.Time is a string.

Now I see that the new Date() is needed.
So it should work with
msg.TimeDelta = ((new Date(msg.Time).getTime()) - (new Date(msg.topic + '_Time').getTime())) / 1000;
but I am getting TimeDelta: NaN

My idea was to format the strings and then get the time of those strings in one expression.

When I paste it into my function node I get a warning "expected one argument but got two" for the typeof.

Where did I miss using CamelCase? My contexts are all CamelCase.
I am setting a few extra message properties right now for debugging purposes. They will be removed later. I am using them now to check the triggering condition and to see how long between two triggerings (in s) to check efficiency of my thresholds.
I am using the topic names to be able to trace back the behavior to a device for debugging. Also not necessary but good for debugging right now.

I am a complete newbie, but right now I find my overuse quite useful for debugging because every step in processing is rather small and can only create a limited number of issues.

Right now the processing works perfectly. I am just struggling to get the bonus info of time delta working -.-

I missed the closing square bracket

node.warn(['typeof msg.Time', typeof msg.Time])

msg.Time is not camelCase
msg.TimeDelta is not camelCase
msg.Temperature is not camelCase

Those are not contexts. Those are msg properties. I presume you are talking about the "msg." part? Because "TimeDelta" and "PowerDelta" are CamelCase.

No, not. camelCase start with lowerCaseLetters and word joins are formed by a capital letter likeThis andLikeThis

Anyhow, it is a stylistic thing mainly, but using a common convention avoids issues with wondering - does it start with a captial or lower case? camelCase for JS is always lowercase first letter.


PS: "TimeDelta" and "PowerDelta" are PascalCase

No one said they were.

camelCase applies to property names and function names. in msg.xxx, msg is the object, xxx is the property. so Xxx is not camelCase.

Ah well, that is not the definition of CamelCase. The definition of CamelCase does not specify the first letter. It only specifies that words are joing by using uppercase for the next word.

But if that is the convention or the generally used approach for JS, then that is fine of course.

I like using uppercase for the first letter because almost all systems use lowercase for the first letter by default. So as soon as a string starts with uppercase, you know it is my string.

msg.payload is system generated. msg.time is system generated. msg.Time is user generated. msg.Payload would be user generated.

We are splitting hairs here and I mean no offense but

The more specific terms Pascal case and upper camel case refer to a joined phrase where the first letter of each word is capitalized, including the initial letter of the first word. Similarly, lower camel case (also known as dromedary case ) requires an initial lowercase letter.

So I am using CamelCase, but not lower CamelCase :wink: