Detecting global/flow object change

Hi all folks!

I'm trying to detect a change of value in self created object.
This object contains presence info from the diferent domo devices in home (Fibaro FGMS-001) via zwave.

I set the PRES object global every 1s with the values of presence (0:no presence, 100:presence).

In other flow, with a function, I want to know if is there any difference between the last object value. So other object is set (flow) with name PRESAnt (initialized with all values to 0), and is SET again when the objects differ (PES!=PREAnt).

See attached the function code:

var PRES=global.get("PRES");
var PRESAnt=flow.get("PRESAnt");
var msg2;
msg2 = {payload: {payload:"Nothing changed"}}
var msg3;
msg3 = {payload: {payload:"Values Changed!"}}

if (PRES!=PRESAnt){
    PRESAnt=PRES;
    global.set("PRESAnt",PRESAnt);
    return msg3;
}
else{
    return msg2;
}

Here you can see the global/flow objects:

The problem:
msg3"Values Changed!" is never showed in degub messages, but PRESAnt is writted with a change.
Why this message is never shown?

Why I want to do that:
msg3, now with "Values Changed!", will have other string, with a MySQL query with INSERT in every object change.

Thanks in advance for your help. I'm crashing in this point.

Hi @Steve-Mcl posted a function to monitor context variables. you might want to read it, Lighting controller ideas - #19 by Steve-Mcl

3 Likes

This will be because NodeJS is a pass by reference language, so unless you explicitly make a deep copy of the PRESAnt object, you will be storing and updating the same original object in both areas of the context all the time.

1 Like

As @hardillb said you are comparing references so they will never be equal. You will have to compare properties.

If you are just looking for a change in PRES.presence you could use;

if (PRES.presence != PRESAnt.presence)

If you wish to compare many properties you will have to use an Object comparison function, so;

if (isOjectEqual(PRES, PRESAnt))

    /**
    * Description   Deep compare of two objects.
    * 
    * @param    {*} x       Any object that has Object as prototype i.e. Object, Array, Date, String, Map, Set, Number, etc
    * @param    {*} y       Any object that has Object as prototype i.e. Object, Array, Date, String, Map, Set, Number, etc
    * 
    * @returns  {boolean}   Whether the two objects are equivalent, that is, every property in x is equal to every property in y recursively. Primitives
    *                       must be strictly equal, that is "1" and 1, null and undefined and similar objects are considered different
    */

    function isObjectEqual(x, y) {
        let seen = [];

        return (function equals(x, y) {
            // If both x and y are null or undefined and exactly the same
            if (x === y) {
                return true;

            }

            // If they are not strictly equal, they both need to be Objects
            if (!(x instanceof Object) || !(y instanceof Object)) {
                return false;

            }

            // They must have the exact same prototype chain, the closest we can do is test the constructor.            if (x.constructor !== y.constructor) {
                return false;

            }

            for (const p in x) {
                // Inherited properties were tested using x.constructor === y.constructor
                if (x.hasOwnProperty(p)) {
                    // Allows comparing x[p] and y[p]  when set to undefined
                    if (!y.hasOwnProperty(p)) {
                        return false;

                    }

                    // If they have the same strict value or identity then they are equal
                    if (x[p] === y[p]) {
                        continue;

                    }

                    // Numbers, Strings, Functions, Booleans must be strictly equal
                    if (typeof(x[p]) !== "object") {
                        return false;

                    }

                    // Test cyclicality
                    if (seen.indexOf(x[p]) !== -1) {
                        throw new Error("Cannot compare some cyclical objects");

                    }

                    seen.push(x[p]);

                    // Objects and Arrays must be tested recursively
                    if (!equals(x[p], y[p])) {
                        return false;

                    }

                }

            }

            for (const p in y) {
                // allows x[p] to be set to undefined
                if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
                    return false;

                }

            }

            return true;

        })(x, y);

    } // End Function isObjectEqual()

This is a function I found on t'internet and it seems to work but I am sure there are others. If you are wanting to send a different message when any property in the two objects change you will have to compare each property, probably using Object.entries

PS If you want msg#.payload to hold the 'Changed' message you can also use

let msg2.payload = "Nothing changed"

1 Like

On continuation from the example by @Buckskin:
You could employ lodash.isMatch Link here.

Add the module in a function node, call it say LD, you can then do LD.isMatch(object, source) in your function node.

isMatch returns a boolean, based on Key and Value.
it takes away the heavy lifting, one them self will have to do.

1 Like

Or you could use RED.util.compareObjects, which the NR developers have been kind enough to make available.

2 Likes

And if you use the Monaco editor, you will get built in help via auto completion...

jTS5iprIZY

2 Likes

You could have of course, if you had thought to look :blush:

Does this function check recursively as it doesn't say?

Not terribly hard to test...

var o1 = {
    p1: "hello",
    a1: [1, 2, 3],
    deep1: {
        x: 1, y: 2, z: 3,
        s: "substring1",
        deep2: {
            deep3: "i am deep"
        }
    }
}
var o2 = {
    p1: "hello",
    a1: [1, 2, 4],
    deep1: {
        x: 1, y: 2, z: 3,
        s: "substring1",
        deep2: {
            deep3: "i am deep"
        }
    }
}

var o1_ref_copy = o1
var o2_clone = RED.util.cloneMessage(o2);
var o2_clone_mod = RED.util.cloneMessage(o2);
o2_clone_mod.deep1.deep2.deep3 += ": modified";


msg.payload = {
    "RED.util.compareObjects(o1,o2)": RED.util.compareObjects(o1,o2),
    "RED.util.compareObjects(o1,o1_ref_copy)": RED.util.compareObjects(o1, o1_ref_copy),
    "RED.util.compareObjects(o2,o2_clone)": RED.util.compareObjects(o2, o2_clone),
    "RED.util.compareObjects(o2,o2_clone_mod)": RED.util.compareObjects(o1, o2_clone_mod),
    o1, o2, o1_ref_copy, o2_clone, o2_clone_mod
}
return msg;

2 Likes

Cheers, proof that it does

Thank you all folks!!

Using RED.util.compareObjects work perfect.
As I said, I want to compare two objects, one is dinamically written depending of presence sensor readings, the other object initialized (ON START).
When objects are different, I want to make a MariaDB INSERT in a table. If it's different, the 2nd object is overwrited by the first object . Teorically, 2nd object is overwritted only when is a difference between objects.

With that function I'm trying to do that, but doesn't works fine. I have tested in other ways, but I don't understant why with this functions the objects are always the same (without diffrences).
Remember that the first object (PRES) it changes values in other flow, depending on values of presence domo sensors, and the second object, (for me / don't works in the way I expected) it's only writted when the objects don't match.

var PRES = global.get("PRES");
var PRES_A = global.get("PRES_A"); // object initiallized (ON START)

var objeq = RED.util.compareObjects(PRES, PRES_A);
var addtext="Equal Objects";
if (!objeq){
    global.set("PRES_A", PRES); //overwrite PRES_A with PRES values
    addtext ="Objects not equal. Global set PRES_A to match";
}

msg.payload = {
    "RED.util.compareObjects(o1,o2)": RED.util.compareObjects(PRES, PRES_A),
    PRES, PRES_A, addtext
}
return msg;

The function is outputting ALWAYS "Equal Objects"...

What i'm doing wrong? Can you help me please?

Best Regards,
Pau

Add some global.warn() statements in the function to work out what is going on.

However there are all sorts of traps to fall into when referencing objects in global memory, as the global will actually hold effectively a pointer to an object, not the actual object. Therefore if you change an object referenced by a global then it immediately changes the global reference.
You have the line

    global.set("PRES_A", PRES); //overwrite PRES_A with PRES values

Once you execute that then both globals are referencing the same object in memory, so they will always be equal (as they are pointing to the same object).

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.