Modbus data aggregation for plotting

Hi everyone,
I'm currently working with Node-RED to build a dashboard. I have three Modbus requests that return energy data from different sources (here transformed to functions). My goal is to create a line or bar chart that visualizes these individual values as well as the total combined value.

I thought I had it working with the current flow, but in the debug output I’m receiving 12 values (3 trafos * 4 values) instead of the four I need. I suspect the issue might be related to trafo 7a, but I’m not entirely sure where the problem is.

Any insights or suggestions would be greatly appreciated.
Thanks a lot for your help!

[
    {
        "id": "21297489339be1a6",
        "type": "group",
        "z": "63fc936c1bc6a015",
        "name": "total energy",
        "style": {
            "label": true
        },
        "nodes": [
            "0de4975323a54482",
            "12956cb651baf94a",
            "f6588068a9b9ea02",
            "72c65f466ad950a8",
            "95eb25d73301aceb",
            "9097e0b15a6490e6",
            "c0069a539bc50eb6",
            "013f33106ceb7d94",
            "0a09dd6719ee601c",
            "68c7e8081c4d8de9"
        ],
        "x": 294,
        "y": 799,
        "w": 1412,
        "h": 162
    },
    {
        "id": "0de4975323a54482",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "parse energy float",
        "func": "let registers = msg.payload;\nconst label =  \"totalenergy\";\n\nconst bytes = [];\nfor (const reg of registers) {\n    bytes.push((reg >> 8) & 0xFF);\n    bytes.push(reg & 0xFF);\n}\n\nconst buf = Buffer.from(bytes);\nconst value = buf.readBigInt64BE(0); //big int 64!!\n\nmsg.payload = { [label]: value };\nmsg.deviceId = msg.deviceId || \"trafo8\"; \n\n//sets msg.deviceId to its current value otherwise trafo 8\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 890,
        "y": 840,
        "wires": [
            [
                "12956cb651baf94a"
            ]
        ]
    },
    {
        "id": "12956cb651baf94a",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "aggregate and timestamp ",
        "func": "// Aggregate energy readings per device\n\n// retrieve current global energy data\nlet allData = flow.get(\"allEnergy\") || {};\n\n// check if there is energy data already \nlet deviceData = allData[msg.deviceId] || {};\n\n// store totalenergy under device data\ndeviceData.totalenergy = msg.payload.totalenergy;\n\n//store timestamp\ndeviceData.timestamp = new Date().toISOString();\n\n//update allData object \nallData[msg.deviceId] = deviceData;\n\n//save in flow context\nflow.set(\"allEnergy\", allData);\n\n//combines device ID and all data for that device in a single object \nmsg.payload = { deviceId: msg.deviceId, totalenergy: deviceData.totalenergy, timestamp: deviceData.timestamp };\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "flow.set(\"allEnergy\", {});",
        "finalize": "",
        "libs": [],
        "x": 1130,
        "y": 880,
        "wires": [
            [
                "9097e0b15a6490e6"
            ]
        ]
    },
    {
        "id": "f6588068a9b9ea02",
        "type": "inject",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "Start Trafos",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": "",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 410,
        "y": 880,
        "wires": [
            [
                "c0069a539bc50eb6",
                "013f33106ceb7d94",
                "0a09dd6719ee601c"
            ]
        ]
    },
    {
        "id": "72c65f466ad950a8",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "parse energy float",
        "func": "let registers = msg.payload;\nconst label = \"totalenergy\";\n\nconst bytes = [];\nfor (const reg of registers) {\n    bytes.push((reg >> 8) & 0xFF);\n    bytes.push(reg & 0xFF);\n}\nconst buf = Buffer.from(bytes);\nconst value = buf.readBigInt64BE(0); //big int 64!!\nmsg.payload = { [label]: value };\nmsg.deviceId = msg.deviceId || \"trafo 7a\";\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 890,
        "y": 880,
        "wires": [
            [
                "12956cb651baf94a"
            ]
        ]
    },
    {
        "id": "95eb25d73301aceb",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "parse energy float",
        "func": "let registers = msg.payload;\nconst label = \"totalenergy\";\n\nconst bytes = [];\nfor (const reg of registers) {\n    bytes.push((reg >> 8) & 0xFF);\n    bytes.push(reg & 0xFF);\n}\nconst buf = Buffer.from(bytes);\nconst value = buf.readBigInt64BE(0); //big int 64!!\nmsg.payload = { [label]: value };\nmsg.deviceId = msg.deviceId || \"trafo 7b\";\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 890,
        "y": 920,
        "wires": [
            [
                "12956cb651baf94a"
            ]
        ]
    },
    {
        "id": "9097e0b15a6490e6",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "message for individual chart",
        "func": "let allEn = flow.get(\"allEnergy\") || {};\nlet messages = [];\nlet total = 0n; // BigInt\n\nfor (let key in allEn) {\n    let value = allEn[key]?.totalenergy || 0n;\n    total += value;\n\n    // push single values \n    let roundedValue = Math.round((Number(value) / 1000) * 100) / 100;\n    messages.push({\n        payload: roundedValue,\n        topic: key\n    });\n}\n\n// push total only once \nlet totalRounded = Math.round((Number(total) / 1000) * 100) / 100;\nmessages.push({\n    payload: totalRounded,\n    topic: \"total\"\n});\n\nreturn [messages];\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1380,
        "y": 880,
        "wires": [
            [
                "68c7e8081c4d8de9"
            ]
        ]
    },
    {
        "id": "c0069a539bc50eb6",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "modbus simulation trafo 8",
        "func": "msg.payload = [0, 0, 5041, 61918];\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 840,
        "wires": [
            [
                "0de4975323a54482"
            ]
        ]
    },
    {
        "id": "013f33106ceb7d94",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "modbus simulation trafo 7a",
        "func": "msg.payload= [0,0,2562,55629] ;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 880,
        "wires": [
            [
                "72c65f466ad950a8"
            ]
        ]
    },
    {
        "id": "0a09dd6719ee601c",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "modbus simulation trafo 7b",
        "func": "msg.payload = [0,0,363,2726] ;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 920,
        "wires": [
            [
                "95eb25d73301aceb"
            ]
        ]
    },
    {
        "id": "68c7e8081c4d8de9",
        "type": "debug",
        "z": "63fc936c1bc6a015",
        "g": "21297489339be1a6",
        "name": "message",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1600,
        "y": 840,
        "wires": []
    }
]

Hi and welcome to the forum.

For the first few days, you will be limited to how many replies/posts you can make.

Fair enough you have a problem displaying the data, but posting the flow - I'm guessing that's what you posted - not many people will be keen in getting it as it is going to mess with their set ups.

Dashboards, foreign nodes - not sure if you have any.

What I'd suggest you do is make a sample flow with only the non graphic nodes.
Rather than graphs/charts, put debug nodes.

Rather than receiving data from ..... what ever: use inject nodes with nominal values.
(So that is quite a few of them all with different values)

Hi,
Thanks for the welcoming and your recommendation.
I already exchanged the nodes that are unusable for others and as a "preview" I added a screenshot of the flow. I think that is what you suggested right?

Add debug nodes to work out which node is sending the extra messages.

The "message" debug, the one that I added at the last function sends the additional messages. But I think the problem could be in the beginning because three modbus nodes are called.

Rather then doing the modbus fetches in parallel, do the first one, extract the data, and store the result in, for example, msg.trafo8. Then use that to trigger the next modbus fetch (which should not destroy msg.traf08), and save that in msg.trafo7a, and so on. Then at the end you can access all the data in one message. So the flow will look something like

That worked out perfect!
Thanks a lot.

Hi again,
Sadly I encountered another problem with this flow. Somehow the flow does not wait until all values are ready and triggers the aggregate function too early. When I Start Trafos manually the issue is not there anymote. I tried to fix that with severall codes. Like:

flow.set("trafo8", null);
flow.set("trafo7a", null);
flow.set("trafo7b", null);

But sadly nothing has worked. Maybe you have any suggestions.

Export the flow and paste it here please.

I am confused by your reference to flow context. You should be saving the data in the message not in context. You are probably seeing a race condition involving context which will not happen if you use message properties.

Here is the flow (but the modbus simulator only has one value I don't know how to simulate it with several values after eachother). Maybe it still works to find the error. This flow works because the values are always the same. But if the values are exchanged I think something happens with trafo 7a and 8 because these get wrong values while 7b gets a new good one. If you want you could try it with these second modbus values: 8: [0,0, 5060, 40735] , 7a : [0,0, 2629,643], 7b: [0, 0, 367, 60249]
Flow:

[
    {
        "id": "9b7bfd5cf2534710",
        "type": "group",
        "z": "63fc936c1bc6a015",
        "name": "total energy ",
        "style": {
            "label": true
        },
        "nodes": [
            "63dc1ec4749863fc",
            "167080a0e22e70f0",
            "92d43bd7c7015a93",
            "c97246c5174e0828",
            "e4e857955c0bdcc8",
            "da490370d21f677d",
            "10eea56da32fb77a",
            "1f524511d68b7bb1",
            "9afaf762f475ba48",
            "6fa0aebdb9b83a87",
            "25e25372848eb69b",
            "56671e568a47a6b2",
            "c2a1c570abdecc8b",
            "1ab29637f7078e3d",
            "b388748a9a771348"
        ],
        "x": 116,
        "y": 1378,
        "w": 1250,
        "h": 302
    },
    {
        "id": "63dc1ec4749863fc",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "request energy ",
        "func": "const req = { topic: \"energy\", address: 32095, quantity: 4, fc: 3 };\nreturn {\n    payload: { fc: req.fc, address: req.address, quantity: req.quantity },\n    topic: req.topic,\n    deviceId: msg.deviceId || \"trafo8\"  \n};",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 1460,
        "wires": [
            [
                "c2a1c570abdecc8b"
            ]
        ]
    },
    {
        "id": "167080a0e22e70f0",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "parse energy int",
        "func": "let registers = msg.payload;\nconst bytes = [];\n\nfor (const reg of registers) {\n    bytes.push((reg >> 8) & 0xff);\n    bytes.push(reg & 0xff);\n}\n\nconst buf = Buffer.from(bytes);\nconst value = buf.readBigInt64BE(0);\n\nconst numValue = Number(value);\n\nmsg.topic = \"trafo8\";  \nmsg.payload = numValue;\n\nflow.set(\"trafo8\", value);\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 872,
        "y": 1459,
        "wires": [
            [
                "da490370d21f677d",
                "6fa0aebdb9b83a87"
            ]
        ]
    },
    {
        "id": "92d43bd7c7015a93",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "aggregate and timestamp ",
        "func": "let trafo8 = flow.get(\"trafo8\");\nlet trafo7a = flow.get(\"trafo7a\");\nlet trafo7b = flow.get(\"trafo7b\");\n\nflow.set(\"trafo8\", null);\nflow.set(\"trafo7a\", null);\nflow.set(\"trafo7b\", null);\n\nif (typeof trafo8 === \"bigint\") trafo8 = Number(trafo8);\nif (typeof trafo7a === \"bigint\") trafo7a = Number(trafo7a);\nif (typeof trafo7b === \"bigint\") trafo7b = Number(trafo7b);\n\nlet total = trafo8 + trafo7a + trafo7b;\n\nmsg.payload = {\n    trafo8: Math.round((trafo8) / 1000 * 10) / 10,\n    trafo7a: Math.round((trafo7a) / 1000 * 10) / 10,\n    trafo7b: Math.round((trafo7b) / 1000 * 10) / 10,\n    total: Math.round((total) / 1000 * 10) / 10,\n    timestamp: new Date().toISOString()\n}\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1152,
        "y": 1559,
        "wires": [
            [
                "9afaf762f475ba48"
            ]
        ]
    },
    {
        "id": "c97246c5174e0828",
        "type": "inject",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "Start Trafos",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "1",
        "crontab": "",
        "once": false,
        "onceDelay": "",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 232,
        "y": 1499,
        "wires": [
            [
                "63dc1ec4749863fc"
            ]
        ]
    },
    {
        "id": "e4e857955c0bdcc8",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "request energy",
        "func": "// Single request for 'energy'\nconst req = { topic: \"energy\", address:32095, quantity: 4, fc: 3 };\nreturn {\n    payload: { fc: req.fc, address: req.address, quantity: req.quantity },\n    topic: req.topic,\n    deviceId: msg.deviceId || \"trafo7b\"  \n};",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 1580,
        "wires": [
            [
                "b388748a9a771348"
            ]
        ]
    },
    {
        "id": "da490370d21f677d",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "request energy",
        "func": "// Single request for 'energy'\nconst req = { topic: \"energy\", address: 32095, quantity: 4, fc: 3 };\nreturn {\n    payload: { fc: req.fc, address: req.address, quantity: req.quantity },\n    topic: req.topic,\n    deviceId: msg.deviceId || \"trafo7a\"  \n};",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 1520,
        "wires": [
            [
                "1ab29637f7078e3d"
            ]
        ]
    },
    {
        "id": "10eea56da32fb77a",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "parse energy int",
        "func": "let registers = msg.payload;\nconst bytes = [];\n\nfor (const reg of registers) {\n    bytes.push((reg >> 8) & 0xff);\n    bytes.push(reg & 0xff);\n}\n\nconst buf = Buffer.from(bytes);\nconst value = buf.readBigInt64BE(0);\n\nconst numValue = Number(value);\n\nmsg.topic = \"trafo7a\";  \nmsg.payload = numValue;\n\nflow.set(\"trafo7a\", value);\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 872,
        "y": 1519,
        "wires": [
            [
                "e4e857955c0bdcc8",
                "25e25372848eb69b"
            ]
        ]
    },
    {
        "id": "1f524511d68b7bb1",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "parse energy int ",
        "func": "let registers = msg.payload;\nconst bytes = [];\n\nfor (const reg of registers) {\n    bytes.push((reg >> 8) & 0xff);\n    bytes.push(reg & 0xff);\n}\n\nconst buf = Buffer.from(bytes);\nconst value = buf.readBigInt64BE(0);\n\nconst numValue = Number(value);\n\nmsg.topic = \"trafo7b\";  \nmsg.payload = numValue;\n\nflow.set(\"trafo7b\", value);\n\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 872,
        "y": 1579,
        "wires": [
            [
                "56671e568a47a6b2",
                "92d43bd7c7015a93"
            ]
        ]
    },
    {
        "id": "9afaf762f475ba48",
        "type": "debug",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "debug 37",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1260,
        "y": 1460,
        "wires": []
    },
    {
        "id": "6fa0aebdb9b83a87",
        "type": "debug",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "debug 38",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1032,
        "y": 1419,
        "wires": []
    },
    {
        "id": "25e25372848eb69b",
        "type": "debug",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "debug 39",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1072,
        "y": 1499,
        "wires": []
    },
    {
        "id": "56671e568a47a6b2",
        "type": "debug",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "debug 40",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1032,
        "y": 1639,
        "wires": []
    },
    {
        "id": "c2a1c570abdecc8b",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "modbus simulation trafo 8",
        "func": "msg.payload = [0, 0, 5041, 61918];\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 650,
        "y": 1460,
        "wires": [
            [
                "167080a0e22e70f0"
            ]
        ]
    },
    {
        "id": "1ab29637f7078e3d",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "modbus simulation trafo 7a",
        "func": "msg.payload= [0,0,2562,55629] ;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 1520,
        "wires": [
            [
                "10eea56da32fb77a"
            ]
        ]
    },
    {
        "id": "b388748a9a771348",
        "type": "function",
        "z": "63fc936c1bc6a015",
        "g": "9b7bfd5cf2534710",
        "name": "modbus simulation trafo 7b",
        "func": "msg.payload = [0,0,363,2726] ;\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 1580,
        "wires": [
            [
                "1f524511d68b7bb1"
            ]
        ]
    }
]

I suggested in my first post

I don't see where you are storing the data in msg.traf08.

Yes I know but modbus looses the msg.trafo8/7a. That's why I did it with

msg.topic = "trafo8";  
msg.payload = numValue;
flow.set("trafo8", value);

it still does not work but atleast it sends values and not "undefined".

Look for an option in the modbus node to "keep msg properties"

Thanks for the recommendation. I already did that.
The weird thing is that if I inject manually it works perfectly with the flow that I send above but if do it with intervall the values of 8 and 7a are wrong but 7b is always right. Even with the first intervall inject it is wrong.

Use debug nodes to determine where the properties are lost. At a quick glance of your flow, I'm pretty sure it's because you return a brand new message from the functions instead of updating &returning the original message.

Disclaimer: I didn't load your flow. I only glanced the function code in this forum thread. So my apologies if I've misunderstood.

Can you also share your flow with the actual Modbus nodes, instead of the one with the simulated data using Function nodes. Maybe some recommendations can be made to the original flow.

The properties are not lost. Up to the aggregate function the debug shows the correct values. After that the values of 8 and 7a are wrong.
Another weird thing I just discovered is that if the inject node makes intervalls of 1 seconds I get correct values after the aggregate function. If the intervalls get > 1 second I get wrong values. I am confused.
Thanks to all for the help I really appreciate it.

I can but I guess it will not be useful as you don't get any values out of the modbus getter. Maybe a picture helps

Guys I finally found the problem.
I used the same flow.set values for two different flows so the values got mixed up.
Now it makes sense.
Sorry I did not know that I should not do that.