Stopping unwanted messages

Hi all

I have two UPS here, both connected to Home Assistant and Node Red, for some unknown reason they both throw a “UPS online, low battery, battery needs replacing” rapidly followed by a normal-state “UPS Online” message.

Both units are new so batteries are good and both units exhibit the same message pattern.

I don’t want these message pairs but do want to be notified of failure/offline modes and eventual online messages.

I tried setting a state time in the events:state node but while it filters out the first message fine, the online message naturally persists for a long while until the next occurrence of the pair.

Is there a way to filter out both messages, maybe based on them always appearing together within 90 seconds?

We need to know the exact model of the ups and how you are connected before we can even begin to guess.

‘I have these ups doing something unexpected’ is current understanding… could literally dlbe doing anything.

Going to need a LOT more to understand your issue so? What do you have

Of course, my bad…

they are both APC “Back-UPS BX750MI” units connected via USB to my HA server running network ups tools (NUT)

Hope that helps a little.

From the HA integration, you can see it flips from one state and back pretty quick…

In automation there is a changes to for (time) concept

Don’t know why yours is flipping like that but you can absolutely make - and it stays that way for a few seconds before I trigger. Part of yihr trigger. Just change the automation to include the timeout

1 Like

Thats what i did but because the second message persists it is validated and times out then passes through. The trouble is that in this instance it is unwanted as it follows directly after the first message. In normal use there would be a much bigger time interval between the messages.

I’m not sure there is any way to make it ignore this particular pattern but I’ll keep looking :slight_smile:

It sort of needs a bit of logic that says “if message A is followed rapidly by message B then ignore both messages”

Update:

It can be done, in the end I threw the problem at ChatGPT and it gave me this code which works perfectly, I’ll figure out how it works but at least it works :slight_smile:

//This function checks the payload for the two messages below, they get sent
//in error by the UPS software so this blocks them if they arrive within 70 seconds
//they usually arrive at 1 minute apart. Other messages pass straight through.

// Define payloads to trap
const PAYLOAD_A = "Online, Low Battery, Battery Needs Replacement";
const PAYLOAD_B = "Online";

//const THRESHOLD = 2 * 60 * 1000; // 2 minutes
const THRESHOLD = 70 * 1000; // 70 seconds

let stored = flow.get("storedA") || null;
let timer = flow.get("timer") || null;

// Helper to clear timer safely
function clearStoredTimer() {
    if (timer) {
        clearTimeout(timer);
        flow.set("timer", null);
    }
}

// === CASE 1: Payload A ===
if (msg.payload === PAYLOAD_A) {
    // Reset any existing state
    clearStoredTimer();

    // Store A
    stored = {
        payload: msg.payload,
        time: Date.now()
    };
    flow.set("storedA", stored);

    // Start timeout: if B never comes → send A
    let t = setTimeout(() => {
        let s = flow.get("storedA");
        if (s) {
            node.send({ payload: s.payload });
            flow.set("storedA", null);
        }
    }, THRESHOLD);

    flow.set("timer", t);

    return null;
}

// === CASE 2: Payload B ===
if (msg.payload === PAYLOAD_B) {
    if (!stored) {
        return msg; // No A → pass B through
    }

    let diff = Date.now() - stored.time;

    // Clear state
    clearStoredTimer();
    flow.set("storedA", null);

    if (diff < THRESHOLD) {
        // Drop both
        return null;
    } else {
        // Send A then B
        return [
            { payload: stored.payload },
            { payload: msg.payload }
        ];
    }
}

// === CASE 3: Anything else ===
return msg;

I have no idea what code you’re talking about… All you need to do is throttle the trigger. If it doesn’t stay in state for x time… You do that by adding for (time) to the trigger.

Ai will give you an answer a long drawn out didn’t need to code anything answer…

Glad it works but automations are built to handle the case. This is what I meant as wait for it to see if it sticks for.a few seconds before triggering…

Ok, cool.

I don’t use automations; I do it all in NR. I tried throttling the state-change event but it only suppressed the first message - these two messages always appear in pairs and always about a minute apart, always false outputs.

The code will skip these two if they appear in the false sequence but pass all messages if not.

Sometimes complicated can be easier :wink:

This is an important fact. Next time add it in your question.

2 Likes

In any computer code, complicated is nearly always a sign of underlying problems. The KISS principle is still very much alive, and something that apparently AI has yet to learn.

Node-RED is a powerful language, and was built on the principles of “visual programming” and “zero-coding”. The Function node is there, intended as a node of last resort. There are plenty of good nodes, and the basic node-red set can perform almost everything you want.

Not using a Function node

  • makes flows simpler and easier to understand, as you can see what is going on from the flow
  • removes the need to write code (or get AI to write it for you)
  • is more suitable for support from the community. Writing, what is basically incomprehensible JS, makes it harder for anyone to understand what you are doing

AI is, at least for the moment, very verbose and over the top. As well as frequently being wrong. Your bit of JavaScript is a good example of how to crack a walnut with a sledgehammer. It may well work, but it may not work well. And, if you don’t understand what it is doing, then you will be completely unaware of any side-effects that the code may generate.

For example, the code includes an asynchronous call to a timer, and uses node.send call to issue messages outside of the standard send-on-exit. I do not see any node.done in there, so the Node-RED execution engine will never know that the node has actually finished, or the asynchronous function is sending a message after the node has already finished exiting with a return null.

Documentation on Node-RED function nodes sending messages asynchronously.

Those who use Node-RED just to string together a long list of Function nodes with complex JS code have, in my opinion, completely missed the point.

1 Like

I fully agree on your points and on the KISS principle, my whole NR/HA setup uses only a handful of function nodes as I really dislike them but sometimes it’s needed I think.

I only reached out to AI after several hours of messing with various combinations of standard visual nodes - I just could not get the result I wanted in any variation. I’m certainly not saying it could not be done just that I could not hit the right combination.

If anyone has time to kill feel free to throw something in here - I’m very open to educational help and will likely also return to messing with various nodes at some point.

:slight_smile:

It takes three node.

[{"id":"0877940a27aa1ebb","type":"inject","z":"dc25556591dc08be","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Message A","payloadType":"str","x":220,"y":4100,"wires":[["12618443aaaeab9b"]]},{"id":"0f4779b8f2cfadc7","type":"inject","z":"dc25556591dc08be","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Message B","payloadType":"str","x":220,"y":4160,"wires":[["12618443aaaeab9b"]]},{"id":"9d3906fa05a7c79f","type":"join","z":"dc25556591dc08be","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","useparts":false,"accumulate":false,"timeout":"10","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":430,"y":4120,"wires":[["b13d31cf26f2bac4"]]},{"id":"b13d31cf26f2bac4","type":"switch","z":"dc25556591dc08be","name":"","property":"payload = [\"Message A\", \"Message B\"]","propertyType":"jsonata","rules":[{"t":"false"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":570,"y":4120,"wires":[["202a1d535d1acfb0"],[]]},{"id":"202a1d535d1acfb0","type":"split","z":"dc25556591dc08be","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":710,"y":4120,"wires":[["80d019e033efcf97"]]},{"id":"80d019e033efcf97","type":"debug","z":"dc25556591dc08be","name":"debug 149","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":870,"y":4120,"wires":[]},{"id":"12618443aaaeab9b","type":"junction","z":"dc25556591dc08be","x":340,"y":4120,"wires":[["9d3906fa05a7c79f"]]}]

The join is set to trigger manually, based on either 2 messages arriving, or a timeout of 10 seconds (you can change that to 90 as you wish), and to combine the message(s) payload into an array (since we don’t have the msg.topic to form an object).

The switch forms a JSONata test for is msg.payload equal to an array of message A, message B (the sequence you are testing for). This requires a JSONata test rather than direct comparision as I don’t find that works for some reason.

The switch will then pass anything except “message A” followed by “message B” within 90 seconds.

The Split simply expands the array of one / two messages back into the original message.

I am sure that there are many many other ways to acheive this…

1 Like

Thanks for that, fascinating.

I don’t like being beaten by a task so I put some more time into it and have come up with this slightly more complex flow and it doesn’t have the constant delay of your flow caused by the join…

I think both are pretty cool options and better than the function node.

[{"id":"e73a5faf8d47be36","type":"tab","label":"A B Logic Tester","disabled":false,"info":""},{"id":"702bdf1926574b33","type":"junction","z":"e73a5faf8d47be36","x":1400,"y":300,"wires":[["643c4f181756bf12"]]},{"id":"e3cc91830e4f88c7","type":"junction","z":"e73a5faf8d47be36","x":640,"y":300,"wires":[["702bdf1926574b33"]]},{"id":"959956d132c2f708","type":"junction","z":"e73a5faf8d47be36","x":1400,"y":260,"wires":[["643c4f181756bf12"]]},{"id":"3880adde8f750b78","type":"switch","z":"e73a5faf8d47be36","name":"Classify A/B/Other","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"Online, Low Battery, Battery Needs Replacement","vt":"str"},{"t":"eq","v":"Online","vt":"str"},{"t":"else"}],"repair":false,"outputs":3,"x":470,"y":240,"wires":[["0b06af52da236f0e"],["2f59d80b0c1554e3"],["e3cc91830e4f88c7"]]},{"id":"0b06af52da236f0e","type":"change","z":"e73a5faf8d47be36","name":"Mark [A] active","rules":[{"t":"set","p":"A_active","pt":"flow","to":"true","tot":"bool"}],"x":720,"y":160,"wires":[["395e0e471fd1ce10"]]},{"id":"395e0e471fd1ce10","type":"trigger","z":"e73a5faf8d47be36","name":"Delay 20s","op1":"","op2":"payload","op1type":"nul","op2type":"pay","duration":"20","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":900,"y":160,"wires":[["4ad051902bc8c3b7"]]},{"id":"4ad051902bc8c3b7","type":"switch","z":"e73a5faf8d47be36","name":"[A] still active?","property":"A_active","propertyType":"flow","rules":[{"t":"true"},{"t":"false"}],"repair":false,"outputs":2,"x":1080,"y":160,"wires":[["ae68cb4fa3f94f52"],[]]},{"id":"ae68cb4fa3f94f52","type":"change","z":"e73a5faf8d47be36","name":"Reset [A] & send msg","rules":[{"t":"set","p":"A_active","pt":"flow","to":"false","tot":"bool"}],"x":1300,"y":160,"wires":[["643c4f181756bf12"]]},{"id":"2f59d80b0c1554e3","type":"switch","z":"e73a5faf8d47be36","name":"Is [A] active?","property":"A_active","propertyType":"flow","rules":[{"t":"true"},{"t":"false"}],"repair":false,"outputs":2,"x":930,"y":240,"wires":[["cf62a027c8b0ca50"],["6f0888652ccbe412"]]},{"id":"cf62a027c8b0ca50","type":"change","z":"e73a5faf8d47be36","name":"Cancel [A] & drop [B]","rules":[{"t":"set","p":"A_active","pt":"flow","to":"false","tot":"bool"},{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"x":1160,"y":220,"wires":[[]]},{"id":"6f0888652ccbe412","type":"change","z":"e73a5faf8d47be36","name":"Pass [B]","rules":[{"t":"set","p":"A_active","pt":"flow","to":"false","tot":"bool"}],"x":1120,"y":260,"wires":[["959956d132c2f708"]]},{"id":"643c4f181756bf12","type":"debug","z":"e73a5faf8d47be36","name":"FINAL OUTPUT","active":true,"tosidebar":true,"complete":"payload","x":1560,"y":220,"wires":[]},{"id":"f678f47f09114e07","type":"inject","z":"e73a5faf8d47be36","name":"A - Battery msg","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Online, Low Battery, Battery Needs Replacement","payloadType":"str","x":180,"y":140,"wires":[["3880adde8f750b78"]]},{"id":"01ca0ce230419d99","type":"inject","z":"e73a5faf8d47be36","name":"B - Online msg","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Online","payloadType":"str","x":180,"y":240,"wires":[["3880adde8f750b78"]]},{"id":"584b5a0ff7829bfe","type":"inject","z":"e73a5faf8d47be36","name":"Any other msg","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"some other msg","payloadType":"str","x":180,"y":340,"wires":[["3880adde8f750b78"]]}]