Global context mutability

I have a function-file node in which I would like to use configs which are stored in the global context.

I am struggling because my approach appears to mutate the global context which I do not want. I am not sure whether I am misunderstanding Node-red, JavaScript or what?

The basic principle is that there is a bunch of config in the global context for sensors and devices. Sensors reference the device upon which they are mounted. Devices reference the room in which they are located.

global.const = {
  sensors: [
    {
      name: 'sensor_light_1',
      device: 'room_1',
    },
    {
      name: 'sensor_humidity',
      device: 'room_1',
    },
    {
      name: 'sensor_temp',
      device: 'room_2',
    }
  ],
  devices: [
    {
      name: 'room_1',
      location: 'bedroom',
    },
    {
      name: 'room_2',
      location: 'kitchen',
    }
  ]
}

I would like to be able to create a means of retrieving a sensor config and replacing the reference to the device with the device's config:

// Transforming this
{
  name: 'sensor_light_1',
  device: 'room_1',
}

// Into this
{
  name: 'sensor_light_1',
  device: {
   name: 'room_1',
   location: 'bedroom',
  }
}

However, my approach seems to modify the Global context replacing the sensor config with the combined result.

A simplified example is here:

class Sensors {
  constructor(config) {
    this.sensors = config.sensors;
    this.devices = config.devices;
  }

  getSensor(sensorName) {
    // Find the sensor config with the matching name
    const sensor = this.sensors
      .find((sensor) => sensor.name === sensorName);

    // Pass the config to a method with replaces the
    // device name property with that device's config
    return this.addDevice(sensor);
  }

  addDevice(sensor) {
    const sensorWithDevice = sensor;

    // Find the device config with the matching name
    const sensorDevice = this.devices
      .find((device) => device.name === sensor.device);

    // Replace the name property in the sensor config
    // with the device config
    sensorWithDevice.device = sensorDevice;

    return sensorWithDevice;
  }
}

const sensors = new Sensors({
  sensors: global.get('const').sensors,
  devices: global.get('const').devices,
});

msg.payload = sensors.getSensor('sensor_light_1');

return msg;

JavaScript passes objects by reference and we don't automatically clone them for you for performance reasons. If you want to modify the object without affecting context, you have to clone it or rebuild it.

1 Like

It would, therefore, appear to be my ignorance.

Thank you so much for your speedy response. I'll post back with the modified code for the benefit of the next naive soul.

Many thanks.

Just for fun, a solution using jsonata, passing the original object as inp1

$map(
   inp1.sensors,
   function($v, $i, $a) {
       {
           "name" : $v.name,
           "device" : inp1.devices[name=$v.device]
       } 
   }
)

Testing flow:

[{"id":"f40003a7.9b24c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"81f2584b.e458a8","type":"inject","z":"f40003a7.9b24c","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":160,"wires":[["6030a7f8.16b498"]]},{"id":"6030a7f8.16b498","type":"function","z":"f40003a7.9b24c","name":"Dataset","func":"msg.inp1=  {\n  sensors: [\n    {\n      name: 'sensor_light_1',\n      device: 'room_1',\n    },\n    {\n      name: 'sensor_humidity',\n      device: 'room_1',\n    },\n    {\n      name: 'sensor_temp',\n      device: 'room_2',\n    }\n  ],\n  devices: [\n    {\n      name: 'room_1',\n      location: 'bedroom',\n    },\n    {\n      name: 'room_2',\n      location: 'kitchen',\n    }\n  ]\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":160,"wires":[["3a8db7c1.a3c548","dea41cbb.f370a"]]},{"id":"908406a9.d219a8","type":"debug","z":"f40003a7.9b24c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":730,"y":160,"wires":[]},{"id":"3a8db7c1.a3c548","type":"change","z":"f40003a7.9b24c","name":"","rules":[{"t":"set","p":"out1","pt":"msg","to":"$map(\t   inp1.sensors,\t   function($v, $i, $a) {\t       {\t           \"name\" : $v.name,\t           \"device\" : inp1.devices[name=$v.device]\t       } \t   }\t)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":160,"wires":[["908406a9.d219a8"]]},{"id":"dea41cbb.f370a","type":"debug","z":"f40003a7.9b24c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":560,"y":100,"wires":[]}]
2 Likes

Not really very enlightening...

...
const config = RED.util.cloneMessage(global.get('const'));
const sensors = new Sensors({
  sensors: config.sensors,
  devices: config.devices,
});

msg.payload = sensors.getSensor('sensor_light_1');

return msg;

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