Beginner questions on IF expressions and wildcard usage in function node

Unfortunately did not work either. Still an error symbol.

But payload is already an object.
Incoming message is
{"topic":"raw_data/tasmota_D2EE70/SENSOR","payload":{"Time":"2023-09-07T22:51:07","ATC0d5c0c":{"mac":"a4c1380d5c0c","Temperature":21.5,"Humidity":63.2,"DewPoint":14.2,"Btn":0,"Battery":40,"RSSI":-71},"ATC4e211d":{"mac":"a4c1384e211d","Temperature":33.2,"Humidity":33.6,"DewPoint":15,"Btn":1,"Battery":82,"RSSI":-67},"TempUnit":"C"},"qos":0,"retain":false,"_msgid":"33ec289d8866a035"}

and the outgoing is (when I remove the broken line of code):
{"topic":"tele/xiaomi/ATC4e211d","payload":{"mac":"a4c1384e211d","Temperature":33.2,"Humidity":33.6,"DewPoint":15,"Btn":1,"Battery":82,"RSSI":-67},"_msgid":"33ec289d8866a035"}

So the incoming message has an object payload and the outgoing still does.
All the node does is separate the incoming one into the individual devices. The payload remains an object.
That's what is confusing me.

So all I want to do is add another property to that existing payload object under the respective ATC*.
So instead of this:
{"topic":"tele/xiaomi/ATC4e211d","payload":{"mac":"a4c1384e211d","Temperature":33.2,"Humidity":33.6,"DewPoint":15,"Btn":1,"Battery":82,"RSSI":-67},"_msgid":"33ec289d8866a035"}
I want to output this:
{"topic":"tele/xiaomi/ATC4e211d","payload":{"mac":"a4c1384e211d","Temperature":33.2,"Humidity":33.6,"DewPoint":15,"Btn":1,"Battery":82,"RSSI":-67},"_msgid":"33ec289d8866a035","Time":"2023-09-07T22:51:07"}

Yes you are right, sorry. In that case you will have to put some code in to add that property before sending it.

matchingKeys.forEach((element) => {
    let newMsg = {
        topic: "tele/xiaomi/" + element,
        payload: msg.payload[element]
    }
    newMsg.time = msg.payload.time
    node.send(newMsg)
})
1 Like

Interesting. Thank you!

But I am surprised that I have to put it in before sending it. Because I was changing the message while sending it before. So why is it allowed to change e.g. the topic while sending it but adding a property to an object while sending it is not allowed?

Okay, minor issue, I think:
I am trying to work with breaks in labeled blocks.
The reason is simply that I have a bunch of If ElseIf ElseIF ElseIf Else and each If/ElseIf would have the same statements. Since I do not want to repeat them over and over, I was thinking of something like breaks in labeled blocks.

Commandblock: {
    Else { 
    IfBlock: {
        If blah Then Break IfBlock
        Else If Blah Then Break IfBlock
        Else Break Commandblock
   }}}

But it seems that a block always has to start with a declaration or statement. Is that correct? So am I forced to declare or state something just to use the block break?
Or is there an alternative without that hurdle?

I thought I might be able to use continue but that is also "jump back up" rather than jump to something later in the code and also does not seem to work in front of if

And I cannot think of a switch case that would work.

I cannot find a goTo() function.

Is there nothing that allows me to jump somehwere else when using such a chain of if expressions?

Why?

What language does this pseudocode represent?

Can you explain why if () and else if () coding does not suit?

A mixture of JS and common language :wink:

Because each IF condition is followed by a statement.
Since the statement for each is the same, it would be lots of repeating code.

But using OR will not work because 1000>100 and 1000>50 and 1000>10, so multiple conditions will be met in a top down approach.

            if (
                (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold5') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta5'))
                || (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold4') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta4'))
                || (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold3') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta3'))
                || (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold2') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta2'))
                || (msg.payload.ENERGY['Power'][0] < flow.get('Threshold2') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta1'))
            ){
                statements
            }

with threshold5>4>3>2>1 and powerdelta5>4>3>2>1.

What I think would theoretically work and be "elegant" would be exiting the IF and jumping to the statement via goto, continue or break (if the latter worked with If).

        Power0Block: {
        else { 
            Power0IFBlock: {
                    if (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold5') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta5')) { break Power0IFBlock } 
                    if (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold4') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta4')) { break Power0IFBlock } 
                    if (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold3') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta3')) { break Power0IFBlock } 
                    if (msg.payload.ENERGY['Power'][0] >= flow.get('Threshold2') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta2')) { break Power0IFBlock } 
                    if (msg.payload.ENERGY['Power'][0] < flow.get('Threshold2') && Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0')) >= flow.get('PowerDelta1')) { break Power0IFBlock } 
                    else {break Power0Block}
                    }
                {
                statements
                }
            }
        }

The alternative being the use of range or between(), but if I can I would prefer to work with just one limit per condition, i.e.
value > blah1 instead of between(value, blah1, blah2)

Could you explain what it is you are doing with the powerdelta logic ?

What is the ultimate purpose ?

Input value is power consumption or short "Power". That value is measured in Watts.

If the value is > 1000 W I only want to know if it changes by more than 50 W. If the value is 1000<value<100 I want to know if it changes by 10 W. And so on.
Threshold in this case is the 1000 or 100 and powerdelta is the 50 or 10.

So the larger the value of Power the larger the value of PowerDelta, i.e. the less interested I am in small changes.

I think that if you have a chain of OR operators, evaluation will stop as soon as one is found which evaluates true so "multiple conditions will be met" is not a problem.
That's not to say that this is the best way to code this program logic.

I consider that including [5 copies of] all of these terms in an expression is taking code obfuscation a tad too far.
msg.payload.ENERGY['Power'][0]
flow.get('ThresholdN')
Math.abs(msg.payload.ENERGY['Power'][0] - flow.get(msg.topic + '_Power_0'))
flow.get('PowerDeltaN')

Imagine if a few months down the line you noticed that in some circumstances it does not work properly. Would you quickly spot that you have flow.get('Threshold2') on two lines and no flow.get['Threshold1')? Would you remember if this was by design, or could it be the bug?
I presume it is by design.

If you used some shorter aliases it would be a bit more readable - something like this?

const Pwr = msg.payload.ENERGY['Power'][0]
const LastPwr = flow.get(msg.topic + '_Power_0') ?? 0

const T1 = flow.get('Threshold1') ?? 0
const T2 = flow.get('Threshold2') ?? 0
const T3 = flow.get('Threshold3') ?? 0
const T4 = flow.get('Threshold4') ?? 0
const T5 = flow.get('Threshold5') ?? 0

const D1 = flow.get('PowerDelta1') ?? 0
const D2 = flow.get('PowerDelta2') ?? 0
const D3 = flow.get('PowerDelta3') ?? 0
const D4 = flow.get('PowerDelta4') ?? 0
const D5 = flow.get('PowerDelta5') ?? 0

const Diff = Math.abs(Pwr - LastPwr)

if (
     ((Pwr >= T5) && (Diff >= D5))
  || ((Pwr >= T4) && (Diff >= D4))
  || ((Pwr >= T3) && (Diff >= D3))
  || ((Pwr >= T2) && (Diff >= D2))
  || ((Pwr <  T2) && (Diff >= D1))
 )

I get that but what are you doing with the information, i.e. why does it matter how big the change is ?

Or maybe use a % change so it automatically varies ?

Good idea, but unfortunately no. I used to use a combination of relative and absolute changes, but there is not one value that fits all. So for my switch to Node-RED I decided to stop using both and only use absolute changes.

Ah, the question of all questions :slight_smile:
The short answer is: database size increase due to redundant information.

The longer answer:
I have hundreds of entities (see info on device, entity etc.) which are all being tracked and their data recorded. Home Assistant is very limited in letting me restrict what is being recorded. It will not record values that are identical to the previous one and I can select which entities to record.
But, identical means identical. So a power sensor that varies by x.1 or x.x1 will trigger a database write. And sensors and consumers do not consume that constantly. Same for room temperatures and humidity.

So I have three options:

  • Increase the interval of publish on the devices. This has the disadvantage that I will not register spikes or sudden changes (e.g. a microwave consumes all power in just 2 min which will rarely be seen properly if the interval is 5 min)

  • Decrease the resolution of the devices (e.g. only send integer values for Temperature) but that does not work for all and is again not precise when I want it to be.

  • Filter the source data --> in come Node-RED :raised_hands:

True, but you are forgetting the PowerDelta criteria. Key is that for large values the PowerDelta is the crucial condition.
Example: 1500 W consumer

If (1500 > 1000 && 1500-1490 > 50) // false due to powerdelta
ElseIF (1500 > 100 && 1500-1490 >20 ) // false due to powerdelta
ElseIf (1500 > 10 && 1500-1490 > 5) // TRUE

As you can see, that is clearly not the intended outcome :slight_smile:
[EDIT: Forgot to add the elseif to the breaking condition in my previous post but would need it to prevent this exact behavior]

Yes, probably, but
a) I do not like to declare a bunch of constants just because Node-RED does not follow the general approach of being able to use global (here flow context) variables without a long command)
b) it forces me to look up the values twice (once in the declaration of the constant inside the flow... to see if that is correct... and a second time to get the actual value from the flow context initialization node).

I will use the between(value, x, y) if I need to, although I dislike it a lot (adds even more long flow.get() and more possibilites for errors) if no one can think of a working break or continuealternative.

Or does anybody know a short statement that consumes almost no computing power (and ideally no write cycles to memory) which I can abuse to make Node-RED accept my break approach?
I tried a simple let i=0 but it complained that I was not using it later :smiley:

EDIT: The more I think about it, the more complicated it gets to make the code short but fully working. So I may need to bite one of the many bullets in the end :frowning:

Good example.
What if you wrote it the other way round?

if      (1500 <= 10)                       false
else if (1500 <= 100  && 1500 - 1490 > 5)  false
else if (1500 <= 1000 && 1500 - 1490 > 20) false
else if (                1500 - 1490 > 50) false 

Until I have had coffee I can't tell if that works for all values.

1 Like

Bottom up....hmmmm... I know I scrapped that in my tasmota approach for some reason, but it might have been tasmota feature or syntax related...

Hmmmmm.....I will think about it during breakfast :+1: :slight_smile:

I think most people here do not use HA, and while happy to help with your specific programming questions, we don't have knowledge of HA. However I'm also sure that most people posting in this thread will also be recording many data points, so its not a unique task. Typically people tend to use something like InfluxDB and grafana to store and view metrics like this.

I believe you are using Tasmota for energy monitoring, using the active power property ?

Each device also has an Energy Total property. Taking you microwave example above, most of the time it will consume almost nothing, but there may be slight variations of reported active power.

If however you use the Energy Total figure you could easily subtract the last reading to find out how much was consumed in a given period. Nothing will be missed and you can set the interval to suit your needs.

This is how I store consumption at 5min intervals per device. to save disk space after 30 days this data is aggregated to 1hour intervals.

Yes, it is similar in HA where I switched to the built in SQLite because InfluxDB was not really properly implemented in the dashboard/lovelace cards.

But basically it is the same approach.
Unfortunately the built in cards don't really work like Grafana. They are more user friendly but subtracting data points is usually not supported without templating or worse.

That's why I am directly using ENERGY Power as my data points rather than creating them as a mean from ENERGY Total.

I would like to ensure that I only ever change those properties of a message that I want to actively change. Everything else should just be passed through.

We discussed earlier that in a forEach() I would be overwriting the original msg.topic if I use the = instead of : approach.

Would this also apply here?

const matchingKeys = Object.keys(msg.payload).filter((element) => element.startsWith("ATC"))
matchingKeys.forEach((element) => {
    let newMsg = {
        topic: "tele/xiaomi/" + element,
        payload: msg.payload[element]
    }
    newMsg.payload.Time = msg.payload.Time
    node.send(newMsg)
})

Or would, because I am defining a newMsg, the original topic not be changed and the forEach would continue to function if I replaced the : expressions with = expressions (for payload and topic)?

Or, if the problem remains, could I somehow define something like msg: msg to preserve everything except what I replace afterwards?

    let newMsg = {
        msg: msg
        topic: "tele/xiaomi/" + element,
        payload: msg.payload[element]
    }

I think that is where we started, using cloneMsg, but you said you didn't want to do that because it would consume more resources.

1 Like

You are right, it was the cloning!

I will implement that now that I needed the let newMsg anyway.

Thank you again :slight_smile:

So, hopefully the last challenge of this project: synchronize something asynchronous.

I need to write a function that waits for incoming messages.
I know the join node has this functionality in a basic manner. Is there a template somewhere how the wait function works?

I need to wait until each of the nodes wired into the input has delivered a message with identical msg.topic and msg.Time.

Once I have received one message from each source node, I will perform the actual task.

Each of the three source nodes will always output a message with the same msg.topic and msg.Time. But since Node-RED is asynchronous, I need to check for reception of those three.