Is this a job for an array?

I have a large number of sensors, which return ID:value pairs like this-

DS18B20-1: object
    Id: "030197946C6B"
    Temperature: 21.6

I want to change each ID into a 'friendly name', and also to apply a offset against each ID to compensate for each sensor's innacuracy.
I'm thinking of a table, with |ID|FriendlyName|Offset| rows. I need to access the table from any flow.

Question: is an array the correct approach for this "look-up table"?

You could create a lookup object and store it in flow or global context.

E.g...

Inject > change node

The change node would set flow.offsets to something like...

{
"030197946C6B":-5.88,
"030197946C7C":12.63
}

Then whenever you need to grab the offset use a function node...

var id = msg.payload.Id;//or wherever you get the id from
var offsets = flow.get("offsets") || {};// get the lookup object
var offset = offsets[id];//perform lookup to get offset

Notes...
I have not added any defaults or error checking
You would be wise to store not just an offset number in the lookup but an actual object for future extensibility (e.g don't just store a number, store an object with the offset, calibration date, who calibrated, a friendly name, other relevant data)

As Steve say's, in Javascript this would be an Object.

{
    "030197946C6B": "My nice friendly name",
    ...
}

I go further and have various "meta-data" in a lookup table:

{
    "030197946C6B": {
        "name": "Friendly Name",
        "location": "Living Room",
        "sensors": "THL", // T=Temperature, H=Humidity, L=Light - this could be an array instead
        ...
    },
    ...
}

Using an array would be a pain since you would have to walk the array (do a search through it) every time you wanted to look up the data for the sensor. Nice thing about using newer versions of JavaScript is that you can use an Object as an array simply by doing Object.values(obj). you can also get all of the keys as an array with Object.keys(obj). Both can be useful if you do need to walk through a complete object (for example to return all of the object to ui_table or the equivalent in uibuilder).

I can certainly see the usefulness in the 'meta-data' concept.

What's the wrapper for this look-up table? Inside a Function node?

Do you mean where is the lookup object stored or generated?

As i wrote in my first post, you can use an inject and change node to generate the JS object & store it in flow or global. Then a function node (using flow.get) to retrieve it

e.g....

[{"id":"c1159f62.90212","type":"comment","z":"eea1fe59.ca838","name":"Initialise lookup object & store it in flow context","info":"","x":950,"y":80,"wires":[]},{"id":"e55f078d.4590e8","type":"inject","z":"eea1fe59.ca838","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":840,"y":120,"wires":[["b04c7d77.b4b64"]]},{"id":"b04c7d77.b4b64","type":"change","z":"eea1fe59.ca838","name":"","rules":[{"t":"set","p":"sensors","pt":"flow","to":"{\"030197946C6B\":{\"name\":\"Sensor 1\",\"location\":\"greenhouse\",\"calibationOffset\":-5.12},\"030197946C7A\":{\"name\":\"Sensor 2\",\"location\":\"outhouse\",\"calibationOffset\":1.74}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":1040,"y":120,"wires":[[]]},{"id":"fdf6e51b.e8f788","type":"inject","z":"eea1fe59.ca838","name":"","topic":"","payload":"030197946C6B","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":820,"y":220,"wires":[["f1dcf04.e4b191"]]},{"id":"7256044a.770ebc","type":"inject","z":"eea1fe59.ca838","name":"","topic":"","payload":"030197946C7A","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":820,"y":260,"wires":[["f1dcf04.e4b191"]]},{"id":"602a40cf.ade86","type":"debug","z":"eea1fe59.ca838","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1230,"y":220,"wires":[]},{"id":"1d7ec3aa.a4eeac","type":"comment","z":"eea1fe59.ca838","name":"Get lookup object from flow context","info":"","x":880,"y":180,"wires":[]},{"id":"f1dcf04.e4b191","type":"function","z":"eea1fe59.ca838","name":"get sensor details","func":"var id = msg.payload;//or wherever you get the id from\nvar sensors = flow.get(\"sensors\") || {};// get the lookup object\nmsg.payload = sensors[id];//perform lookup to get offset\nmsg.payload.id = id;\nreturn msg;","outputs":1,"noerr":0,"x":1050,"y":220,"wires":[["602a40cf.ade86"]]}]

Ah, thanks for that example ~ I can see the 'table' buried in the Change node...

Even more learning required methinks!

The "table" is a text representation of a JavaScript object (or JSON - JavaScript Object Notation)

Something you will become aware of as you get more familiar with this environment.

So this example creates a JS "lookup" object and stores it in flow memory

Then the function node GETS the object out of flow context and then performs a lookup (I commented up the code to help you)

That's how I generally do it but I use a persistent global variable so I no longer have to run it every time Node-RED restarts, only when it changes. You could, of course, also add a simple web interface to update it rather than having to use the Editor UI each time - though unless you have multiple people doing editing of flows or you want your family or non-NR using colleagues to edit it, there is little benefit. The inject node now has a nice JSON editor built in which makes it easy.

Over time you can then integrate other ways to change the meta-data if you wanted to. With Node-RED, you don't have to do everything at once.

Here is a single inject node with the data from my example above:

[{"id":"4578c040.57087","type":"inject","z":"a851b997.a01c18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"030197946C6B\":{\"name\":\"Friendly Name\",\"location\":\"Living Room\",\"sensors\":\"THL\"}}","payloadType":"json","x":300,"y":300,"wires":[[]]}]

And here are the 2 simple flows - 1 to set the metadata and 1 that simulates incoming data from your sensor and combines with the metadata:

[{"id":"4578c040.57087","type":"inject","z":"a851b997.a01c18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"030197946C6B\":{\"name\":\"Friendly Name\",\"location\":\"Living Room\",\"sensors\":\"THL\"}}","payloadType":"json","x":310,"y":300,"wires":[["babc5eb4.40533"]]},{"id":"60a5f2f4.37284c","type":"function","z":"a851b997.a01c18","name":"","func":"/*jshint asi:true*/\n\nconst id = msg.payload.Id\n\nconst meta = flow.get('meta')\n\nif ( meta[id] ) {\n    msg.payload.name = meta[id].name\n    // ...\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":460,"y":380,"wires":[["d32e9eba.f4cc1"]]},{"id":"1b7786b2.601c89","type":"inject","z":"a851b997.a01c18","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"Id\":\"030197946C6B\",\"Temperature\":21.6}","payloadType":"json","x":310,"y":380,"wires":[["60a5f2f4.37284c","95dfe296.3bd2b"]]},{"id":"babc5eb4.40533","type":"change","z":"a851b997.a01c18","name":"","rules":[{"t":"set","p":"meta","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":300,"wires":[[]]},{"id":"d32e9eba.f4cc1","type":"debug","z":"a851b997.a01c18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":380,"wires":[]}]

You could do it with JSONata as well but for me, JS is usually quicker and easier for most things.

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